EPOLLONESHOT选项

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上的事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值