網路程式設計:網路搶答器程式的實現
阿新 • • 發佈:2021-06-14
網路程式設計:網路搶答器程式的實現
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