http://blog.csdn.net/shanshanpt/article/details/7383400
在前面已经经过了PPC、TPC、select之类( TPC就是使用进程处理data,TPC就是使用线程处理 ),前面两个的缺点大家应该都是知道的是吧,对于select( 其实poll和他差不多 ),缺点是能同时连接的fd是在是不多,在linux中一般是1024/2048,对于很大的服务器来说是不够的!当然我们可以自己修改其值!但是效率上就会下降!
对于改进poll的epoll来说:支持一个进程打开大数目的socket描述符,也就是说与本机的内存是有关系的!( 一般服务器的都是很大的! )
下面是我的小PC机上的显示:
pt@ubuntu:~$ cat /proc/sys/fs/file-max
391658
达到了391658个,那么对于服务器而言,显然,嘿嘿嘿~~~
epoll的基础知识吧大家在网上到处都能找到,不就是epoll_creat 、epoll_ctl、epoll_wait 3函数!大家自己搜去,我也是在学习。。。
此处主要是贴上自己的测试的一些垃圾代码,与大家共勉!呵呵呵~
哦,忘了要注意一下:
epoll_ctl epoll的事件注册函数,其events参数可以是以下宏的集合:
EPOLLIN: 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT: 表示对应的文件描述符可以写;
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR: 表示对应的文件描述符发生错误;写已关闭socket pipe broken
EPOLLHUP: 表示对应的文件描述符被挂断;譬如收到RST包。在注册事件的时候这个事件是默认添加。
EPOLLRDHUP: 表示对应的文件描述符对端socket关闭事件,主要应用于ET模式下。
在水平触发模式下,如果对端socket关闭,则会一直触发epollin事件,驱动去处理client socket。
在边沿触发模式下,如果client首先发送协议然后shutdown写端。则会触发epollin事件。但是如果处理程序只进行一次recv操作时,根据recv收取到得数据长度来判读后边是
否还有需要处理的协议时,将丢失客户端关闭事件。
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
server端:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> /* socket类定义需要*/ #include <sys/epoll.h> /* epoll头文件 */ #include <fcntl.h> /* nonblocking需要 */ #include <sys/resource.h> /* 设置最大的连接数需要setrlimit */ #define MAXEPOLL 10000 /* 对于服务器来说,这个值可以很大的! */ #define MAXLINE 1024 #define PORT 6000 #define MAXBACK 1000 //!> 设置非阻塞 //!> int setnonblocking( int fd ) { if( fcntl( fd, F_SETFL, fcntl( fd, F_GETFD, 0 )|O_NONBLOCK ) == -1 ) { printf("Set blocking error : %d\n", errno); return -1; } return 0; } int main( int argc, char ** argv ) { int listen_fd; int conn_fd; int epoll_fd; int nread; int cur_fds; //!> 当前已经存在的数量 int wait_fds; //!> epoll_wait 的返回值 int i; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; struct epoll_event ev; struct epoll_event evs[MAXEPOLL]; struct rlimit rlt; //!> 设置连接数所需 char buf[MAXLINE]; socklen_t len = sizeof( struct sockaddr_in ); //!> 设置每个进程允许打开的最大文件数 //!> 每个主机是不一样的哦,一般服务器应该很大吧! //!> rlt.rlim_max = rlt.rlim_cur = MAXEPOLL; if( setrlimit( RLIMIT_NOFILE, &rlt ) == -1 ) { printf("Setrlimit Error : %d\n", errno); exit( EXIT_FAILURE ); } //!> server 套接口 //!> bzero( &servaddr, sizeof( servaddr ) ); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl( INADDR_ANY ); servaddr.sin_port = htons( PORT ); //!> 建立套接字 if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { printf("Socket Error...\n" , errno ); exit( EXIT_FAILURE ); } //!> 设置非阻塞模式 //!> if( setnonblocking( listen_fd ) == -1 ) { printf("Setnonblocking Error : %d\n", errno); exit( EXIT_FAILURE ); } //!> 绑定 //!> if( bind( listen_fd, ( struct sockaddr *)&servaddr, sizeof( struct sockaddr ) ) == -1 ) { printf("Bind Error : %d\n", errno); exit( EXIT_FAILURE ); } //!> 监听 //!> if( listen( listen_fd, MAXBACK ) == -1 ) { printf("Listen Error : %d\n", errno); exit( EXIT_FAILURE ); } //!> 创建epoll //!> epoll_fd = epoll_create( MAXEPOLL ); //!> create ev.events = EPOLLIN | EPOLLET; //!> accept Read! ev.data.fd = listen_fd; //!> 将listen_fd 加入 if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev ) < 0 ) { printf("Epoll Error : %d\n", errno); exit( EXIT_FAILURE ); } cur_fds = 1; while( 1 ) { if( ( wait_fds = epoll_wait( epoll_fd, evs, cur_fds, -1 ) ) == -1 ) { printf( "Epoll Wait Error : %d\n", errno ); exit( EXIT_FAILURE ); } for( i = 0; i < wait_fds; i++ ) { if( evs[i].data.fd == listen_fd && cur_fds < MAXEPOLL ) //!> if是监听端口有事 { if( ( conn_fd = accept( listen_fd, (struct sockaddr *)&cliaddr, &len ) ) == -1 ) { printf("Accept Error : %d\n", errno); exit( EXIT_FAILURE ); } printf( "Server get from client !\n"/*, inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port */); ev.events = EPOLLIN | EPOLLET; //!> accept Read! ev.data.fd = conn_fd; //!> 将conn_fd 加入 if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev ) < 0 ) { printf("Epoll Error : %d\n", errno); exit( EXIT_FAILURE ); } ++cur_fds; continue; } //!> 下面处理数据 //!> nread = read( evs[i].data.fd, buf, sizeof( buf ) ); if( nread <= 0 ) //!> 结束后者出错 { close( evs[i].data.fd ); epoll_ctl( epoll_fd, EPOLL_CTL_DEL, evs[i].data.fd, &ev ); //!> 删除计入的fd --cur_fds; //!> 减少一个呗! continue; } write( evs[i].data.fd, buf, nread ); //!> 回写 } } close( listen_fd ); return 0; }
对于client:
由于本人比较懒,所以就使用上一次的select的client吧,一样的,呵呵:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/select.h> #define MAXLINE 1024 #define SERV_PORT 6000 //!> 注意输入是由stdin,接受是由server发送过来 //!> 所以在client端也是需要select进行处理的 void send_and_recv( int connfd ) { FILE * fp = stdin; int lens; char send[MAXLINE]; char recv[MAXLINE]; fd_set rset; FD_ZERO( &rset ); int maxfd = ( fileno( fp ) > connfd ? fileno( fp ) : connfd + 1 ); //!> 输入和输出的最大值 int n; while( 1 ) { FD_SET( fileno( fp ), &rset ); FD_SET( connfd, &rset ); //!> 注意不要把rset看作是简单的一个变量 //!> 注意它其实是可以包含一组套接字的哦, //!> 相当于是封装的数组!每次都要是新的哦! if( select( maxfd, &rset, NULL, NULL, NULL ) == -1 ) { printf("Client Select Error..\n"); exit(EXIT_FAILURE ); } //!> if 连接口有信息 if( FD_ISSET( connfd, &rset ) ) //!> if 连接端口有信息 { printf( "client get from server ...\n" ); memset( recv, 0, sizeof( recv ) ); n = read( connfd, recv, MAXLINE ); if( n == 0 ) { printf("Recv ok...\n"); break; } else if( n == -1 ) { printf("Recv error...\n"); break; } else { lens = strlen( recv ); recv[lens] = '\0'; //!> 写到stdout write( STDOUT_FILENO, recv, MAXLINE ); printf("\n"); } } //!> if 有stdin输入 if( FD_ISSET( fileno( fp ), &rset ) ) //!> if 有输入 { //!> printf("client stdin ...\n"); memset( send, 0, sizeof( send ) ); if( fgets( send, MAXLINE, fp ) == NULL ) { printf("End...\n"); exit( EXIT_FAILURE ); } else { //!>if( str ) lens = strlen( send ); send[lens-1] = '\0'; //!> 减一的原因是不要回车字符 //!> 经验值:这一步非常重要的哦!!!!!!!! if( strcmp( send, "q" ) == 0 ) { printf( "Bye..\n" ); return; } printf("Client send : %s\n", send); write( connfd, send, strlen( send ) ); } } } } int main( int argc, char ** argv ) { //!> char * SERV_IP = "10.30.97.188"; char buf[MAXLINE]; int connfd; struct sockaddr_in servaddr; if( argc != 2 ) { printf("Input server ip !\n"); exit( EXIT_FAILURE ); } //!> 建立套接字 if( ( connfd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { printf("Socket Error...\n" , errno ); exit( EXIT_FAILURE ); } //!> 套接字信息 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); //!> 链接server if( connect( connfd, ( struct sockaddr * )&servaddr, sizeof( servaddr ) ) < 0 ) { printf("Connect error..\n"); exit(EXIT_FAILURE); } /*else { printf("Connet ok..\n"); }*/ //!> //!> send and recv send_and_recv( connfd ); //!> close( connfd ); printf("Exit\n"); return 0; }
编译运行: