#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数一是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合
对于pollfd结构体:
// pollfd结构 struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };
fd表示需要关心的文件描述符
events表示需要关心该fd的哪些事件
revents表示该fd的哪些事件已经就绪
因此fd + events 用来输入的时候看,fd + revents 用来输出的时候看
对于events和revents的取值:
参数二表示fds数组的长度
参数三表示poll函数的超时时间, 单位是毫秒(ms)
timeout > 0 : 表示在timeout时间内阻塞,否则非阻塞返回一次
timeout == 0:表示非阻塞等待
timeout < 0:表示阻塞等待
返回值:
返回值小于0, 表示出错;
返回值等于0, 表示poll函数等待超时;
返回值大于0, 表示poll由于监听的文件描述符就绪而返回
poll中监听的文件描述符数目增多时的缺点:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "log.hpp"
#include
#include
#include
#include
using namespace std;
#define INITPORT 8000
#define DEFAULTFD -1
// 打印函数调试
void Print(const struct pollfd *fdv)
{
cout << "fd list: ";
for (int i = 0; i < 1024; i++)
{
if (fdv[i].fd != DEFAULTFD)
cout << fdv[i].fd << " ";
}
cout << endl;
}
class Util
{
public:
static void Recv(struct pollfd *fdv, int i)
{
cout << "recv in" << endl;
// 读取
// 读取失败就关闭sock并且修改集合组里的数据
char buff[1024];
ssize_t s = recv(fdv[i].fd, buff, sizeof(buff) - 1, 0);
if (s > 0)
{
buff[s] = 0;
cout << "client: " << buff << endl;
LogMessage(NORMAL, "client: %s", buff);
}
else if (s == 0)
{
close(fdv[i].fd);
fdv[i].fd = DEFAULTFD;
fdv[i].events = 0;
fdv[i].revents = 0;
LogMessage(NORMAL, "client quit");
return;
}
else
{
close(fdv[i].fd);
fdv[i].fd = DEFAULTFD;
fdv[i].events = 0;
fdv[i].revents = 0;
LogMessage(ERROR, "client quit: %s", strerror(errno));
return;
}
// 写回数据
// 这里不考虑写事件
string response = buff;
write(fdv[i].fd, response.c_str(), response.size());
LogMessage(DEBUG, "Recver end");
}
// 将通信sock添加进集合组
static void AddSock(struct pollfd *fdv, int listensock)
{
// listensock读事件就绪
string clientip;
uint16_t clientport;
int sock = Util::GetSock(listensock, &clientip, &clientport);
if (sock < 0)
return;
LogMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);
int i = 0;
for (; i < 1024; i++)
{
if (fdv[i].fd != DEFAULTFD)
continue;
else
break;
}
if (i == 1024)
{
LogMessage(WARNING, "server if full, please wait");
close(sock);
}
else
{
fdv[i].fd = sock;
fdv[i].events = POLLIN;
fdv[i].revents = 0;
}
Print(fdv);
LogMessage(DEBUG, "Accepter out");
}
// 获取新连接创建通信sock
static int GetSock(int listensock, string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
LogMessage(ERROR, "accept socket error, next");
else
{
LogMessage(NORMAL, "accept socket %d success", sock);
cout << "sock: " << sock << endl;
*clientip = inet_ntoa(peer.sin_addr);
*clientport = ntohs(peer.sin_port);
}
return sock;
}
// 设置监听套接字为监听状态
static void setListen(int listensock)
{
if (listen(listensock, 5) < 0)
{
LogMessage(FATAL, "listen socket error!");
exit(3);
}
LogMessage(NORMAL, "listen socket success");
}
// 绑定网络信息
static void bindSock(int port, int listensock)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
LogMessage(FATAL, "bind socket error!");
exit(2);
}
LogMessage(NORMAL, "bind sock success");
}
// 创建监听套接字
static void createSock(int *listensock)
{
*listensock = socket(AF_INET, SOCK_STREAM, 0);
if (listensock < 0)
{
LogMessage(FATAL, "create socket error!");
exit(1);
}
LogMessage(NORMAL, "create socket success");
// 设置进程可以立即重启
int opt = 1;
setsockopt(*listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
}
// 设置非阻塞
static void SetNonBlock(int fd)
{
int f = fcntl(fd, F_GETFL);
if (f < 0)
{
cerr << "fcntl" << endl;
return;
}
fcntl(fd, F_SETFL, f | O_NONBLOCK);
}
};
#pragma once
#include "Util.hpp"
class Server
{
public:
Server(const uint16_t port = INITPORT)
: _port(port), _listensock(-1)
{
}
void HandlerEvent()
{
int i = 0;
for (int i = 0; i < 1024; ++i)
{
// 过滤掉非法的fd
if (fdv[i].fd == DEFAULTFD)
continue;
if(!(fdv[i].events & POLLIN))
continue;
if (fdv[i].fd == _listensock && (fdv[i].revents & POLLIN)) // 判断listensock在不在就绪的集合中
Util::AddSock(fdv, _listensock);
else if (fdv[i].revents & POLLIN)
Util::Recv(fdv, i);
else
{
}
}
}
void Init()
{
// 创建监听套接字
Util::createSock(&_listensock);
// 绑定网络信息
Util::bindSock(_port, _listensock);
// 设置监听套接字为监听状态
Util::setListen(_listensock);
fdv = new struct pollfd[1024];
for (int i = 0; i < 1024; ++i)
{
fdv[i].fd = DEFAULTFD;
fdv[i].events = 0;
fdv[i].revents = 0;
}
fdv[0].fd = _listensock;
fdv[0].events = POLLIN;
}
void start()
{
int timeout = 1000;
while (1)
{
int n = poll(fdv, 1024, timeout);
switch (n)
{
case 0:
cout << "timeout...." << endl;
LogMessage(NORMAL, "timeout....");
break;
case -1:
printf("select error, code: %d, err string: %s", errno, strerror(errno));
LogMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
break;
default:
// 有事件就绪
cout << "event readly" << endl;
LogMessage(NORMAL, "event readly");
// 处理事件
HandlerEvent();
break;
}
}
}
~Server()
{
if (_listensock < 0)
close(_listensock);
}
private:
int _listensock;
uint16_t _port;
struct pollfd *fdv;
};
#include "Server.hpp"
#include
// 输出命令错误函数
void Usage(string proc)
{
cout << "Usage:\n\t" << proc << " local_port\n\n";
}
int main(int argc, char *argv[])
{
// 启动服务端不需要指定IP
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
unique_ptr<Server> sptr(new Server(port));
sptr->Init();
sptr->start();
return 0;
}
#pragma once
#include
#include
#include
#include
#include
using namespace std;
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *to_levelstr(int level)
{
switch (level)
{
case DEBUG:
return "DEBUG";
case NORMAL:
return "NORMAL";
case WARNING:
return "WARNING";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
default:
return nullptr;
}
}
void LogMessage(int level, const char *format, ...)
{
#define NUM 1024
char logpre[NUM];
snprintf(logpre, sizeof(logpre), "[%s][%ld][%d]", to_levelstr(level), (long int)time(nullptr), getpid());
char line[NUM];
// 可变参数
va_list arg;
va_start(arg, format);
vsnprintf(line, sizeof(line), format, arg);
// 保存至文件
FILE* log = fopen("log.txt", "a");
FILE* err = fopen("log.error", "a");
if(log && err)
{
FILE *curr = nullptr;
if(level == DEBUG || level == NORMAL || level == WARNING)
curr = log;
if(level == ERROR || level == FATAL)
curr = err;
if(curr) fprintf(curr, "%s%s\n", logpre, line);
fclose(log);
fclose(err);
}
}
是为处理大批量句柄而作了改进的poll
它几乎具备了select 和poll 的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法
epoll 有3个相关的系统调用
#include
int epoll_create(int size);
创建一个epoll的句柄,size就是epoll的容量大小。返回epoll的文件描述符
注意用完之后, 必须调用close()关闭
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
- 第一个参数是epoll_create()的返回值(epoll的句柄).
- 第二个参数表示动作,用三个宏来表示.
- 第三个参数是需要监听的fd.
- 第四个参数是告诉内核需要监听什么事
其中参数二的三个动作分别为:
- EPOLL_CTL_ADD :注册新的fd到epfd中;
- EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL :从epfd中删除一个fd;
参数四的结构为:
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ } __EPOLL_PACKED;
events可以是以下几个宏的集合:
- EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
- EPOLLOUT : 表示对应的文件描述符可以写;
- EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
- EPOLLERR : 表示对应的文件描述符发生错误;
- EPOLLHUP : 表示对应的文件描述符被挂断;
- EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
- EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里
收集在epoll监控的事件中已经发送的事件
#include
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
- 参数events是分配好的epoll_event结构体数组.
- epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
- maxevents告诉内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
- 参数timeout是超时时间 (毫秒, 0会立即返回, -1是永久阻塞).
- 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败
当某一进程调用epoll_create方法时, Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关
- 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.
- 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度).
- 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
- 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
- 在epoll中,对于每一个事件,都会建立一个epitem结构体
- 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
- 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1)
总的来说,epoll的使用过程就是三部曲
- 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
- 没有数量限制: 文件描述符数目无上限
epoll有2种工作方式-水平触发(LT)和边缘触发(ET)
LT:就好比快递员给你送快递,他打电话叫你下楼拿,可是你告诉快递员还没有空,于是快递员就一直在打电话给你,直到你下楼将快递取回。
ET:就好比快递员给你送快递,他打电话叫你下楼拿,可是你告诉快递员还没有空,这时这位快递员就不等你也不会再打电话给你,把快递放在楼下就走了,至于你有没有下楼取快递他不关心了
这两种模式的区别就在于其通知机制不一样,在LT模式下只要还有数据没有取走就会一直通知,而对于ET从始至终只会通知一次,除非数据从无到有,从有到多发生变化的时候才会再次通知
LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完,相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的。另一方面, ET 的代码复杂程度更高了
LT的文件描述符可以是阻塞的也可以是非阻塞的,但是ET模式下必须为非阻塞的
#pragma once
#include "log.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define INITPORT 8000
#define DEFAULTFD -1
#define EPOLLSIZE 128
class Util
{
public:
// 获取新连接创建通信sock
static int GetSock(int listensock, string *clientip, uint16_t *clientport)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(listensock, (struct sockaddr *)&peer, &len);
if (sock < 0)
LogMessage(ERROR, "accept socket error, next");
else
{
LogMessage(NORMAL, "accept socket %d success", sock);
cout << "sock: " << sock << endl;
*clientip = inet_ntoa(peer.sin_addr);
*clientport = ntohs(peer.sin_port);
}
return sock;
}
// 创建epoll模型
static int CreateEpollFD()
{
int epfd = epoll_create(EPOLLSIZE);
if(epfd < 0)
{
LogMessage(FATAL, "Create epoll_fd error!");
exit(4);
}
LogMessage(NORMAL, "Create epoll_fd success!");
return epfd;
}
// 设置监听套接字为监听状态
static void SetListen(int sock)
{
int n = listen(sock, 5);
if(n < 0)
{
LogMessage(FATAL, "Set listen error!");
exit(3);
}
LogMessage(NORMAL, "Set listen success!");
}
// 绑定网络信息
static void BindSock(int sock, uint16_t port)
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(sock, (struct sockaddr *)&local, sizeof(local));
if(n < 0)
{
LogMessage(FATAL, "Bind error!");
exit(2);
}
LogMessage(NORMAL, "Bind success!");
}
// 创建监听套接字
static int CreateSock()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
LogMessage(FATAL, "Create sock error!");
exit(1);
}
LogMessage(NORMAL, "Create sock success!");
return sock;
}
};
#pragma once
#include "Util.hpp"
class Server
{
public:
Server(const uint16_t port = INITPORT) : _port(port), _listensock(-1), _epfd(-1), _revs(nullptr), _num(64)
{
}
void Init()
{
// 创建监听套接字
_listensock = Util::CreateSock();
// 绑定网络信息
Util::BindSock(_listensock, _port);
// 设置监听套接字为监听状态
Util::SetListen(_listensock);
// 创建epoll模型
_epfd = Util::CreateEpollFD();
// 添加listensock到epoll中
struct epoll_event ev;
ev.events = EPOLLIN;
// 记录fd,当事件就绪时可以知道是哪一个就绪
ev.data.fd = _listensock;
epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);
// 申请就绪事件的空间
_revs = new struct epoll_event[_num];
}
void start()
{
int timeout = 1000;
while (1)
{
int n = epoll_wait(_epfd, _revs, _num, timeout);
switch (n)
{
case 0:
LogMessage(NORMAL, "....timeout....");
break;
case -1:
LogMessage(WARNING, "epoll_wait error: %s", strerror(errno));
break;
default:
HandlerEvent(n);
break;
}
}
}
// 获取事件
void HandlerEvent(int n)
{
LogMessage(NORMAL, "HandlerEvent in");
for (int i = 0; i < n; ++i)
{
// 保留事件就绪的类型
uint32_t events = _revs[i].events;
// 获取当前就绪事件的fd
int sock = _revs[i].data.fd;
if (sock == _listensock && (events & EPOLLIN))
{
string Clienip;
uint16_t ClientPort;
// 获取通信的套接字
int fd = Util::GetSock(_listensock, &Clienip, &ClientPort);
if (fd < 0)
continue;
// 将sock添加进epoll
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
}
else if (events & EPOLLIN)
{
// 读取数据
char buff[1024];
int s = recv(sock, buff, sizeof(buff), 0);
if (s > 0)
{
buff[s] = 0;
cout << "client: " << buff << endl;
LogMessage(NORMAL, "client: %s", buff);
}
else if (s == 0)
{
// 将sock从epoll中移除并关闭sock
epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
close(sock);
LogMessage(NORMAL, "client quit!");
}
else
{
// 将sock从epoll中移除并关闭sock
epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
close(sock);
LogMessage(ERROR, "Recv error: %s", strerror(errno));
}
string response = buff;
write(sock, response.c_str(), response.size());
}
else
{
}
}
LogMessage(NORMAL, "HandlerEvent end");
}
~Server()
{
if (_listensock != -1)
close(_listensock);
if (_epfd != -1)
close(_epfd);
}
private:
int _listensock;
uint16_t _port;
int _epfd; // epoll
struct epoll_event *_revs; // 保存就绪事件
int _num; // 就绪事件的空间大小
};
#include "Server.hpp"
#include
// 输出命令错误函数
void Usage(string proc)
{
cout << "Usage:\n\t" << proc << " local_port\n\n";
}
int main(int argc, char *argv[])
{
// 启动服务端不需要指定IP
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
unique_ptr<Server> sptr(new Server(port));
sptr->Init();
sptr->start();
return 0;
}
#pragma once
#include
#include
#include
#include
#include
using namespace std;
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
const char *to_levelstr(int level)
{
switch (level)
{
case DEBUG:
return "DEBUG";
case NORMAL:
return "NORMAL";
case WARNING:
return "WARNING";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
default:
return nullptr;
}
}
void LogMessage(int level, const char *format, ...)
{
#define NUM 1024
char logpre[NUM];
snprintf(logpre, sizeof(logpre), "[%s][%ld][%d]", to_levelstr(level), (long int)time(nullptr), getpid());
char line[NUM];
// 可变参数
va_list arg;
va_start(arg, format);
vsnprintf(line, sizeof(line), format, arg);
// 保存至文件
FILE* log = fopen("log.txt", "a");
FILE* err = fopen("log.error", "a");
if(log && err)
{
FILE *curr = nullptr;
if(level == DEBUG || level == NORMAL || level == WARNING)
curr = log;
if(level == ERROR || level == FATAL)
curr = err;
if(curr) fprintf(curr, "%s%s\n", logpre, line);
fclose(log);
fclose(err);
}
}