1. 程式人生 > 其它 >網路程式設計:網路搶答器程式的實現

網路程式設計:網路搶答器程式的實現

網路程式設計:網路搶答器程式的實現

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++):

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