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 #include //inet_addr() sockaddr_in #include //bzero() #include //socket #include #include //exit() #define BUFFER_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_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 #include //inet_addr() sockaddr_in #include //bzero() #include //socket #include #include //exit() #define BUFFER_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_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
# include
//inet_addr() sockaddr_in # include
//bzero() # include
//socket # include
# include
//exit() # include
//waitpid(); #define BUFFER_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_size; char buffer[BUFFER_SIZE]; //缓冲区大小 int str_length; pid_t pid; int status = 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, (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); printf( "%d 连接成功\n" , client_socket); char msg[] = "恭喜你连接成功" ; 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( "signal status = %d\n" , WTERMSIG(status)); //R->T } if (WIFSTOPPED(status)) { printf( "stop sig num = %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 #include //inet_addr() sockaddr_in #include //bzero() #include //socket #include #include //exit() #include //非阻塞 #define BUFFER_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_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 #include //inet_addr() sockaddr_in #include //bzero() #include //socket #include #include //exit() #define BUFFER_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_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_set reads,copy_reads; int fd_max,fd_num; struct timeval timeout; 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 ( "select error" ); break ; } if (fd_num==0){ //没有变动的socket continue ; } for ( int i=0;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 #include //inet_addr() sockaddr_in #include //bzero() #include //socket #include #include //exit() #include //epoll #define BUFFER_SIZE 1024 #define CLIENT_MAX_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_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_event event; //监听事件 struct epoll_event wait_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 } } } } // return 0; } |
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 #include //inet_addr() sockaddr_in #include //bzero() #include //socket #include #include //exit() #include //epoll #define BUFFER_SIZE 1024 #define CLIENT_MAX_SIZE 1024 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_in server_addr, client_addr; socklen_t addr_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_event event; //监听事件 struct epoll_event wait_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 } } } } } // return 0; } |
以上说明,可看出:
1:epoll不需要遍历其他没有事件的socket,避免了select的性能浪费
2:epoll有两种工作模式,用于不同的场景,et和lt模式都可以用非阻塞,但et模式必须非阻塞,et模式编程难度较大,每次epoll_wait都得考虑必须处理掉所有事件
出自尊重,本文声明:本文为仙士可原创文章,转载来自仙士可博客www.php20.cn