epoll有两种工作模式:
这两中模式用电平来表示,
LT模式:
1. 低电平 => 高电平 2. 处于高电平状态
ET模式:
1. 低电平 => 高电平
socket可读事件LT触发条件:
1. socket上无数据 => socket上有数据
2. socket处于有数据状态
socket可读事件ET触发条件:
1. socket上无数据 => socket上有数据
2. socket又新来一次数据
socket可写事件LT触发条件:
1. socket可写 => socket可写
2. socket不可写 => socket可写
socket可写事件ET触发条件:
1. socket不可写 => socket可写
该链接epoll LT模式和ET模式详解 分别给出了以上四种情况的实例,在这里给出建议代码:
服务器大致代码如下:
// 将监听套接字注册到epoll实例中
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
//将监听sokcet绑定到epollfd上去
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
// epoll_wait返回各种事件
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
....
for (size_t i = 0; i < n; ++i) {
if (epoll_events[i].events & EPOLLIN) {
if (epoll_events[i].data.fd == listenfd) {
// 将连接套接字注册到epoll实例中,注册可读事件
....
} else {
// 处理连接套接字上的可读事件,注意,每次只读一个字节
int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
// 针对m的逻辑处理, 将接受到的一个字符输出
std::cout << "recv from client:" << ch << std::endl;
}
} else if (epoll_events[i].events & EPOLLERR) {
// 处理错误
}
}
如果次数客户端,对该服务器发送abcd
,那么服务端会输出,
recv from client:a
recv from client:b
recv from client:c
recv from client:d
// 将监听套接字注册到epoll实例中
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)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
// epoll_wait返回各种事件
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
....
for (size_t i = 0; i < n; ++i) {
if (epoll_events[i].events & EPOLLIN) {
if (epoll_events[i].data.fd == listenfd) {
// 将连接套接字注册到epoll实例中,注册可读事件
....
} else {
// 处理连接套接字上的可读事件,注意,每次只读一个字节
int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
// 针对m的逻辑处理, 将接受到的一个字符输出
std::cout << "recv from client:" << ch << std::endl;
}
} else if (epoll_events[i].events & EPOLLERR) {
// 处理错误
}
}
如果次数客户端,对该服务器发送abcd
,那么服务端会输出,
recv from client:a
此时注意啊,et模式下只会触发一次事件,当客户端再此发送123
,服务端输出:
recv from client:b
// 将监听套接字注册到epoll实例中
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)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
// epoll_wait返回各种事件
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
....
for (size_t i = 0; i < n; ++i) {
if (epoll_events[i].events & EPOLLIN) {
if (epoll_events[i].data.fd == listenfd) {
// 将连接套接字注册到epoll实例中,注册可读可写事件
....
} else {
// 处理连接套接字上的可读事件,注意,每次只读一个字节
int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
// 针对m的逻辑处理, 将接受到的一个字符输出
std::cout << "recv from client:" << ch << std::endl;
}
}else if (epoll_events[i].events & EPOLLOUT) {
std::cout << "EPOLLOUT triggered,clientfd:"<< std::endl;
}
else if (epoll_events[i].events & EPOLLERR) {
// 处理错误
}
}
如果客户端对服务器发起连接,由于套接字输出缓存可写,一直触发可写事件,因此输出:
EPOLLOUT triggered,clientfd:
EPOLLOUT triggered,clientfd:
EPOLLOUT triggered,clientfd:
......
// 将监听套接字注册到epoll实例中
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)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
// epoll_wait返回各种事件
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
....
for (size_t i = 0; i < n; ++i) {
if (epoll_events[i].events & EPOLLIN) {
if (epoll_events[i].data.fd == listenfd) {
// 将连接套接字注册到epoll实例中,注册可读可写事件
....
} else {
// 处理连接套接字上的可读事件,注意,这里每次可输出1024个字节
int m = recv(epoll_events[i].data.fd, &ch, 1024, 0);
// 针对m的逻辑处理, 将接受到的一个字符输出
std::cout << "recv from client:" << ch << std::endl;
}
}else if (epoll_events[i].events & EPOLLOUT) {
std::cout << "EPOLLOUT triggered,clientfd:"<< std::endl;
}
else if (epoll_events[i].events & EPOLLERR) {
// 处理错误
}
}
ET模式下,可写事件只会触发一次,刚连接时服务端输出
EPOLLOUT triggered,clientfd:
如果需要再次触发,客户端需要再次发送消息,比如客户端发送abc
,此时服务端会激发写事件和读事件,因此输出
recv from client:abc
EPOLLOUT triggered,clientfd:
首先给出结论,
ET
模式适用高流量的情景下,比如nginx就是使用et模式,比如发送大文件et模式较为使用。redis
就是LT模式。原因:使用ET模式,特定场景下会比LT更快,因为它可以便捷的处理EPOLLOUT事件,省去打开与关闭EPOLLOUT的epoll_ctl(EPOLL_CTL_MOD)调用。从而有可能让你的性能得到一定的提升。
LT代码
代码大概信息如下:
当epoll实例监听套接字有读事件触发时,将新产生的 连接套接字
注册到epoll
实例中,事件类型为EPOLLIN
。
当连接套接字有请求时,激发读事件,此时我们读取套接字内容并进行处理,接着发送一个reponsd
。
注意,这里就需要根据是否一次性发完respond
进行区别:
if (一次性发完) {
// 只注册读事件
updateEvents(efd, fd, EPOLLIN, EPOLL_CTL_MOD);
} else {
updateEvents(efd, fd, EPOLLIN | EPOLLOUT, EPOLL_CTL_MOD);
}
像比如ET模式,效率就出现再最后一个updateEvents上,如果文件特别大,是不是这个语句执行的次数就更多,因此大文件适用于ET模式。
ET代码
代码大概信息如下:
连接套接字
注册到epoll
实例中,事件类型为EPOLLIN | EPOLLOUT | EPOLLRT
。reponsd
。从上述流程可以看出,像比如ET模式,效率就出现再最后一个updateEvents上,如果文件特别大,是不是这个语句执行的次数就更多,因此大文件适用于ET模式。