[转] linux epoll 问题小结
1,server端的fd不需要设置et模式
我们在创建socket成功后会有个listenfd,listenfd = socket(AF_INET, SOCK_STREAM, 0)
然后会把这个fd加入epoll wait队列中,网上很多没有经过验证的代码是这样写的:
ev.data.fd = listenfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
这样会导致服务器在并发处理客户端连接时,丢失部分连接,或者说丢失epoll事件
表现为:采用netstat查看网络,会看到Recv-Q大于0,但是程序执行不到accept代码段。
这个也和具体内核有关系,但是最好代码方面不要这么写。
正确的写法为:ev.events=EPOLLIN; 这里不用指定EPOLLET
2,如果采用et模式,读的时候要读完数据
3,要处理发送,接收函数的返回值和错误码。如:read,recv,send,write,errno
write如果返回-1,可能是写缓冲满,需要等待out事件再写入
下面贴个基本模型,代码不能运行,仅参考
struct _sgc_epoll_socketserver_config_s{
char * local_addr;// 监听的本地地址
int port;// 监听的端口
int epfd;// epoll 文件描述符
size_t buffer_size;// 缓冲区大小,单位byte
int maxclients;// 最多允许连接的客户端数
sgc_epoll_client_new_handler_t handler_new;// 有新连接处理函数
sgc_epoll_client_msg_handler_t hander_read;// 读取数据
sgc_epoll_client_close_hander_t hander_close;// 断开
};
/**
* 启动socket服务
*/
void sgc_serversocket_start(sgc_epoll_socketserver_config_t *config)
{
int epfd, listenfd,connfd,readyfds,i,sockfd, clients, nreadbytes;
int flag=1,len=sizeof(int);
int cfg_sndbuff = 1024 * 1024;// 1M
int cfg_revbuffer = 1024 * 1024; // 1M
struct epoll_event ev, events[config->maxclients];
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
socklen_t clilen = sizeof(struct sockaddr_in);
if (config->handler_new == NULL ||
config->hander_read == NULL){
sgc_log_error("config error hand is NULL\n");
exit(1);
}
char * buffer = (char *)malloc(config->buffer_size);
memset(buffer, 0, config->buffer_size);
epfd = epoll_create(config->maxclients);
config->epfd = epfd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, len))
sgc_log_error("set sockopt SO_REUSEADDR error\n");
if (setsockopt(listenfd, SOL_SOCKET, SO_SNDBUF, &cfg_sndbuff, len))
sgc_log_error("set sockopt SO_SNDBUF error\n");
if (setsockopt(listenfd, SOL_SOCKET, SO_SNDBUF, &cfg_revbuffer, len))
sgc_log_error("set sockopt SO_SNDBUF error\n");
setnonblocking(listenfd);
ev.data.fd = listenfd;
ev.events=EPOLLIN;
// 监听端口
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr= config->local_addr;
inet_aton(local_addr,&(serveraddr.sin_addr));
serveraddr.sin_port=htons(config->port);
if (bind(listenfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr)))
{
sgc_log_error("bind fail %s:%d\n", config->local_addr, config->port);
exit(1);
}
sgc_log_debug("start listen %s:%d backlog:%d\n", local_addr, config->port, LISTEN_BACKLOG);
if (listen(listenfd, LISTEN_BACKLOG))
{
sgc_log_error("listen %s:%d max:%d error\n", config->local_addr, config->port, config->maxclients);
exit(1);
}
clients = 0;
for (;;) {
readyfds=epoll_wait(epfd,events,config->maxclients,EPOLL_WAIT_TIMEOUT);
if (readyfds < 0){
sgc_log_error("epoll_weit errno:%d %s\n", errno, strerror(errno));
}else if (readyfds > 0)
sgc_log_debug("readyfds:%d errno:%d err:%s\n", readyfds, errno, strerror(errno));
for(i=0;i<readyfds;++i)
{
// 新用户连接
if(events[i].data.fd==listenfd)
{
if (clients >= config->maxclients){
sgc_log_debug("clients full,close:%s socket fd:%d clients:%d\n", inet_ntoa(clientaddr.sin_addr), listenfd, clients);
continue;
}else{
clients++;
connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
if(connfd<0){
sgc_log_error("accept error connfd<0 fd=%d errno:%d %s\n", connfd, errno, strerror(errno));
exit(1);
}
setnonblocking(connfd);
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
sgc_log_debug("new clients:%d, num:%d %s\n", connfd, clients, inet_ntoa(clientaddr.sin_addr));
config->handler_new(connfd, &clientaddr, config);
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
}else if(events[i].events & EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
{
if ((sockfd = events[i].data.fd) < 0){
sgc_log_error("EPOLLIN fd < 0\n");
continue;
}
// 一次读完缓冲区
memset(buffer, 0, config->buffer_size);
while((nreadbytes = recv(sockfd, buffer, config->buffer_size, 0)) > 0)
{
sgc_log_debug("read fd:%d len:%d\n", sockfd, nreadbytes);
config->hander_read(sockfd, buffer, nreadbytes);
memset(buffer, 0, config->buffer_size);
}
sgc_log_debug("read finish nreadbytes:%d fd:%d errno:%d, err:%s\n", nreadbytes, sockfd, errno, strerror(errno));
if (nreadbytes < 0){
if (errno == ECONNRESET){
EPOLL_CLOSE_CODE("connrset")
}
}else if (nreadbytes == 0){
EPOLL_CLOSE_CODE("normal")
}
}else{// 其它异常情况,关闭
sockfd = events[i].data.fd;
EPOLL_CLOSE_CODE("exception")
}
}
}
}