温州网站开发培训北京网站制作外包
news/
2025/10/2 19:27:54/
文章来源:
温州网站开发培训,北京网站制作外包,预约网站怎么做,ppt怎么做流程图http://blog.csdn.net/dandelion_gong/article/details/51673085 Unix下可用的I/O模型一共有五种#xff1a;阻塞I/O 、非阻塞I/O 、I/O复用 、信号驱动I/O 、异步I/O。此处我们主要介绍第三种I/O符复用。 I/O复用的功能#xff1a;如果一个或多个I/O条件满足#xff08;输…http://blog.csdn.net/dandelion_gong/article/details/51673085 Unix下可用的I/O模型一共有五种阻塞I/O 、非阻塞I/O 、I/O复用 、信号驱动I/O 、异步I/O。此处我们主要介绍第三种I/O符复用。 I/O复用的功能如果一个或多个I/O条件满足输入已准备好读或者描述字可以承接更多输出时我们就被通知到。这就是有select、poll、epoll实现。 I/O复用应用场合 1、当客户处理多个描述字时一般是交互式输入和网络套接口必须使用I/O复用。在这前一段中已做描述。 2、一个客户同时处理多个套接口是可能的但很少出现。 3、如果一个TCP服务器机要处理监听套接口有要处理已连接套接口一般也要用到I/O复用。 4、如果一个服务器机要处理TCP有要处理UDP一般也要使用I/O复用。 5、如果一个服务器要处理多个服务或者多个协议一般要使用I/O复用。 I/O复用原理图 select: 使用Select就可以完成非阻塞所谓非阻塞方式non-block就是进程或线程执行此函数时不必非要等待事件的发生一旦执行肯定返回以返回值的不同来反映函数的执行情况如果事件发生则与阻塞方式相 同若事件没有发生则返回一个代码来告知事件未发生而进程或线程继续执行所以效率较高方式工作的程序它能够监视我们需要监视的文件描述符的变化情 况——读写或是异常。 所要用到的结构体 struct timeval{ long tv_sec; //等待的秒数 long tv_usec; //等待的微秒数 } select()函数 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 作用用来检测描述符中是否有准备好读、写、或异常的描述符 参数1(nfds)被测试的描述字个数它的值为要被测试的最大描述符个 数1而描述字012……..nfds-1 参数2—4 (readfds)、(writefds)、(exceptfds)这三个参数指定我们要让内核测试读、写、异常条件所需的描述字。当我们在调用该函数指定好我们所要检测的描述字集后如果检测三种情况下任何一中情况准备好则将相应的状态变为可用状态。如果到达函数返回时没有可读可写则返回失败。如果我们不关心其中哪个状态可将其设为NULL。 参数5(timeout)指定等待时间有三种情况 (1)、永远等待下去(参数timeout设置为空指针)仅在有一个描述字准备好I/O时才返回。 (2)、等待固定时间(指定timeval中的秒数和微秒数)在不超过timeval结构体中所指定的秒数和微秒数内检测到有一个描述字准备好I/O时返回 (3)、根本不等待(timeval中秒数和微秒数均设置为0)检查描述字后立即返回。 select工作原理 select就是巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠有资源可读/写时唤醒。下面我们看看select睡眠的详细过程。 select会循环遍历它所监测的fd_set(一组文件描述符(fd)的集合内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列)然后返回一个bitmask告诉select当前资源哪些可用。当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后如果没有一个资源可用(即没有一个文件可供操作)则select让该进程睡眠一直等到有资源可用为止进程被唤醒(或者timeout)继续往下执行。 select调用过程 头文件下面poll、epoll的头文件与该文件相同
#includeunistd.h
#includestdio.h
#includestring.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.h
#includesys/select.h
#includepoll.h
#includesys/epoll.h
#includesys/types.h#define IPADDR 192.168.3.169
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5//select
#define SIZE 10
//poll
#define OPEN_SIZE 10
//epoll
#define FDSIZE 10012345678910111213141516171819202122 服务器端
#include../unp.h
#includemalloc.htypedef struct server_context_st //服务器描述表
{int cli_cnt; //客户端连接个数int clifds[SIZE]; //描述字集合fd_set allfds; //设置所有的描述字int maxfd; //最大描述字个数
}server_context_st;static server_context_st *s_srv_ctx NULL; int server_init() //服务器初始化函数
{s_srv_ctx (server_context_st*)malloc(sizeof(server_context_st)); //申请一个服务器描述表if(s_srv_ctx NULL)return -1;memset(s_srv_ctx, 0, sizeof(server_context_st)); //将该描述表清0for(int i0; iSIZE; i) //将该表中的每一位设为-1{s_srv_ctx-clifds[i] -1;}return 0;
}
void server_uninit() //服务器去初始化函数
{if(s_srv_ctx) //如果服务器描述表不为0即该表申请成功存在{free(s_srv_ctx); //释放该表的内存s_srv_ctx NULL; //将指针值为NULL。}
}int create_server_proc(const char *ip, short port) //创建服务器进程
{int fd; fd socket(AF_INET, SOCK_STREAM, 0); //建立一个套接字记录返回的描述字if(fd -1) //检测是否创建成功{perror(socket);return -1;}//初始化服务器信息结构体struct sockaddr_in addrSer;addrSer.sin_family AF_INET; //指定用到的协议族addrSer.sin_port htons(port); //指定服务器端口号addrSer.sin_addr.s_addr inet_addr(ip); //指定服务器ip地址socklen_t addrlen sizeof(struct sockaddr);int res bind(fd, (struct sockaddr*)addrSer, addrlen); //将创建的描述字与刚才所设置的服务器信息绑定if(res -1) //检测是否绑定成功嗯{perror(bind);return -1;}listen(fd, LISTENQ); //监听是否有客户端请求连接如果有则将该套接字设为可用return fd;
}int accept_client_proc(int srvfd) //结束客户端连接请求
{struct sockaddr_in addrCli;socklen_t addrlen sizeof(struct sockaddr);int clifd;
ACCEPT:clifd accept(srvfd, (struct sockaddr*)addrCli, addrlen); //结束客户端的连接请求if(clifd -1) //判断是否连接成功{goto ACCEPT; //如果没有连接成功则跳转至ACCEPT处继续连接}printf(accept a new client: %s:%d\n,inet_ntoa(addrCli.sin_addr),addrCli.sin_port);int i;for(i0; iSIZE; i) //循环遍历描述字{if(s_srv_ctx-clifds[i] -1) //如果描述字为-1表明只连接了i个客户端0 —— i-1{s_srv_ctx-clifds[i] clifd; //则将连接描述字赋给服务器描述表中第i个描述字s_srv_ctx-cli_cnt; //已连接的客户端数量加一break;}}if(i SIZE) //如果i等于SIZE说明描述字集合已满{printf(Server Over Load.\n); return -1;}
}void handle_client_msg(int fd, char *buf) //处理接收到的客户端信息函数
{printf(recv buf is: %s\n,buf); //服务器将接收到的来自客户端的信息打印出来send(fd, buf, strlen(buf)1, 0); //向客户端发送信息
}void recv_client_msg(fd_set *readfds) //接收客户端信息
{int clifd; char buffer[256];int n;for(int i0; is_srv_ctx-cli_cnt; i) //轮寻查找从描述字集合的第一个描述字开始到最后一个已连接的客户端{clifd s_srv_ctx-clifds[i]; if(clifd 0) //如果套接字小于0则失败continue;if(FD_ISSET(clifd, readfds)) //将该描述字设置为可读{n recv(clifd, buffer, 256, 0); //接收来自客户端的信息if(n 0) //如果返回址小于等于0则接收失败说明客户端已断开连接{FD_CLR(clifd, s_srv_ctx-allfds); //将描述字清0close(clifd); //关闭这个描述字s_srv_ctx-clifds[i] -1; //将第i个描述字设为-1i为已连接的客户端个数-1s_srv_ctx-cli_cnt--; //将以连接的客户端个数减一continue;}handle_client_msg(clifd, buffer); //调用处理客户端信息函数来处理即接收到的信息}}
}int handle_client_proc(int srvfd) //客户端处理程序
{int clifd -1; //将客户端描述字设置为-1int retval 0;fd_set *readfds s_srv_ctx-allfds; //让可读指针指向描述字struct timeval tv; while(1){FD_ZERO(readfds); //清除可读描述字指针FD_SET(srvfd, readfds); //用srvfd设置readfdss_srv_ctx-maxfd srvfd; //设置最大描述符个数tv.tv_sec 30; //设置时间结构体中的秒与微秒tv.tv_usec 0;int i;for(i0; is_srv_ctx-cli_cnt; i) //遍历已经连接的客户端{clifd s_srv_ctx-clifds[i]; FD_SET(clifd, readfds); //将他们的描述字设置为可读s_srv_ctx-maxfd (clifd s_srv_ctx-maxfd ? clifd : s_srv_ctx-maxfd); //选取两个中较大的一个作为最大描述字个数}retval select(s_srv_ctx-maxfd1, readfds, NULL, NULL, tv); //检测是否有准备好的I/O接口if(retval -1) //如果没有则检测失败{perror(select);return -1;}if(retval 0) //如果返回址为0则检测超时{printf(server time out.\n);continue;}//acceptif(FD_ISSET(srvfd, readfds)) //如果该位是作为描述字的{accept_client_proc(srvfd); //接收客户端的连接}else //如果是作为可读指针的{recv_client_msg(readfds); //则接收客户端信息}}
}int main(int argc, char *argv[])
{int sockSer;if(server_init() 0) //检测服务器描述表是否初始化失败perror(server_init);sockSer create_server_proc(IPADDR, PORT); //创建一个服务器进程if(sockSer 0) //检测服务器服务是否创建失败{perror(create_server_porc);goto err;}handle_client_proc(sockSer); //处理客户端return 0;
err:server_uninit(); //去初始化服务器描述表return -1;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 客户端
#include../unp.hvoid handle_connection(int sockfd) //连接处理函数
{fd_set readfds; int maxfd sockfd; //描述字最大个数struct timeval tv;while(1){FD_ZERO(readfds); //将可读描述字空间清0FD_SET(sockfd, readfds); //用套接字设置可读描述字空间maxfd sockfd; //设置最大描述字个数tv.tv_sec 5; //设置时间结构体中的秒数tv.tv_usec 0; //设置时间结构体中的微秒数int res select(maxfd1, readfds, NULL, NULL, tv); //等待可读的描述字if(res -1) //如果函数返回时没有可读I/O准备好{perror(select); //则检测失败return;}if(res 0) //如果返回值为0{printf(Client time out.\n); //则表明检测超时continue;}int n;char recvbuf[256];if(FD_ISSET(sockfd, readfds)) {n recv(sockfd,recvbuf, 256, 0); //接收来自服务器的信息if(n 0) //如果返回的读取长度小于0则表明服务器已经关闭{printf(Server is closed.\n); close(sockfd); //关闭套接字描述符FD_CLR(sockfd, readfds); //清除描述字可读标识为return;}printf(client recv slef msg: %s\n,recvbuf); //打印客户端接收到的信息sleep(3);send(sockfd, recvbuf, strlen(recvbuf)1, 0); //发送信息}}
}int main()
{int sockCli;sockCli socket(AF_INET, SOCK_STREAM, 0); //创建一个套接字struct sockaddr_in addrSer; addrSer.sin_family AF_INET; //设置通信所用到的协议族addrSer.sin_port htons(PORT); //指定通信端口号addrSer.sin_addr.s_addr inet_addr(IPADDR); //指定ip地址socklen_t addrlen sizeof(struct sockaddr);int res connect(sockCli, (struct sockaddr*)addrSer, addrlen); //连接服务器已客户端if(res 0) //连接失败perror(connect);printf(Client connect Server Ok.\n);send(sockCli,hello Server.,strlen(hello Server.)1, 0); //给服务器发送数据此处为了方便用固定值handle_connection(sockCli); //处理连接return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263 运行结果 select几点不足 1每次调用select都需要把fd集合从用户态拷贝到内核态这个开销在fd很多时会很大 2同时每次调用select都需要在内核遍历传递进来的所有fd这个开销在fd很多时也很大 3select支持的文件描述符数量太小了默认是1024 poll: poll的机制与select类似与select在本质上没有多大差别管理多个描述符也是进行轮询根据描述符的状态进行处理但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间而不论这些文件描述符是否就绪它的开销随着文件描述符数量的增加而线性增大。 使用poll()和select()不一样你不需要显式地请求异常情况报告。 POLLIN | POLLPRI等价于select()的读事件POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND而POLLOUT则等价于POLLWRNORM。例如要同时监视一个文件描述符是否可读和可写我们可以设置 events为POLLIN |POLLOUT。在poll返回时我们可以检查revents中的标志对应于文件描述符请求的events结构体。如果POLLIN事件被设置则文件描述符可以被读取而不阻塞。如果POLLOUT被设置则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的它们可能被同时设置表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。 timeout参数指定等待的毫秒数无论I/O是否准备好poll都会返回。timeout指定为负数值表示无限超时使poll()一直挂起直到一个指定事件发生timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符但并不等待其它的事件。这种情况下poll()就像它的名字那样一旦选举出来立即返回。 pollfd结构体 struct pollfd{ int fd; //文件描述符 short events; //等待的事件 short revents; //实际发生了的事件 } poll函数 int poll(struct pollfd *fds, nfds_t nfds, int timeout); 作用返回准备好的描述字的个数。 参数1struct pollfd结构体用来指定一个被监听的文件描述符 参数2nfds_t类型的参数用于标记数组fds中的结构体元素的总数量 参数3是poll函数调用阻塞的时间单位毫秒 用poll实现客户端与服务器之间的通信头文件与select相同 客户端程序
#include ../unp.hvoid handle_connection(int sockfd) //连接处理函数
{pollfd fds[2];fds[0].fd sockfd; //让第一个描述字设为创建套接字的描述符fds[0].events POLLIN; //把第一个描述字设置为普通或优先级带数据可读fds[1].fd STDIN_FILENO; //把第二个检验描述字设置为标准文件输入fds[1].events POLLIN; //把第二个描述字事件标志设置为普通或优先级带数据可读int n;char buf[256];for(; ;){ poll(fds, 2, -1); //无限等待因为POSIX标准里并没有INFTIM所以用-1 if(fds[0].revents POLLIN){ //如果fd[0]中事件已经发生并且为普通或优先级带数据可读表明客户端可读n recv(sockfd, buf, 256, 0); //则接收来自服务器哦的数据if(n 0){ //如果接收到的数据长度小于0则表明服务器已经关闭printf(Server is Closed.\n);close(sockfd);}write(STDOUT_FILENO, buf, n); //标准输出打印出接收大的来自服务器的信息}if(fds[1].revents POLLIN){ //如果fd[0]中事件已经发生并且为标准输入表明客户端可写n read(STDIN_FILENO, buf, 256); //读取键盘输入的内容if(n 0){ //如果没有地到输入的内容则继续continue;}write(sockfd, buf, n); //将从键盘获得的数据写入套接字描述符中}}
}int main()
{int sockCli;sockCli socket(AF_INET, SOCK_STREAM, 0); //创建一个套接字struct sockaddr_in addrSer;addrSer.sin_family AF_INET; //规定通信协议族addrSer.sin_port htons(PORT); //设置所用端口号addrSer.sin_addr.s_addr inet_addr(IPADDR); //设置服务器ipconnect(sockCli, (struct sockaddr*)addrSer, sizeof(struct sockaddr)); //创建连接handle_connection(sockCli); //调用处理客户端连接函数return 0;
}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 服务器端程序
#include ../unp.h
#include stdlib.hint sock_bind(const char *ip, short port) //绑定处理函数
{int fd;fd socket(AF_INET, SOCK_STREAM, 0); //创建一个套接字struct sockaddr_in addrSer;addrSer.sin_family AF_INET; //设置所用到的协议族addrSer.sin_port htons(port); //规定所用到的端口号addrSer.sin_addr.s_addr inet_addr(ip); //设置ip号socklen_t addrlen sizeof(struct sockaddr); bind(fd, (struct sockaddr*)addrSer, addrlen); //绑定套接字描述符和地址结构体信息return fd;
}void handle_connection(struct pollfd *connfds, int num) //连接处理函数
{int n;char buf[256];for(int i 1; i num; i){ //轮寻if(connfds[i].fd -1){ //如果fd额日-1则继续执行continue;}if(connfds[i].revents POLLIN){ //如果该描述字有事件发生并且时普通或优先级数据可读n recv(connfds[i].fd, buf, 256, 0); //则接收来自客户端的数据if(n 0){ //如果接收到的数据常速小于0说明客户端退出close(connfds[i].fd); //关闭连接描述符connfds[i].fd -1; continue;}printf(recv msg:%s\n, buf); //打印接收到的来自客户端的信息send(connfds[i].fd, buf, n, 0); //发送服务器的信息}}
}void do_poll(int sockSer)
{pollfd clientfds[OPEN_SIZE]; //定义一个存放描述字的数组clientfds[0].fd sockSer; //让fd[0]的描述字设置为创建的套接字描述符clientfds[0].events POLLIN; //将其事件设置为普通或优先级带数据for(int i 1; i OPEN_SIZE; i){ //将数组内所有的描述字设置为-1clientfds[i].fd -1;}int maxi 0;int nready;struct sockaddr_in addrCli; socklen_t addrlen sizeof(struct sockaddr);int i; for(; ;){nready poll(clientfds, maxi1, -1); //等待准备好的描述字if(nready -1){ //如果返回值为-1则表明查找失败perror(poll);exit(1);}if(clientfds[0].revents POLLIN){ //如果clientfd[0]有事件发生并且为POLLIN则接收连接int sockConn accept(sockSer, (struct sockaddr*)addrCli, addrlen); //接收客户端连接请求if(sockConn -1){ //检测返回值判断是否连接成功perror(accept);continue;}//打印连接信息printf(accept a new client:%s:%d\n, inet_ntoa(addrCli.sin_addr), addrCli.sin_port);for(i 1; i OPEN_SIZE; i){ //找到还没有标志连接的描述字将连接返回的描述符给他if(clientfds[i].fd 0){clientfds[i].fd sockConn;break;}}if(i OPEN_SIZE){ //如果数组内所有描述字都标志连接则说明连接的客户端数量已够printf(Server Over Load.\n);continue;}clientfds[i].events POLLIN; //将该描述字的事件设为POLLINmaxi (i maxi ? i : maxi); //如果此时连接数已经超过描述字的最大个数则更改最大值否则不变if(--nready 0){continue;}}handle_connection(clientfds, maxi); //调用连接处理函数}
}int main()
{int sockSer;sockSer sock_bind(IPADDR, PORT); //服务器信息的绑定listen(sockSer, LISTENQ); //监听等待队列有没有客户端申请连接do_poll(sockSer); //do_epoll函数,来处理信息传递return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 epoll: epoll对文件描述符的操作有两种模式LTlevel trigger和ETedge trigger。LT模式是默认模式LT模式与ET模式的区别如下 LT模式当epoll_wait检测到描述符事件发生并将此事件通知应用程序应用程序可以不立即处理该事件。下次调用epoll_wait时会再次响应应用程序并通知此事件。 ET模式当epoll_wait检测到描述符事件发生并将此事件通知应用程序应用程序必须立即处理该事件。如果不处理下次调用epoll_wait时不会再次响应应用程序并通知此事件。 首先通过epoll_create(int maxfds)来创建一个epoll的句柄其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄之后的所有操作将通过这个句柄来进行操作。在用完之后记得用close()来关闭这个创建出来的epoll句柄。 然后在你的网络主循环里面每一帧的调用epoll_wait(int epfd, epoll_event* events, int max events, int timeout)来查询所有的网络接口看哪一个可以读哪一个可以写了。基本的语法为nfds epoll_wait(kdpfd, events, maxevents, -1); 其中kdpfd为用epoll_create创建之后的句柄events是一个epoll_event*的指针当epoll_wait这个函数操作成功之后 events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时为0的时候表示马上返回为-1的时候表示一直等下去直到有事件范围为任意正整数的时候表示等这么长的时间如果一直没有事件则返回。一般如果网络主循环是单独的线程的话可以用-1来等这样可以保证一些效率如果是和主逻辑在同一个线程的话则可以用0来保证主循环的效率。 events可以是以下几个宏的集合 EPOLLIN 表示对应的文件描述符可以读包括对端SOCKET正常关闭 EPOLLOUT表示对应的文件描述符可以写 EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外数据到来 EPOLLERR表示对应的文件描述符发生错误 EPOLLHUP表示对应的文件描述符被挂断 EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里。 用epoll代替select/poll实现代码 服务器端
#include ../unp.h
#include utili.h
#include stdlib.hint sock_bind(const char *ip, short port) //绑定函数
{int fd;fd socket(AF_INET, SOCK_STREAM, 0); //创建一个套接字struct sockaddr_in addrSer;addrSer.sin_family AF_INET; //设定所用的协议族addrSer.sin_port htons(port); //设定所用到的端口号addrSer.sin_addr.s_addr inet_addr(ip); //设定服务器ipsocklen_t addrlen sizeof(struct sockaddr);return fd;
}void handle_accept(int epollfd, int listenfd) //结束连接函数
{struct sockaddr_in addrCli;int sockConn;socklen_t addrlen sizeof(struct sockaddr); sockConn accept(listenfd, (struct sockaddr*) addrCli, addrlen); //服务器接受客户端的连接请求if(sockConn -1){ //判断是否接受成功perror(accept);}else{printf(accept a new client:%s:%d\n, inet_ntoa(addrCli.sin_addr), addrCli.sin_port);add_event(epollfd, sockConn, EPOLLIN); //增加事件}
}void do_read(int epollfd, int fd, char *buf) //读数据函数
{int nread read(fd, buf, 256); //读数据if(nread 0){ //如果独到的数据长度小于0则表明服务器关闭printf(Server is Closed.\n);close(fd);delete_event(epollfd, fd, EPOLLIN); //删除刚才添加的事件}printf(recv msg:%s\n, buf); //如果接受成功则打印出所接受到的内容modify_event(epollfd, fd, EPOLLOUT); //设置事件列表为输出
}void do_write(int epollfd, int fd, char *buf) //写数据函数
{int nwrite write(fd, buf, strlen(buf)1); //向缓存区中写入数据if(nwrite 0){ //如果写入数据长度小于0说明客户端关闭printf(client is closed.\n);close(fd);delete_event(epollfd, fd, EPOLLOUT); //删除所添加的事件}else{modify_event(epollfd, fd, EPOLLIN); //如果写入成功则设置事件列表为输入}
}void handle_events(int epollfd, epoll_event *events, int num, int listenfd, char *buf) //事件处理函数
{int fd;for(int i 0; i num; i){ //将所有描述字遍历一遍fd events[i].data.fd; if((fd listenfd) (events[i].events EPOLLIN)){ //判断如果描述字处于监听状态并且有事件准备好并且为EPOLLINhandle_accept(epollfd, listenfd); //则调用接受连接函数}else if(events[i].events EPOLLIN){ //如果不处于监听状态并且有事件准备好且为EPOLLINdo_read(epollfd, fd, buf); //则调用读处理函数}else if(events[i].events EPOLLOUT){ //如果不处于监听状态并且有事件准备好且为EPOLLOUTdo_write(epollfd, fd, buf); //则调用写处理函数}}
}void do_epoll(int listenfd)
{int epollfd;epoll_event events[1024]; //事件列表epollfd epoll_create(FDSIZE); //创建一个epoll句柄add_event(epollfd, listenfd, EPOLLIN); //将EPOLLIN添加到事件列表中int res;char buf[256];for(; ;){res epoll_wait(epollfd, events, 1024, -1); //等待事件列表中的事件准备好if(res -1){ //判断是否有事件准备好perror(epoll_wait);exit(1);}handle_events(epollfd, events, res, listenfd, buf); //调用事件处理函数}close(epollfd); //关闭描述字
}int main()
{int listenfd;listenfd sock_bind(IPADDR, PORT); //调用绑定函数listen(listenfd, LISTENQ); //监听是否有客户端请求连接do_epoll(listenfd); //调用do_epoll函数return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 客户端
#include ../unp.h
#include utili.hvoid do_read(int epollfd, int fd, int sockfd, char *buf) //读处理函数
{int nread;nread read(fd, buf, 256); //读数据if(nread -1){ //如果读到的数据长度为-1则显示读错误信息perror(read);close(fd); //关闭描述字}else if(nread 0){ //如果读数据返回值为0则表明服务器关闭printf(Server is close.\n);close(fd);exit(1);}else{if(fd STDIN_FILENO){ //如果描述字为标准输入add_event(epollfd, sockfd, EPOLLOUT); //则在事件列表中增加EPOLLOUT}else{ delete_event(epollfd, fd, EPOLLIN); //否则删除事件列表中的事件EPOLLIN}}printf(recv msg:%s\n, buf); //打印接收到的信息modify_event(epollfd, fd, EPOLLFD) //设置事件为EPOLLIN
}void do_write(int epollfd, int fd, int sockfd, char *buf){ //写处理函数int nwrite;nwrite write(fd, buf, strlen(buf)1, 0); //向缓存区中写入数据if(nwrite -1){ //判断是否写入失败perror(write);close(fd);}
}void handle_events(int epollfd, epoll_event *events, int num, int sockfd, char *buf) //事件处理函数
{int fd;for(int i 0; i num; i){ //将描述字遍历一遍fd events[i].dsts.fd;if(events[i].events EPOLLIN){ //如果有事件准备好并且为EPOLLINdo_read(epollfd, fd, sockfd, buf); //则调用读操作函数}else if(events[i].events EPOLLOUT){ //额uguo有事件准备好并且为EPOLLOUTdo_write(epollfd, fd, sockfd, buf); //则调用写操作函数}}
}void handle_connection(int sockfd) //连接处理函数
{char buf[256];int epollfd;epoll_event events[1024]; //事件列表epollfd epoll_create(FDSIZE); //创建一个epoll句柄ad_event(epollfd, STDIN_FILENO, EPOLLIN); //增加事件EPOLLINint res;for(; ;){res epoll_wait(epollfd, events, 1024, -1); //等待事件列表中存在的事件准备好handle_events(epollfd, events, res, sockfd, buf); //调用事件处理函数}close(epollfd); //关闭描述字
}int main()
{int sockCli;sockCli socket(AF_INET, SOCK_STREAM, 0); //创建套接字struct sockaddr_in addrSer;addrSer.sin_family AF_INET; //设置协议族addrSer.sin_port htons(PORT); //设置端口号addrSer.sin_addr.s_addr inet_addr(IPADDR); //设置ip号connect(sockCli, (struct sockaddr *)addrSer, sizeof(struct sockaddr)); //连接服务器与客户端handle_connection(sockCli); //调用连接处理函数进行连接后的相关操作return 0;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 总结 1selectpoll实现需要自己不断轮询所有fd集合直到设备就绪期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表期间也可能多次睡眠和唤醒交替但是它是设备就绪时调用回调函数把就绪fd放入就绪链表中并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替但是select和poll在“醒着”的时候要遍历整个fd集合而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了这节省了大量的CPU时间。这就是回调机制带来的性能提升。 2selectpoll每次调用都要把fd集合从用户态往内核态拷贝一次并且要把current往设备等待队列中挂一次而epoll只要一次拷贝而且把current往等待队列上挂也只挂一次在epoll_wait的开始注意这里的等待队列并不是设备等待队列只是一个epoll内部定义的等待队列。这也能节省不少的开销。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/925227.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!