EPOLLONESHOT选项
如果某个socket
注册了该标志,则其注册监听的事件在触发一次后再也不会触发,除非重新注册监听该事件类型。
//epoll_server_with_oneshot.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <poll.h>
#include <iostream>
#include <string.h>
#include <vector>
#include <errno.h>
int main()
{
//创建一个监听socket
int listenfd = socket(AF_INET,SOCK_STREAM, 0);
if(listenfd == -1)
{
std::cout << "create listen socket error" << std::endl;
return -1;
}
//设置重用的ip地址和端口号
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
//将监听socket设置为非阻塞的
int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if(fcntl(listenfd, F_SETFL, newSocketFlag) == -1)
{
close(listenfd);
std::cout<<"set listenfd to nonblock error" << std::endl;
return -1;
}
//初始化服务器的地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if(bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1)
{
std::cout << "bind listen socket error" << std::endl;
close(listenfd);
return -1;
}
//启动监听
if(listen(listenfd, SOMAXCONN) == -1)
{
std::cout << "listen error" << std::endl;
close(listenfd);
return -1;
}
//创建epollfd
int epollfd = epoll_create(1);
if (epollfd == -1)
{
std::cout << "create epollfd error. " << std::endl;
close(listenfd);
return -1;
}
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
//将监听socket绑定到epollfd上
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
int n;
while(true)
{
epoll_event epoll_events[1024];
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
if(n < 0)
{
//被信号中断
if(errno == EINTR)
continue;
//出错退出
break;
}
else if(n == 0)
{
//超时,继续
continue;
}
for(size_t i = 0; i < n; ++i)
{
//事件可读
if(epoll_events[i].events & EPOLLIN)
{
if(epoll_events[i].data.fd == listenfd)
{
//监听socket,接受新连接
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
if(clientfd != -1)
{
int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if(fcntl(clientfd, F_SETFL, newSocketFlag) == -1)
{
close(clientfd);
std::cout << "set clientfd to nonblock error" << std::endl;
}
else
{
epoll_event client_fd_event;
client_fd_event.data.fd = clientfd;
client_fd_event.events = EPOLLIN;
//为clientfd注册EPOLLONESHOT事件
client_fd_event.events |= EPOLLONESHOT;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1)
{
std::cout << "new client accepted,clientfd: " << clientfd << std::endl;
}
else
{
std::cout << "add client fd to epollerror" << std::endl;
close(clientfd);
}
}
}
}
else
{
std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;
//普通clientfd
char ch;
//每次只接受1字节
int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
if(m == 0)
{
//对端关闭了连接,从epollfd上移除clientfd
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout <<"client disconnected, clientfd: " << epoll_events[i].data.fd<<std::endl;
}
close(epoll_events[i].data.fd);
}
else if(m < 0)
{
//出错
if(errno != EWOULDBLOCK && errno != EINTR)
{
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout <<"client disconnected, clientfd: " << epoll_events[i].data.fd<<std::endl;
}
close(epoll_events[i].data.fd);
}
}
else
{
//正常接受数据
std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << ch << std::endl;
}
}
}
else if(epoll_events[i].events & POLLERR)
{}
}
}
close(listenfd);
return 0;
}
以上代码在clientfd
注册了EPOLLONESHOT
事件,由于使用水平模式,所以EPOLLIN
在每次触发后都只读取1个字符。
使用nc
命令模拟客户端执行效果。
kkk@kkk-VirtualBox:~/桌面$ nc -v 127.0.0.1 3000
Connection to 127.0.0.1 3000 port [tcp/*] succeeded!
hello
hello
服务器端效果如下:
kkk@kkk-VirtualBox:~/code$ ./epoll_server_with_oneshot
new client accepted,clientfd: 5
client fd: 5 recv data.
recv from client:5, h
服务端每次读取1个字符,由于注册了EPOLLONESHOT
选项,所以第一次EPOLLIN
事件触发后,即使客户端再发送字符串,服务端也不会触发EPOLLIN
了。
把上面的代码修改一下,把下面这段代码加入到上面输出接受的字符串后面。
//epoll_server_with_oneshot.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <poll.h>
#include <iostream>
#include <string.h>
#include <vector>
#include <errno.h>
int main()
{
//创建一个监听socket
int listenfd = socket(AF_INET,SOCK_STREAM, 0);
if(listenfd == -1)
{
std::cout << "create listen socket error" << std::endl;
return -1;
}
//设置重用的ip地址和端口号
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
//将监听socket设置为非阻塞的
int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if(fcntl(listenfd, F_SETFL, newSocketFlag) == -1)
{
close(listenfd);
std::cout<<"set listenfd to nonblock error" << std::endl;
return -1;
}
//初始化服务器的地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if(bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1)
{
std::cout << "bind listen socket error" << std::endl;
close(listenfd);
return -1;
}
//启动监听
if(listen(listenfd, SOMAXCONN) == -1)
{
std::cout << "listen error" << std::endl;
close(listenfd);
return -1;
}
//创建epollfd
int epollfd = epoll_create(1);
if (epollfd == -1)
{
std::cout << "create epollfd error. " << std::endl;
close(listenfd);
return -1;
}
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
//将监听socket绑定到epollfd上
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
int n;
while(true)
{
epoll_event epoll_events[1024];
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
if(n < 0)
{
//被信号中断
if(errno == EINTR)
continue;
//出错退出
break;
}
else if(n == 0)
{
//超时,继续
continue;
}
for(size_t i = 0; i < n; ++i)
{
//事件可读
if(epoll_events[i].events & EPOLLIN)
{
if(epoll_events[i].data.fd == listenfd)
{
//监听socket,接受新连接
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
if(clientfd != -1)
{
int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if(fcntl(clientfd, F_SETFL, newSocketFlag) == -1)
{
close(clientfd);
std::cout << "set clientfd to nonblock error" << std::endl;
}
else
{
epoll_event client_fd_event;
client_fd_event.data.fd = clientfd;
client_fd_event.events = EPOLLIN;
//为clientfd注册EPOLLONESHOT事件
client_fd_event.events |= EPOLLONESHOT;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1)
{
std::cout << "new client accepted,clientfd: " << clientfd << std::endl;
}
else
{
std::cout << "add client fd to epollerror" << std::endl;
close(clientfd);
}
}
}
}
else
{
std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;
//普通clientfd
char ch;
//每次只接受1字节
int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
if(m == 0)
{
//对端关闭了连接,从epollfd上移除clientfd
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout <<"client disconnected, clientfd: " << epoll_events[i].data.fd<<std::endl;
}
close(epoll_events[i].data.fd);
}
else if(m < 0)
{
//出错
if(errno != EWOULDBLOCK && errno != EINTR)
{
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout <<"client disconnected, clientfd: " << epoll_events[i].data.fd<<std::endl;
}
close(epoll_events[i].data.fd);
}
}
else
{
//正常接受数据
std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << ch << std::endl;
epoll_event client_fd_event;
client_fd_event.data.fd = epoll_events[i].data.fd;
client_fd_event.events = EPOLLIN;
//这里再次为clientfd注册EPOLLIN事件
if(epoll_ctl(epollfd, EPOLL_CTL_MOD, epoll_events[i].data.fd, &client_fd_event) != -1)
{
std::cout << "rearm EPOLLIN event to clientfd: " <<epoll_events[i].data.fd << std::endl;
}
else
{
if(epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout <<"remove clientfd from epoll fd successfully, clientfd:"<< epoll_events[i].data.fd <<std::endl;
}
close(epoll_events[i].data.fd);
}
}
}
}
else if(epoll_events[i].events & POLLERR)
{}
}
}
close(listenfd);
return 0;
}
客户端:
kkk@kkk-VirtualBox:~/桌面$ nc -v 127.0.0.1 3000
Connection to 127.0.0.1 3000 port [tcp/*] succeeded!
hello
hello
服务器
kkk@kkk-VirtualBox:~/code$ ./epoll_server_with_oneshot
new client accepted,clientfd: 5
client fd: 5 recv data.
recv from client:5, h
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, e
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, l
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, l
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, o
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5,
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, h
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, e
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, l
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, l
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5, o
rearm EPOLLIN event to clientfd: 5
client fd: 5 recv data.
recv from client:5,
在一些特殊的应用场景中,如果涉及多个线程同时处理某个socket
上的事件,则为了避免数据乱序,我们不得不使用复杂的多线程同步机制;但是有了EPOLLONESHOT
选项,我们就可以减少线程同步逻辑。以EPOLLIN
为例,多个线程同时从一个socket
上读数据,可以使某个线程先处理,在该线程处理完之后再重新给该socket
添加读事件,这样读事件再次触发时,就可以被其他线程继续处理。这种做法本质上还是保证同一个时刻只有一个线程在处理某个socket
上的事件。