网络编程:网络抢答器程序的实现

Posted GXG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程:网络抢答器程序的实现相关的知识,希望对你有一定的参考价值。

网络编程:网络抢答器程序的实现

1.直接跳转到Linux端代码


导语:

这是网络编程的最后一个实验了,也就意味着此门课程进入了尾声,之前的实验代码也都有,我希望自己写的这些代码能够帮助到后来人。

代码中的注释详细,可以让初次接触的人也能看懂,大部分代码都具有相同的格式,比如创建套接字、设置套接字相关属性、捆绑、监听等。

这些代码使用的是C++,改成C语言也较为方便。

我使用的Linux系统是国产的UOS,推荐一下,完全可以满足日常的使用,比Windows更加省电和流畅。

此次我使用了和之前的select函数不同的epoll函数来实现多路复用


一、实验目的

实现基于多线程的网络抢答器程序。

二、实验内容

(1) 系统由1个服务器端和2个以上客户端组成;
(2) 事先准备多道简单题目,服务器随机出题,客户端进行抢答;
(3) 出题后5秒内如果无人抢答,自动进入下一题;
(4) 如果已有人抢答,则其他人再回答时,答案无效,并收到服务器的提示;
(5) 回答正确加分,错误减分,最后计算总成绩,并将结果发送给各客户端。

  1. 可以选择使用select函数、epoll函数等实现多线程抢答程序。编写程序前综合考虑协议制定、流程控制、数据结构等内容。编写程序过程中根据实际情况灵活使用临界区锁定、I/O分离等知识点。

Linux端效果图如下:

Linux端的(采用UOS+VScode+g++):
image

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

以上是关于网络编程:网络抢答器程序的实现的主要内容,如果未能解决你的问题,请参考以下文章

TinywebDB+MQTT实现掌控板抢答实验

TinywebDB+MQTT实现掌控板抢答实验

201555332盛照宗—网络对抗实验1—逆向与bof基础

竞争抢答器

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装