epoll边缘触发(epoll et) 源代码例子
2011-03-14 10:12
411 查看
在利用epoll编写网络应用程序,特别是服务器的时候。为了得到最优的效果,一般采用边缘触发(epoll ET)的方式。由于边缘触发,epoll_wait只有在套接字状态发生变化的时候才会返回。所以要对套接字(socket)进行循环accept,read,write;直到套接字的缓冲区空(read,accept)或者填满(write)为止。当read返回的字节数小于要读的字节数,或者返回EAGAIN的时候,认为缓存区为空了。由于网络上已经有很多epollet如何处理epollin事件的例子,所以下面是本人只提供测试如何处理epollout事件的代码。
下面是测试的perl代码(epoll_test.pl)
运行效果
/* ============================================================================ Name : epoll_test.c Author : Version : Copyright : Your copyright notice Description : epoll et example(echo) 此echo服务器对输入的内容复制了REPEAT_NUM(20000次),然后返回给客户端 用于测试epollout事件如何触发。 ============================================================================ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/epoll.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <signal.h> #define EPOLL_SIZE 10 #define EVENT_ARR 20 #define BACK_QUEUE 10 #define PORT 18001 #define BUF_SIZE 16 #define REPEAT_NUM 20000 #define OUT_BUF_SIZE 32*REPEAT_NUM int g_srv_fd; //由于有可能不能一次write所有的内容,所以需要全局变量保存内容的长度,内容输出到那里, //在监听到epollout事件后继续上一次的发送 char g_out_buf[OUT_BUF_SIZE];//保存输出的内容 int g_out_buf_offset; //保存输出到那里 int g_out_buf_len; //保存输出内容的长度 int g_has_write_buf; //保存是否要写输出内容 void setnonblocking(int sockFd) { int opt; opt = fcntl(sockFd, F_GETFL); if (opt < 0) { printf("fcntl(F_GETFL) fail."); exit(-1); } opt |= O_NONBLOCK; if (fcntl(sockFd, F_SETFL, opt) < 0) { printf("fcntl(F_SETFL) fail."); exit(-1); } } void handle_sig(int signum) { close(g_srv_fd); fprintf(stderr, "receiv sig int"); sleep(5); exit(0); } int write_out_buf(int fd, char *out_buf,int buf_len,int offset) { int snd_len = write(fd, out_buf+offset, buf_len-offset); int tmp_len; if (snd_len==(buf_len-offset)){ do{ tmp_len = write(fd, out_buf+offset+snd_len, buf_len-offset-snd_len); if (tmp_len>0 && tmp_len<(buf_len-offset-snd_len)){ snd_len += tmp_len; break; } if(tmp_len == -1){ break; } snd_len += tmp_len; }while(tmp_len>0); } if (((snd_len==-1||tmp_len==-1) && errno ==EAGAIN) || snd_len<buf_len-offset){ fprintf(stderr, "snd receiv eagin or snd_len<out_len/r/n"); } fprintf(stderr, "snd ret:%d/r/n", snd_len); return snd_len; } // void process_write(int fd, char *in_buf,int buf_len) { char *p_out_buf=g_out_buf; int tmp_offset; int i; for(i=0; i<REPEAT_NUM;i++){ tmp_offset+=snprintf(p_out_buf+tmp_offset,OUT_BUF_SIZE-tmp_offset,"%d:%s/r/n", i,in_buf); } g_out_buf_len =tmp_offset; g_out_buf_offset = 0; g_has_write_buf = 0; g_out_buf_offset +=write_out_buf(fd, g_out_buf, g_out_buf_len,g_out_buf_offset); fprintf(stderr, "write_out_buf len:%d wret:%d/r/n", g_out_buf_len, g_out_buf_offset); if (g_out_buf_offset<g_out_buf_len){ g_has_write_buf=1; } } int main() { int serverFd; serverFd = socket(AF_INET, SOCK_STREAM, 0); g_srv_fd = serverFd; setnonblocking(serverFd); signal(SIGPIPE, SIG_IGN); signal(SIGINT, handle_sig); int epFd = epoll_create(EPOLL_SIZE); struct epoll_event ev, evs[EVENT_ARR]; ev.data.fd = serverFd; ev.events = EPOLLIN | EPOLLET; epoll_ctl(epFd, EPOLL_CTL_ADD, serverFd, &ev); struct sockaddr_in serverAddr; socklen_t serverLen = sizeof(struct sockaddr_in); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr.sin_port = htons(PORT); if (bind(serverFd, (struct sockaddr *) &serverAddr, serverLen)) { printf("bind() fail./n"); exit(-1); } if (listen(serverFd, BACK_QUEUE)) { printf("Listen fail./n"); exit(-1); } int clientFd; struct sockaddr_in clientAddr; socklen_t clientLen; char buf[BUF_SIZE]; int i = 0; while (1) { int nfds = epoll_wait(epFd, evs, EVENT_ARR, -1); for (i = 0; i < nfds; i++) { if (evs[i].data.fd == serverFd && (evs[i].events & EPOLLIN)) { //epollet需要循环对监听的套接字accept,直到返回EAGAIN do { if ((clientFd = accept(serverFd, (struct sockaddr *) &clientAddr, &clientLen)) < 0) { printf("accept fail./n"); break; } printf("Connect from %s:%d/n", inet_ntoa(clientAddr.sin_addr), htons(clientAddr.sin_port)); setnonblocking(clientFd); ev.data.fd = clientFd; //注意,为了效率,这里直接对EPOLLIN,EPOLLOUT事件监听 ev.events = EPOLLIN | EPOLLET | EPOLLOUT; //ev.events = EPOLLIN; epoll_ctl(epFd, EPOLL_CTL_ADD, clientFd, &ev); }while(clientFd>0); } else if (evs[i].events & EPOLLIN) { fprintf(stderr, "epollin event fd:%d/n", clientFd); if ((clientFd = evs[i].data.fd) > 0) { //epollet需要对套接字循环的读,直到len < BUF_SIZE,或者len<=0返回 int len = read(clientFd, buf, BUF_SIZE); fprintf(stderr, "read fd:%d len:%d/n", clientFd, len); if (len == BUF_SIZE) { do { /* if (write(clientFd, buf, len) < 0) { fprintf(stderr, "write fail!/n"); //break; } */ process_write(clientFd, buf, len); if (len < BUF_SIZE) { fprintf(stderr, "len <bufsize %d
下面是测试的perl代码(epoll_test.pl)
#!/usr/bin/perl use IO::Socket; my $host = "127.0.0.1"; my $port = 18001; my $socket = IO::Socket::INET->new("$host:$port") or die "create socket error $@"; my $msg_out = "1234567890"; print $socket $msg_out; print "now send over, go to sleep/n"; sleep(10); my $msg_in; while (1){ read $socket, $msg_in, 10240; print "receiv msg $msg_in/n"; } print "5 second gonesend another line/n"; #print $socket $msg_out; while (1) { sleep(1); }
运行效果
epollin event fd:5 read fd:5 len:0 close fd:5 Connect from 127.0.0.1:48552 accept fail. epollin event fd:-1 read fd:5 len:10 snd receiv eagin or snd_len<out_len snd ret:65536 write_out_buf len:408890 wret:65536 receive epoll out fd:5 snd receiv eagin or snd_len<out_len snd ret:131072 write len :408890 writed:196608 receive epoll out fd:5 snd receiv eagin or snd_len<out_len snd ret:65536 write len :408890 writed:262144 receive epoll out fd:5 snd receiv eagin or snd_len<out_len snd ret:49152 write len :408890 writed:311296 receive epoll out fd:5 snd receiv eagin or snd_len<out_len snd ret:49152 write len :408890 writed:360448 receive epoll out fd:5 snd ret:48442 write len :408890 writed:408890 epollin event fd:5 read fd:5 len:0 close fd:5
相关文章推荐
- epoll边缘触发(epoll et) 源代码例子
- epoll边缘触发(epoll et) 源代码例子
- 关于epoll边缘触发模式(ET)下的EPOLLOUT触发
- 【网络编程】epoll边缘触发(ET模式)
- epoll的边缘触发(ET)和水平触发(LT)
- epoll用法说明,ET模式下的边缘触发处理同时多事件
- EPOLL的LT和ET触发方式介绍
- 剖析 epoll ET/LT 触发方式的性能差异误解(定性分析)
- 剖析 epoll ET/LT 触发方式的性能差异误解(定性分析)
- 实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO
- epoll 水平触发和边缘触发的区别
- 剖析 epoll ET/LT 触发方式的性能差异
- 聊聊epoll的水平触发和边缘触发
- 彻底学会使用epoll(二)——ET和LT的触发方式
- 对linux 多路复用Epoll模型的水平出发模式和边缘触发模式的理解
- 彻底学会使用epoll(二)——ET和LT的触发方式
- 条件触发和边缘触发 及 epoll 的长处
- 彻底学会使用epoll(二)——ET和LT的触发方式
- 实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO
- 实例浅析IO阻塞、非阻塞epoll的水平触发和边缘触发