关于epoll的et模式一次没读完,下次有新数据来了,还会触发么?
我是看了群主的公众号以后又疑问,刚好上次头条面试面试官也问我了,然后问大家也是模棱两可,所以我把群主的代码稍微改了下。
公众号名称:高性能服务器开发
问题是:当epoll触发可读时,万一数据没有从缓冲区读完,下次有新的数据过来,还会触发么?
答案是: 会触发
et模式触发是这样说的:由不可读变为可读时触发读事件,由不可写变为可写触发写事件。 有点宏观
然后我们具体看代码:
#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>
#include<iostream>
using namespace std;
int setSocketNonblocking(int fd)
{
//将监听socker设置为非阻塞的
int oldSocketFlag = fcntl(fd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if(fcntl(fd, F_SETFL, newSocketFlag)==-1)
{
close(fd);
cout << "set listenfd to nonblock error" << endl;
return -1;
}
return oldSocketFlag;
}
int main()
{
//创建一个监听socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
{
cout << "create listen socket error" << endl;
return -1;
}
setSocketNonblocking(listenfd);
//初始化服务器地址
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)
{
cout << "bind listen socker error." << endl;
close(listenfd);
return -1;
}
//启动监听
if(listen(listenfd, SOMAXCONN)==-1)
{
cout << "listen error." << endl;
close(listenfd);
return -1;
}
//复用地址和端口号
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
//创建epollfd
int epollfd = epoll_create(1);
if(epollfd == -1)
{
cout << "create epollfd error." << endl;
close(listenfd);
return -1;
}
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
listen_fd_event.events |= EPOLLET;
//将监听sokcet绑定到epollfd上去
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd,&listen_fd_event)==-1)
{
cout << "epoll_ctl error" << 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_SETFD, newSocketFlag)==-1)
{
close(clientfd);
cout << "set clientfd to nonblocking error." << endl;
}
else
{
epoll_event client_fd_event;
client_fd_event.data.fd = clientfd;
client_fd_event.events = EPOLLIN;
client_fd_event.events |= EPOLLET; //设置为边缘出发
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event)!=-1)
{
cout << "new client accepted,clientfd: " << clientfd << endl;
}
else
{
cout << "add client fd to epollfd error" << endl;
close(clientfd);
}
}
}
}
else
{
//普通clientfd
char ch;
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)
{
cout << "client disconnected,clientfd:" <<epoll_events[i].data.fd << 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)
{
cout << "client disconnected,clientfd:" <<epoll_events[i].data.fd << endl;
}
close(epoll_events[i].data.fd);
}
break;
}
else
{
//正常收到数据
cout << "recv from client:" << epoll_events[i].data.fd << " " << ch << endl;
}
}
}
else if(epoll_events[i].events & POLLERR)
{
// TODO 暂不处理
}
}
}
close(listenfd);
return 0;
}
然后我们去测试: 注意代码里每次只读取一个字节。
char ch;
int m = recv(epoll_events[i].data.fd,&ch,1,0);
看结果
最后看到是会继续触发的。
当然我们一般是使用while循环一直读写的,直到返回小于0的数才退出。