在利用epoll编写网络应用程序,特别是服务器的时候。为了得到最优的效果,一般采用边缘触发(epoll ET)的方式。由于边缘触发,epoll_wait只有在套接字状态发生变化的时候才会返回。所以要对套接字(socket)进行循环accept,read,write;直到套接字的缓冲区空(read,accept)或者填满(write)为止。当read返回的字节数小于要读的字节数,或者返回EAGAIN的时候,认为缓存区为空了。由于网络上已经有很多epollet如何处理epollin事件的例子,所以下面是本人只提供测试如何处理epollout事件的代码。
/*
============================================================================
Name : epoll_test.c
Author :
Version :
Copyright : Your copyright notice
Description : epoll et example(echo) 此echo服务器对输入的内容复制了REPEAT_NUM(20000次),然后返回给客户端
用于测试epollout事件如何触发。
============================================================================
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 fprintf(stderr, "snd receiv eagin or snd_len }
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 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_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 BUF_SIZE);
break;
}
len = read(clientFd, buf, BUF_SIZE);
fprintf(stderr, "read2 fd:%d len:%d\n", clientFd, len);
} while (len > 0);
if (len == 0) {
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
fprintf(stderr, "close fd:%d\n", clientFd);
} else if (len == -1 && errno != EAGAIN) {
fprintf(stderr, " fd:%d\n", clientFd);
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
}
} else if (len > 0 && len < BUF_SIZE) {
process_write(clientFd, buf, len);
} else if (len == 0 || (len == -1 && errno != EAGAIN)) {
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
fprintf(stderr, "close fd:%d\n", clientFd);
}
}
} else if(evs[i].events & EPOLLOUT){
//监听到epollout时间,说明发送缓冲去可以写,那继续上一次的写操作
clientFd = evs[i].data.fd;
fprintf(stderr, "receive epoll out fd:%d\n", clientFd);
if(g_has_write_buf){
g_out_buf_offset +=write_out_buf(clientFd, g_out_buf, g_out_buf_len,g_out_buf_offset);
fprintf(stderr, "write len :%d writed:%d\n",g_out_buf_len,g_out_buf_offset);
if(g_out_buf_offset==g_out_buf_len){
g_has_write_buf = 0;
}
}
}else {
printf("other event.\n");
}
}
}
return 0;
}
============================================================================
Name : epoll_test.c
Author :
Version :
Copyright : Your copyright notice
Description : epoll et example(echo) 此echo服务器对输入的内容复制了REPEAT_NUM(20000次),然后返回给客户端
用于测试epollout事件如何触发。
============================================================================
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
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
}
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
}
}
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
break;
}
len = read(clientFd, buf, BUF_SIZE);
fprintf(stderr, "read2 fd:%d len:%d\n", clientFd, len);
} while (len > 0);
if (len == 0) {
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
fprintf(stderr, "close fd:%d\n", clientFd);
} else if (len == -1 && errno != EAGAIN) {
fprintf(stderr, " fd:%d\n", clientFd);
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
}
} else if (len > 0 && len < BUF_SIZE) {
process_write(clientFd, buf, len);
} else if (len == 0 || (len == -1 && errno != EAGAIN)) {
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
fprintf(stderr, "close fd:%d\n", clientFd);
}
}
} else if(evs[i].events & EPOLLOUT){
//监听到epollout时间,说明发送缓冲去可以写,那继续上一次的写操作
clientFd = evs[i].data.fd;
fprintf(stderr, "receive epoll out fd:%d\n", clientFd);
if(g_has_write_buf){
g_out_buf_offset +=write_out_buf(clientFd, g_out_buf, g_out_buf_len,g_out_buf_offset);
fprintf(stderr, "write len :%d writed:%d\n",g_out_buf_len,g_out_buf_offset);
if(g_out_buf_offset==g_out_buf_len){
g_has_write_buf = 0;
}
}
}else {
printf("other event.\n");
}
}
}
return 0;
}
下面是测试的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);
}
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_lensnd ret:65536
write_out_buf len:408890 wret:65536
receive epoll out fd:5
snd receiv eagin or snd_lensnd ret:131072
write len :408890 writed:196608
receive epoll out fd:5
snd receiv eagin or snd_lensnd ret:65536
write len :408890 writed:262144
receive epoll out fd:5
snd receiv eagin or snd_lensnd ret:49152
write len :408890 writed:311296
receive epoll out fd:5
snd receiv eagin or snd_lensnd 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
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
write_out_buf len:408890 wret:65536
receive epoll out fd:5
snd receiv eagin or snd_len
write len :408890 writed:196608
receive epoll out fd:5
snd receiv eagin or snd_len
write len :408890 writed:262144
receive epoll out fd:5
snd receiv eagin or snd_len
write len :408890 writed:311296
receive epoll out fd:5
snd receiv eagin or snd_len
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