1. 程式人生 > 其它 >C語言網路程式設計-tcp伺服器實現

C語言網路程式設計-tcp伺服器實現

5種io模型

tcp伺服器分為了5種io複用模型,分別是:

阻塞io模型

非阻塞io模型

io複用

訊號驅動io

非同步io

本文會講前面3種io模型的tcp伺服器實現(本文只做tcp伺服器實現,客戶端邏輯處理,接收資料等緩衝區不做深入說明)

簡單實現

首先,我們需要理解下tcp伺服器的建立過程:

1:通過socket函式建立一個套接字檔案

2:通過bind函式將本地一個地址和套接字捆綁

3:使用listen函式監聽外部請求

4:使用accept函式接收外部請求

5:read,write,close 用於收,發,關閉客戶端資料

好了,我們瞭解了tcp伺服器的建立過程,就開始實現吧:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #defineBUFFER_SIZE1024 intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str);
intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 intstr_length; server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port);
server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); addr_size=sizeof(client_addr); client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); while(1){ str_length=read(client_socket,buffer,BUFFER_SIZE); if(str_length==0)//讀取資料完畢關閉套接字 { close(client_socket); printf("連線已經關閉:%d\n",client_socket); break; }else{ printf("客戶端傳送資料:%s",buffer); write(client_socket,buffer,str_length);//傳送資料 } } return0; }

多客戶端TCP伺服器

以上程式碼實現了一個伺服器,並且可以接收一個客戶端連線,和它互相收發資訊,但是看程式碼很容易發現不支援多客戶端,只支援一個,那麼怎麼才能實現支援多個客戶端呢?我們稍微改一改這份程式碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #defineBUFFER_SIZE1024 intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str); intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 size_tclient_arr[100];//儲存客戶端陣列 intclient_length=0;//記錄客戶端數量 intstr_length; server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port); server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); while(1){ addr_size=sizeof(client_addr); client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); client_arr[client_length]=client_socket; client_length++; printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); for(inti=0;i<client_length;++i){ if(client_arr[i]==0){ continue; } str_length=read(client_arr[i],buffer,BUFFER_SIZE); if(str_length==0)//讀取資料完畢關閉套接字 { close(client_arr[i]); client_arr[i]=0; printf("連線已經關閉:%d\n",client_arr[i]); break; }else{ printf("客戶端傳送資料:%s",buffer); write(client_arr[i],buffer,str_length);//傳送資料 } } } }

我們通過將client_socket儲存到一個數組裡,然後每次去遍歷該陣列,可以勉強實現一個所謂的多客戶端tcp伺服器,但是有個致命弱點:

由於accept,read函式是阻塞的,導致這份程式碼,每次執行都得客戶端連線,才能到下面的遍歷程式碼,導致程式碼根本就沒什麼卵用:

A客戶端連線好了,然後傳送了條訊息,伺服器還得等到B客戶端連線,才能接收到A的訊息

,然後,B客戶端傳送好訊息,需要C客戶端連線,然後還得A客戶端傳送了條訊息,才能遍歷到B客戶端的訊息

多程序TCP伺服器

這樣的話,這份程式碼根本沒什麼卵用啊!!!!!!該怎麼解決這個問題呢?????

我們或許可以通過多程序去解決這個問題,每個程序只處理一條客戶端,就不存在什麼阻塞不阻塞的問題了:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #include<sys/wait.h>//waitpid(); #defineBUFFER_SIZE1024 intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str); intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 intstr_length; pid_tpid; intstatus=0;//初始化狀態 server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port); server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); while(1){ addr_size=sizeof(client_addr); client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); pid=fork(); if(pid>0){ sleep(1);//父程序,進行下次迴圈,讀取客戶端連線事件 waitpid(-1,&status,WNOHANG|WUNTRACED|WCONTINUED); if(WIFEXITED(status)){ printf("status=%d\n",WEXITSTATUS(status)); } if(WIFSIGNALED(status)){//如果子程序是被訊號結束了,則為真 printf("signalstatus=%d\n",WTERMSIG(status)); //R->T } if(WIFSTOPPED(status)){ printf("stopsignum=%d\n",WSTOPSIG(status)); } //T->R if(WIFCONTINUED(status)){ printf("continue......\n"); } }elseif(pid==0){//子程序,進行阻塞式收發客戶端資料 while(1){ memset(buffer,0,sizeof(buffer)); str_length=read(client_socket,buffer,BUFFER_SIZE); if(str_length==0)//讀取資料完畢關閉套接字 { close(client_socket); printf("連線已經關閉:%d\n",client_socket); exit(1); }else{ printf("%d客戶端傳送資料:%s\n",client_socket,buffer); write(client_socket,buffer,str_length);//傳送資料 } } break; }else{ printf("建立子程序失敗\n"); exit(1); } } return0; }

通過多程序,我們可以實現一個較完美的多程序TCP伺服器,這個伺服器可以完美的去處理多個客戶端的資料

但是,一個程序處理一個連線,如果連線多的時候,會造成程序的頻繁建立銷燬,程序開銷會非常大,導致cpu佔用太大

所以,直接使用多程序去處理,還是不夠完美的

由第二個例子,我們可以發現,主要問題出在於accept,read函式的阻塞上面,有沒有什麼辦法處理掉這個阻塞呢?

非阻塞式TCP伺服器

在c語言中,可以使用fcntl函式,將套接字設定為非阻塞的

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #include<fcntl.h>//非阻塞 #defineBUFFER_SIZE1024 intset_non_block(intsocket){ intflags=fcntl(socket,F_GETFL,0); flags|=O_NONBLOCK; returnfcntl(socket,F_SETFL,flags); } intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str); intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 size_tclient_arr[100];//儲存客戶端陣列 intclient_length=0;//記錄客戶端數量 intstr_length; server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port); server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } if(set_non_block(server_socket)==-1){//設定非阻塞 printf("設定非阻塞失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); while(1){ addr_size=sizeof(client_addr); client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); if(client_socket>0){//非阻塞下,無法讀取返回-1 client_arr[client_length]=client_socket; client_length++; if(set_non_block(client_socket)==-1){//設定非阻塞 printf("設定客戶端非阻塞失敗\n"); exit(1); } printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); } for(inti=0;i<client_length;++i){ if(client_arr[i]==0){ continue; } memset(&buffer,0,sizeof(buffer)); str_length=read(client_arr[i],buffer,BUFFER_SIZE); if(str_length==-1){//非阻塞下,無法讀取返回-1 continue; } if(str_length==0)//讀取資料完畢關閉套接字 { close(client_arr[i]); client_arr[i]=0; printf("連線已經關閉:%d\n",client_arr[i]); break; }else{ printf("客戶端傳送資料:%s",buffer); write(client_arr[i],buffer,str_length);//傳送資料 } } usleep(100);//非阻塞下,如果全部socket無法讀取(沒有事件變化),則相當於是while(1),會使cpu繁忙 } }

這樣,我們就實現了一個單程序多客戶端的tcp伺服器了,不需要多程序也能實現多客戶端,但是看最後一行註釋能發現一個問題:非阻塞下,會無限迴圈,讓程式碼空轉,這樣浪費的效能也是巨大的,那我們該怎麼完善呢?或許我們可以用到I/O複用模型

select機制TCP伺服器

select是系統級別的功能,它可以同時阻塞探測多個socket,並且返回可呼叫的socket的數量

原理圖大概為:

實現程式碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #defineBUFFER_SIZE1024 intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str); intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 intstr_length; server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port); server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); fd_setreads,copy_reads; intfd_max,fd_num; structtimevaltimeout; FD_ZERO(&reads);//初始化清空socket集合 FD_SET(server_socket,&reads); fd_max=server_socket; while(1){ copy_reads=reads; timeout.tv_sec=5; timeout.tv_usec=5000; //無限迴圈呼叫select監視可讀事件 if((fd_num=select(fd_max+1,&copy_reads,0,0,&timeout))==-1){ perror("selecterror"); break; } if(fd_num==0){//沒有變動的socket continue; } for(inti=0;i<fd_max+1;i++){ if(FD_ISSET(i,&copy_reads)){ if(i==server_socket){//server_socket變動,代表有新客戶端連線 addr_size=sizeof(client_addr); client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); FD_SET(client_socket,&reads); if(fd_max<client_socket){ fd_max=client_socket; } }else{ memset(buffer,0,sizeof(buffer)); str_length=read(i,buffer,BUFFER_SIZE); if(str_length==0)//讀取資料完畢關閉套接字 { close(i); printf("連線已經關閉:%d\n",i); FD_CLR(i,&reads);//從reads中刪除相關資訊 }else{ printf("%d客戶端傳送資料:%s\n",i,buffer); write(i,buffer,str_length);//將資料傳送回客戶端 } } } } } return0; }

上面就是select機制的tcp實現程式碼,可以同時處理多客戶端,效能比多程序好了很多,但這並不是說明select機制沒有缺點了

在這份程式碼中,可以發現以下幾點:
1:客戶端的socket識別符號是存在一個fd_set型別中的集合中的,客戶端大小由fd_set大小決定,開發時需要考慮到這個的最大值

2:每次呼叫select函式之前,都得將集合重新傳給select,效率較慢;

3:每次呼叫完select函式,就算返回1,也會將集合全部遍歷一遍,效率較慢

epoll機制TCP伺服器

原理圖大概為:

epoll機制提供了以下3個核心函式:

epoll_create() 建立epoll監聽socket

epoll_ctl()註冊,刪除,修改監聽

epoll_wait() 等待事件觸發函式

在實現epoll機制前,我們得先了解下ET/LT模式

LT(level-trigger) 水平觸發

epoll的預設工作方式,在這個模式下,只要監聽的socket有可讀/可寫狀態,都將返回該socket,例如:

當客戶端給tcp伺服器傳送一個數據時,這個client_socket將會是可讀的,呼叫epoll_wait函式將會返回該client_socket,

如果伺服器不做處理,這個client_socket將會是一直可讀的,下次呼叫epoll_wait函式將會繼續返回client_socket

實現程式碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #include<sys/epoll.h>//epoll #defineBUFFER_SIZE1024 #defineCLIENT_MAX_SIZE1024 intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str); intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 intstr_length; server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port); server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); structepoll_eventevent;//監聽事件 structepoll_eventwait_event_list[CLIENT_MAX_SIZE];//監聽結果 intfd[CLIENT_MAX_SIZE]; intj=0; intepoll_fd=epoll_fd=epoll_create(10);//建立epoll控制代碼,裡面的引數10沒有意義 if(epoll_fd==-1){ printf("建立epoll控制代碼失敗\n"); exit(1); } event.events=EPOLLIN;//可讀事件 event.data.fd=server_socket;//server_socket intresult=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_socket,&event); if(result==-1){ printf("註冊epoll事件失敗\n"); exit(1); } while(1){ result=epoll_wait(epoll_fd,wait_event_list,CLIENT_MAX_SIZE,-1);//阻塞 if(result<=0){ continue; } for(j=0;j<result;j++){ printf("%d觸發事件%d\n",wait_event_list[j].data.fd,wait_event_list[j].events); //server_socket觸發事件 if(server_socket==wait_event_list[j].data.fd&&EPOLLIN==wait_event_list[j].events&EPOLLIN){ addr_size=sizeof(client_addr); client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); event.data.fd=client_socket; event.events=EPOLLIN;//可讀或錯誤 result=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_socket,&event); if(result==-1){ printf("註冊客戶端epoll事件失敗\n"); exit(1); } continue; } //客戶端觸發事件 if((wait_event_list[j].events&EPOLLIN) ||(wait_event_list[j].events&EPOLLERR))//可讀或發生錯誤 { memset(&buffer,0,sizeof(buffer)); str_length=read(wait_event_list[j].data.fd,buffer,BUFFER_SIZE); if(str_length==0)//讀取資料完畢關閉套接字 { close(wait_event_list[j].data.fd); event.data.fd=wait_event_list[j].data.fd; epoll_ctl(epoll_fd,EPOLL_CTL_DEL,wait_event_list[j].data.fd,&event); printf("連線已經關閉:%d\n",wait_event_list[j].data.fd); }else{ printf("客戶端傳送資料:%s\n",buffer); write(wait_event_list[j].data.fd,buffer,str_length);//執行回聲服務即echo } } } } //return0; }

lt模式下,也可以使用非阻塞模式,以上程式碼未使用

ET(edge-trigger) 邊緣觸發

通過註冊監聽增加EPOLLET引數可將模式轉換成邊緣觸發,

在et模式下,socket觸發的多個事件只會返回一次,必須一次性全部處理,例如:

server_socket 有10個待處理的新連線,在epoll_wait函式返回後,必須迴圈讀取accept直到沒有資料可讀,

由於必須一直迴圈讀取,所以當accept沒有資料可讀時,必須是非阻塞模式,否則會阻塞

實現程式碼

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 #include<stdio.h> #include<arpa/inet.h>//inet_addr()sockaddr_in #include<string.h>//bzero() #include<sys/socket.h>//socket #include<unistd.h> #include<stdlib.h>//exit() #include<sys/epoll.h>//epoll #defineBUFFER_SIZE1024 #defineCLIENT_MAX_SIZE1024 intset_non_block(intsocket){ intflags=fcntl(socket,F_GETFL,0); flags|=O_NONBLOCK; returnfcntl(socket,F_SETFL,flags); } intmain(){ charlisten_addr_str[]="0.0.0.0"; size_tlisten_addr=inet_addr(listen_addr_str); intport=8080; intserver_socket,client_socket; structsockaddr_inserver_addr,client_addr; socklen_taddr_size; charbuffer[BUFFER_SIZE];//緩衝區大小 intstr_length; server_socket=socket(PF_INET,SOCK_STREAM,0);//建立套接字 bzero(&server_addr,sizeof(server_addr));//初始化 server_addr.sin_family=INADDR_ANY; server_addr.sin_port=htons(port); server_addr.sin_addr.s_addr=listen_addr; if(bind(server_socket,(structsockaddr*)&server_addr,sizeof(server_addr))==-1){ printf("繫結失敗\n"); exit(1); } if(listen(server_socket,5)==-1){ printf("監聽失敗\n"); exit(1); } printf("建立tcp伺服器成功\n"); set_non_block(server_socket);//設定非阻塞 structepoll_eventevent;//監聽事件 structepoll_eventwait_event_list[CLIENT_MAX_SIZE];//監聽結果 intfd[CLIENT_MAX_SIZE]; intj=0; intepoll_fd=epoll_fd=epoll_create(10);//建立epoll控制代碼,裡面的引數10沒有意義 if(epoll_fd==-1){ printf("建立epoll控制代碼失敗\n"); exit(1); } event.events=EPOLLIN|EPOLLET;//註冊可讀事件+et模式 event.data.fd=server_socket;//server_socket intresult=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_socket,&event); if(result==-1){ printf("註冊epoll事件失敗\n"); exit(1); } while(1){ result=epoll_wait(epoll_fd,wait_event_list,CLIENT_MAX_SIZE,-1);//阻塞 if(result<=0){ continue; } for(j=0;j<result;j++){ printf("%d觸發事件%d\n",wait_event_list[j].data.fd,wait_event_list[j].events); //server_socket觸發事件 if(server_socket==wait_event_list[j].data.fd&&EPOLLIN==wait_event_list[j].events&EPOLLIN){ addr_size=sizeof(client_addr); while(1){ client_socket=accept(server_socket,(structsockaddr*)&client_addr,&addr_size); if(client_socket==-1){//沒有資料可讀 break; } printf("%d連線成功\n",client_socket); charmsg[]="恭喜你連線成功"; write(client_socket,msg,sizeof(msg)); set_non_block(client_socket);//設定非阻塞 event.data.fd=client_socket; event.events=EPOLLIN|EPOLLET;//可讀+et模式 result=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_socket,&event); if(result==-1){ printf("註冊客戶端epoll事件失敗\n"); exit(1); } } continue; } //客戶端觸發事件 if((wait_event_list[j].events&EPOLLIN) ||(wait_event_list[j].events&EPOLLERR))//可讀或發生錯誤 { memset(&buffer,0,sizeof(buffer)); while(1){ str_length=read(wait_event_list[j].data.fd,buffer,BUFFER_SIZE); //讀取多次資料 if(str_length==-1){//沒有資料返回 break; } if(str_length==0)//讀取資料完畢關閉套接字 { close(wait_event_list[j].data.fd); event.data.fd=wait_event_list[j].data.fd; epoll_ctl(epoll_fd,EPOLL_CTL_DEL,wait_event_list[j].data.fd,&event); printf("連線已經關閉:%d\n",wait_event_list[j].data.fd); }else{ printf("客戶端傳送資料:%s\n",buffer); write(wait_event_list[j].data.fd,buffer,str_length);//執行回聲服務即echo } } } } } //return0; }

以上說明,可看出:

1:epoll不需要遍歷其他沒有事件的socket,避免了select的效能浪費

2:epoll有兩種工作模式,用於不同的場景,et和lt模式都可以用非阻塞,但et模式必須非阻塞,et模式程式設計難度較大,每次epoll_wait都得考慮必須處理掉所有事件