select實現多個客戶機與伺服器之間的通訊
阿新 • • 發佈:2018-12-16
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <sys/select.h> #include <sys/time.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define MAXFD 10 //陣列的大小用巨集定義出來 void fds_init(int fds[])// 初始化存放描述符的陣列,因為正確的描述符都是正數, { //所以將陣列中的所有元素都初始化為“-1” int i = 0; for( ; i < MAXFD; i++) { fds[i] = -1; } } void fds_add(int fds[], fd)//將新描述符新增到描述符陣列中 { int i = 0; for(; i < MAXFD; i++)// 遍歷陣列中,只要發現某一個元素是初始值,就代表沒有描述符存放 { //在該位置,所以就可以將新的描述符存進去 if( fds[i] == -1) { fds[i] = fd;// 注意,一旦找到存放位置,在存放後就要返回,如果不返回,i就會遍歷 return ; //整個陣列,將新描述符存進整個陣列 } } } void fds_del(int fds[], int fd)//將陣列中與fd相等的描述符刪除 { int i = 0; for( ; i < MAXFD; i++) { if(fds[i] == fd) { fds[i] = -1;//注意,一旦找到目標描述符,刪掉後就要返回。原因同存放描述符函式相同 return ; } } } int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); //建立套接字 assert(sockfd != NULL); struct sockaddr_in saddr, caddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //設定協議族埠號和ip地址 int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); //命名套接字 assert(res != -1); listen(sockfd, 5);//監聽套接字 int fds[MAXFD];//定義一個數組用來存放描述符 fds_init(fds);//初始化陣列 fds_add(fds, sockfd);//將sockfd先放進陣列中 while(1) { fd_set fdset; //給select建立一個“集合” FD_ZERO(&fdset);//把集合裡面清零 int maxfd = -1; //設定一個記錄最大描述符的變數,可以告訴select函式監視的描述符範圍,減少時間浪費 int i = 0; for( ; i < MAXFD; i++)//開始遍歷整個存放描述符的陣列 { if(fds[i] == -1)// 如果陣列中的元素的初始值被更改,見代表存放了描述符進去,如果初始值沒有 { //更改,就代表沒有存放描述符進去, break; } FD_SET(fds[i], &fdset);//按照描述符的大小在集合中偏移後做標記 if(fds[i] > maxfd) { maxfd = fds[i]; //集合中每進入一個描述符就將最大的用maxfd標記起來 } } struct timeval tv = {5, 0}; //給設定select函式設定監視集合的時間 int n = select(maxfd + 1, &fdset, NULL, NULL, &tv); //時間、大小、描述符存放好後開始讓select監視 if( n == -1) //如果在規定的時間內select返回值為“-1”就代表select函式發生錯誤,返回去繼續監視集合 { printf("select error!\n"); continue; } if( n == 0)// 如果在規定時間內select監視的集合中沒有描述符上面有資料傳來,就打超時時一次,繼續監視 { printf("time out!\n"); continue; } else//如果在監視的時間內發現有描述符傳來資料 { int i = 0; for( ; i < MAXFD; i++)//遍歷整個陣列,找存放的描述符 { if(fds[i] == -1) { continue; } if(FD_ISSET(fds[i] , &fdset))//每找到一個描述符就測試fds[i]是否可讀,即是否網路上有資料 { if(fds[i] == sockfd)// 如果fds[i]有資料傳來 ,就得判斷是新連線的客戶機還是已經建立 { //連線的客戶機發送給伺服器的資料 int len = sizeof(caddr); int c = accept(sockfd, (struct sockaddr*)caddr, &len); // 如果是新連線的客戶機, if( c < 0) //就將客戶機的埠號(檔案 { //描述符)存在陣列中 continue; } printf("accept = %d\n",c); //將新客戶機描述符打印出 fds_add(fds, c); //將新檔案描述符存進陣列 } else { char buff[128] = {0}; //如果是已經建立連線的客戶機發來的資料,就將資料接收在buff中 int num = recv(fds[i], buff, 127, 0); if( num <= 0) // 如果recv接受資料後返回錯誤值“-1”就代表接收失敗,如果返回“0” { //就代表客戶端斷開了與伺服器的連線,此時伺服器端也斷開個描述符所對應的客戶端 close(fds[i]); fds_del(fds, fds[i]); //將這個描述符從陣列中拿出去丟掉 printf("one client over!\n"); } else { printf("recv(%d) = %s\n",fds[i],buff);//如果recv返回的值大於0,就代表客戶端發來了資料,輸出伺服器傳送來資料 send(fds[i], "ok", 2, 0);//給客戶端傳送資料接收成功確認 } } } } } } exit(0); }