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
int main(){
char listen_addr_str[]= "0.0.0.0" ;
size_t listen_addr=inet_addr(listen_addr_str);
int port=8080;
int server_socket,client_socket;
struct sockaddr_inserver_addr,client_addr;
socklen_taddr_size;
char buffer[BUFFER_SIZE]; //緩衝區大小
int str_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,( struct sockaddr*)&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,( struct sockaddr*)&client_addr,&addr_size);
printf ( "%d連線成功\n" ,client_socket);
char msg[]= "恭喜你連線成功" ;
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); //傳送資料
}
}
return 0;
}
|
多客戶端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
int main(){
char listen_addr_str[]= "0.0.0.0" ;
size_t listen_addr=inet_addr(listen_addr_str);
int port=8080;
int server_socket,client_socket;
struct sockaddr_inserver_addr,client_addr;
socklen_taddr_size;
char buffer[BUFFER_SIZE]; //緩衝區大小
size_t client_arr[100]; //儲存客戶端陣列
int client_length=0; //記錄客戶端數量
int str_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,( struct sockaddr*)&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,( struct sockaddr*)&client_addr,&addr_size);
client_arr[client_length]=client_socket;
client_length++;
printf ( "%d連線成功\n" ,client_socket);
char msg[]= "恭喜你連線成功" ;
write(client_socket,msg, sizeof (msg));
for ( int i=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" );
}
} else if (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);
}
}
return 0;
}
|
通過多程序,我們可以實現一個較完美的多程序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
int set_non_block( int socket){
int flags=fcntl(socket,F_GETFL,0);
flags|=O_NONBLOCK;
return fcntl(socket,F_SETFL,flags);
}
int main(){
char listen_addr_str[]= "0.0.0.0" ;
size_t listen_addr=inet_addr(listen_addr_str);
int port=8080;
int server_socket,client_socket;
struct sockaddr_inserver_addr,client_addr;
socklen_taddr_size;
char buffer[BUFFER_SIZE]; //緩衝區大小
size_t client_arr[100]; //儲存客戶端陣列
int client_length=0; //記錄客戶端數量
int str_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,( struct sockaddr*)&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,( struct sockaddr*)&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);
char msg[]= "恭喜你連線成功" ;
write(client_socket,msg, sizeof (msg));
}
for ( int i=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
int main(){
char listen_addr_str[]= "0.0.0.0" ;
size_t listen_addr=inet_addr(listen_addr_str);
int port=8080;
int server_socket,client_socket;
struct sockaddr_inserver_addr,client_addr;
socklen_taddr_size;
char buffer[BUFFER_SIZE]; //緩衝區大小
int str_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,( struct sockaddr*)&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;
int fd_max,fd_num;
struct timevaltimeout;
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,©_reads,0,0,&timeout))==-1){
perror ( "selecterror" );
break ;
}
if (fd_num==0){ //沒有變動的socket
continue ;
}
for ( int i=0;i<fd_max+1;i++){
if (FD_ISSET(i,©_reads)){
if (i==server_socket){ //server_socket變動,代表有新客戶端連線
addr_size= sizeof (client_addr);
client_socket=accept(server_socket,( struct sockaddr*)&client_addr,&addr_size);
printf ( "%d連線成功\n" ,client_socket);
char msg[]= "恭喜你連線成功" ;
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); //將資料傳送回客戶端
}
}
}
}
}
return 0;
}
|
上面就是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
int main(){
char listen_addr_str[]= "0.0.0.0" ;
size_t listen_addr=inet_addr(listen_addr_str);
int port=8080;
int server_socket,client_socket;
struct sockaddr_inserver_addr,client_addr;
socklen_taddr_size;
char buffer[BUFFER_SIZE]; //緩衝區大小
int str_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,( struct sockaddr*)&server_addr, sizeof (server_addr))==-1){
printf ( "繫結失敗\n" );
exit (1);
}
if (listen(server_socket,5)==-1){
printf ( "監聽失敗\n" );
exit (1);
}
printf ( "建立tcp伺服器成功\n" );
struct epoll_eventevent; //監聽事件
struct epoll_eventwait_event_list[CLIENT_MAX_SIZE]; //監聽結果
int fd[CLIENT_MAX_SIZE];
int j=0;
int epoll_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
int result=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,( struct sockaddr*)&client_addr,&addr_size);
printf ( "%d連線成功\n" ,client_socket);
char msg[]= "恭喜你連線成功" ;
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
int set_non_block( int socket){
int flags=fcntl(socket,F_GETFL,0);
flags|=O_NONBLOCK;
return fcntl(socket,F_SETFL,flags);
}
int main(){
char listen_addr_str[]= "0.0.0.0" ;
size_t listen_addr=inet_addr(listen_addr_str);
int port=8080;
int server_socket,client_socket;
struct sockaddr_inserver_addr,client_addr;
socklen_taddr_size;
char buffer[BUFFER_SIZE]; //緩衝區大小
int str_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,( struct sockaddr*)&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); //設定非阻塞
struct epoll_eventevent; //監聽事件
struct epoll_eventwait_event_list[CLIENT_MAX_SIZE]; //監聽結果
int fd[CLIENT_MAX_SIZE];
int j=0;
int epoll_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
int result=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,( struct sockaddr*)&client_addr,&addr_size);
if (client_socket==-1){ //沒有資料可讀
break ;
}
printf ( "%d連線成功\n" ,client_socket);
char msg[]= "恭喜你連線成功" ;
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都得考慮必須處理掉所有事件
本文為仙士可原創文章,轉載無需和我聯絡,但請註明來自仙士可部落格www.php20.cn