网络编程:网络抢答器程序的实现
Posted GXG
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程:网络抢答器程序的实现相关的知识,希望对你有一定的参考价值。
网络编程:网络抢答器程序的实现
1.直接跳转到Linux端代码
导语:
这是网络编程的最后一个实验了,也就意味着此门课程进入了尾声,之前的实验代码也都有,我希望自己写的这些代码能够帮助到后来人。
代码中的注释详细,可以让初次接触的人也能看懂,大部分代码都具有相同的格式,比如创建套接字、设置套接字相关属性、捆绑、监听等。
这些代码使用的是C++,改成C语言也较为方便。
我使用的Linux系统是国产的UOS,推荐一下,完全可以满足日常的使用,比Windows更加省电和流畅。
此次我使用了和之前的select函数不同的epoll函数来实现多路复用
一、实验目的
实现基于多线程的网络抢答器程序。
二、实验内容
(1) 系统由1个服务器端和2个以上客户端组成;
(2) 事先准备多道简单题目,服务器随机出题,客户端进行抢答;
(3) 出题后5秒内如果无人抢答,自动进入下一题;
(4) 如果已有人抢答,则其他人再回答时,答案无效,并收到服务器的提示;
(5) 回答正确加分,错误减分,最后计算总成绩,并将结果发送给各客户端。
- 可以选择使用select函数、epoll函数等实现多线程抢答程序。编写程序前综合考虑协议制定、流程控制、数据结构等内容。编写程序过程中根据实际情况灵活使用临界区锁定、I/O分离等知识点。
Linux端效果图如下:
Linux端的(采用UOS+VScode+g++):
Linux端代码如下:
1. 服务器端:
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/epoll.h>
#include<pthread.h>
const int BUF_SIZE = 1024;
const int EPOLL_SIZE = 50;
using namespace std;
struct questions
{
string question;
string answer;
bool condition;
};
struct score
{
int fd=0;
int score=0;
};
int FdCount=0;//记录连接的客户端数
int TitleNum=0;//当然问的问题的题号
struct score fdm[BUF_SIZE];//记录连接的客户端fd
//定义问题
struct questions que[10];
void count(int fd,int state);//计算得分
void* QuestionNotify(void*);//发送给所有客户端问题
int main() {
cout<<"等待客户端连接..."<<endl;
int server_sock, client_sock;
sockaddr_in server_addr, client_addr;
socklen_t addr_size;
ssize_t str_len;
int i;
char buf[BUF_SIZE];
epoll_event *ep_events;
epoll_event event;
int epfd, event_cnt;
//设置套接字相关属性
server_sock = socket(PF_INET, SOCK_STREAM, 0);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(1234);
/* 捆绑 sock 描述符 */
if (bind(server_sock, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
cout<<"捆绑出错!"<<strerror(errno)<<endl;
exit(1);
}
/* 监听 sock 描述符 */
if (listen(server_sock, 5) == -1) {
cout<<"监听出错!"<<strerror(errno)<<endl;
}
epfd = epoll_create(EPOLL_SIZE);// 创建监听红黑树
ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE);
event.events = EPOLLIN;//设置
event.data.fd = server_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, server_sock, &event);//添加监听fd
while (1) {
event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);// 监听
if (event_cnt == -1) {
cout<<"监听出错!"<<endl;
break;
}
for (int i = 0; i < event_cnt; ++i) {
if (ep_events[i].data.fd == server_sock) {
addr_size = sizeof(client_addr);
client_sock = accept(server_sock, (sockaddr*)&client_addr, &addr_size);
event.events = EPOLLIN;
event.data.fd = client_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, client_sock, &event);//添加监听fd
cout<<"已连接客户端: "<<client_sock<<endl;
fdm[FdCount++].fd=client_sock;
if(FdCount==3){
//有3个客户端连接之后,启动线程发送问题
pthread_t tid;
pthread_create(&tid,0,QuestionNotify,NULL);
}
else if(FdCount<3){
string buffer="参与答题人数不足三人,请稍等。";
send(client_sock,buffer.c_str(),strlen(buffer.c_str()),0);
}
else{
string buffer="答题已经开始,请赶紧参与答题。\\n";
send(client_sock,buffer.c_str(),strlen(buffer.c_str()),0);
}
} else {
str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
if (str_len == 0) {
epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);//添加监听fd
close(ep_events[i].data.fd);
cout<<"客户端退出: "<<ep_events[i].data.fd<<endl;
} else {
if (!strcmp(buf,que[TitleNum].answer.c_str())&&!que[TitleNum].condition) {
string buffer="恭喜你,回答正确,加10分,请准备下一题吧^-^。\\n";
que[TitleNum].condition=true;
count(ep_events[i].data.fd,1);
write(ep_events[i].data.fd, buffer.c_str(),strlen(buffer.c_str()));
for(int m=0;m<FdCount;m++){
if (fdm[m].fd != 0){
buffer="已有人回答正确本题抢答结束,你目前分数为"+to_string(fdm[m].score)+"\\n";
send(fdm[m].fd,buffer.c_str(),strlen(buffer.c_str()),0);
}
}
}
else if(!strcmp(buf,que[TitleNum].answer.c_str())&&que[TitleNum].condition){
string buffer="对不起抢答失败,请准备下一题吧^-^。\\n";
write(ep_events[i].data.fd, buffer.c_str(),strlen(buffer.c_str()));
}
else{
string buffer="对不起,回答错误,减10分,最低0分,请准备下一题吧^-^。\\n";
count(ep_events[i].data.fd,0);
write(ep_events[i].data.fd, buffer.c_str(),strlen(buffer.c_str()));
}
cout<<ep_events[i].data.fd<<"回答:"<<buf<<endl;
bzero(buf,sizeof(buf));//置字节字符串所有字节为零且包括\'\\0\'
}
}
}
}
close(server_sock);
close(epfd);
return 0;
}
void count(int fd,int state)//计算得分
{
if(state==1){
for(int i=0;i<FdCount;i++){
if(fdm[i].fd==fd)
fdm[i].score+=10;//每对一题加10分,共计一百分
}
}
else{
for(int i=0;i<FdCount;i++){
if(fdm[i].fd==fd&&fdm[i].score>0)
fdm[i].score-=10;//每错一题减10分,最低0分
}
}
}
void* QuestionNotify(void*)//发送所有问题
{
string buffer="参与人数已达到3人,请准备答题。\\n";
for(int m=0;m<FdCount;m++){
if (fdm[m].fd != 0){
send(fdm[m].fd,buffer.c_str(),strlen(buffer.c_str()),0);
}
}
//定义问题
que[0].question="问题一:戈小戈长得帅吗(帅/不帅)?"; que[0].answer="帅";
que[1].question="问题二:网络编程老师长得帅吗(帅/不帅)?"; que[1].answer="帅";
que[2].question="问题三:戈小戈是哪个大学的?"; que[2].answer="内蒙古大学";
que[3].question="问题四:网络编程老师孩子长得可爱吗(可爱/不可爱)?"; que[3].answer="可爱";
que[4].question="问题五:戈小戈是哪个专业的(计科/软工)?"; que[4].answer="计科";
que[5].question="问题六:网络编程老师教得好吗(好/不好)?"; que[5].answer="好";
que[6].question="问题七:戈小戈是男生还是女生(男/女)?"; que[6].answer="男";
que[7].question="问题八:网络编程老师孩子是男孩还是女孩(男/女)?"; que[7].answer="男";
que[8].question="问题九:戈小戈来自哪里(安徽/江苏)?"; que[8].answer="安徽";
que[9].question="问题十:网络编程老师会给高分吗(会/不会)?"; que[9].answer="会";
for(int i=0;i<10;i++)
que[i].condition=false;
//发送问题
for(int i=0;i<10;i++){
sleep(2);
TitleNum=rand()%10;
for(int m=0;m<FdCount;m++){
if (fdm[m].fd != 0){
send(fdm[m].fd,que[TitleNum].question.c_str(),strlen(que[TitleNum].question.c_str()),0);
}
}
clock_t start = clock();
while(1){
if(que[TitleNum].condition==true){
break;
}
clock_t end = (clock() - start)/CLOCKS_PER_SEC;
if((int)end>10&&!que[TitleNum].condition)//判断答题是否超过5秒
{
string buffer="所有人答题超时,请准备回答下一题。\\n";
for(int n=0;n<FdCount;n++){
if (fdm[n].fd != 0){
send(fdm[n].fd,buffer.c_str(),strlen(buffer.c_str()),0);
}
}
break;
}
}
}
for(int m=0;m<FdCount;m++){
if (fdm[m].fd != 0){
buffer="十道题已答完,你的总分数为"+to_string(fdm[m].score)+"\\n";
send(fdm[m].fd,buffer.c_str(),strlen(buffer.c_str()),0);
}
}
pthread_exit(NULL);
}
//g++ 网络编程作业8服务器端.cpp -o test -lpthread&&./test
2. 客户端:
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include<pthread.h>
const int BUF_SIZE = 1024;
void* QuestionAnswer(void* sock);//回答问题
using namespace std;
int main() {
int sock;
struct sockaddr_in server_addr;
char message[BUF_SIZE];
// 发送的字符串长度、接收字符串的长度、每次read函数接受到字符串的长度
ssize_t str_len, recv_len, recv_cnt;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
cout<<"套接字错误"<<endl;
}
// 地址信息初始化
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // IPV4 地址族
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IP地址
server_addr.sin_port = htons(1234); // 服务器端口号
// 向服务器发送连接请求
if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
cout<<"连接错误,无法连接到服务器"<<endl;
} else {
cout<<"已连接到服务器"<<endl;
}
while (1) {
int nbytes=0;
char mes[1024];
bzero(mes,sizeof(mes));//置字节字符串所有字节为零且包括\'\\0\'
nbytes=read( sock,mes,sizeof(mes));
if(nbytes>0)
{
// mes[nbytes]=\'\\0\';
cout<<"来自服务器的消息: "<<mes<<endl<<endl;
}
if(strstr(mes, "问题")!=NULL){
pthread_t tid;
pthread_create(&tid,0,QuestionAnswer,&sock);
}
else if(strstr(mes, "答完")!=NULL)
exit(0);
}
// 关闭连接
close(sock);
return 0;
}
void* QuestionAnswer(void* sock)//回答问题
{
int fd = *(int*)sock;
char message[BUF_SIZE];
ssize_t str_len;
cout<<"请输入答案( Q/q 退出 ): "<<endl;
fgets(message, BUF_SIZE, stdin);
// 如果输入q或者Q,则退出
if (!strcmp(message, "q\\n") || !strcmp(message, "Q\\n")) {
// 关闭连接
close(fd);
exit(0);
}
str_len = write(fd, message, strlen(message)-1); // 向服务器发送数据
bzero(message,sizeof(message));//置字节字符串所有字节为零且包括\'\\0\'
pthread_exit(NULL);
return 0;
}
//g++ 网络编程作业8客户端.cpp -o test2 -lpthread&&./test2
以上是关于网络编程:网络抢答器程序的实现的主要内容,如果未能解决你的问题,请参考以下文章