epoll的相关系统调用
epoll 有3个相关的系统调用
epoll_create
int epoll_create(int size);
创建一个epoll的句柄.
自从linux2.6.8之后,size参数是被忽略的.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
events可以是以下几个宏的集合:
epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
但是问题来了
epoll的使用场景
epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反
对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll.
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define CHECK_RET(exp) \
if (!(exp)) \
{ \
return false; \
} \
class TcpSocket
{
public:
TcpSocket() : fd_(-1) {}
TcpSocket(int fd) : fd_(fd) {}
bool Socket()
{
fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (fd_ < 0)
{
perror("socket");
return false;
}
printf("open fd = %d\n", fd_);
return true;
}
bool Close() const
{
close(fd_);
printf("close fd = %d\n", fd_);
return true;
}
bool Bind(const std::string &ip, uint16_t port) const
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
int ret = bind(fd_, (sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("bind");
return false;
}
return true;
}
bool Listen(int num) const
{
int ret = listen(fd_, num);
if (ret < 0)
{
perror("listen");
return false;
}
return true;
}
bool Accept(TcpSocket *peer, std::string *ip = NULL, uint16_t *port = NULL) const
{
sockaddr_in peer_addr;
socklen_t len = sizeof(peer_addr);
int new_sock = accept(fd_, (sockaddr *)&peer_addr, &len);
if (new_sock < 0)
{
perror("accept");
return false;
}
printf("accept fd = %d\n", new_sock);
peer->fd_ = new_sock;
if (ip != NULL)
{
*ip = inet_ntoa(peer_addr.sin_addr);
}
if (port != NULL)
{
*port = ntohs(peer_addr.sin_port);
}
return true;
}
bool Recv(std::string *buf) const
{
buf->clear();
char tmp[1024 * 10] = {0};
// [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
// 参考 man 手册 MSG_WAITALL 节.
ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
if (read_size < 0)
{
perror("recv");
return false;
}
if (read_size == 0)
{
return false;
}
buf->assign(tmp, read_size);
return true;
}
bool Send(const std::string &buf) const
{
ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
if (write_size < 0)
{
perror("send");
return false;
}
return true;
}
bool Connect(const std::string &ip, uint16_t port) const
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = connect(fd_, (sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("connect");
return false;
}
return true;
}
int GetFd() const
{
return fd_;
}
private:
int fd_;
}
;
tcp_epoll_sever.hpp
#pragma once
#include
#include
#include
#include "tcp_socket.hpp"
typedef std::function Handler;
class Epoll
{
public:
Epoll()
{
_epoll_fd = epoll_create(10);
}
~Epoll()
{
close(_epoll_fd);
}
bool Add(const TcpSocket &sock) const
{
int fd = sock.GetFd();
printf("[epoll add ] fd = %d\n", fd);
epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN;
int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
if (ret < 0)
{
perror("epoll_ctl Add");
return false;
}
return true;
}
bool Del(const TcpSocket &sock) const
{
int fd = sock.GetFd();
printf("[epoll del] fd = %d\n", fd);
int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, NULL);
if (ret < 0)
{
perror("epoll_ctl del");
return false;
}
return true;
}
bool Wait(std::vector *output) const
{
output->clear();
epoll_event events[1000];
int nfds = epoll_wait(_epoll_fd, events, sizeof(events) / sizeof(events[0]), -1);
if (nfds < 0)
{
perror("epool_wait");
return false;
}
// [注意!] 此处必须是循环到 nfds, 不能多循环
for (size_t i = 0; i < nfds; ++i)
{
TcpSocket sock(events[i].data.fd);
output->push_back(sock);
}
return true;
}
private:
int _epoll_fd;
};
class TcpEpollServer
{
public:
TcpEpollServer(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
{
}
TcpEpollServer(uint16_t port) :port_(port)
{
}
bool Start(Handler handler)
{
// 1. 创建 socket
TcpSocket listen_sock;
CHECK_RET(listen_sock.Socket());
// 2. 绑定
CHECK_RET(listen_sock.Bind(ip_, port_));
// 3. 监听
CHECK_RET(listen_sock.Listen(5));
// 4. 创建 Epoll 对象, 并将 listen_sock 加入进去
Epoll epoll;
epoll.Add(listen_sock);
// 5. 进入事件循环
for (;;)
{
// 6. 进行 epoll_wait
std::vector output;
if (!epoll.Wait(&output))
{
continue;
}
// 7. 根据就绪的文件描述符的种类决定如何处理
for (size_t i = 0; i < output.size(); ++i)
{
if (output[i].GetFd() == listen_sock.GetFd())
{
// 如果是 listen_sock, 就调用 accept
TcpSocket new_sock;
listen_sock.Accept(&new_sock);
epoll.Add(new_sock);
}
else
{
// 如果是 new_sock, 就进行一次读写
std::string req, resp;
bool ret = output[i].Recv(&req);
if (!ret)
{
// [注意!!] 需要把不用的 socket 关闭
// 先后顺序别搞反. 不过在 epoll 删除的时候其实就已经关闭 socket 了
epoll.Del(output[i]);
output[i].Close();
continue;
}
handler(req, &resp);
output[i].Send(resp);
} // end for
} // end for (;;)
}
return true;
}
private:
std::string ip_;
uint16_t port_;
};
server.cc
#include
#include "tcp_epoll_server.hpp"
std::unordered_map g_dict;
void Translate(const std::string &req, std::string *resp)
{
auto it = g_dict.find(req);
if (it == g_dict.end())
{
*resp = "未找到";
return;
}
*resp = it->second;
return;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("Usage ./dict_server [port]\n");
return 1;
}
// 1. 初始化词典
g_dict.insert(std::make_pair("hello", "你好"));
g_dict.insert(std::make_pair("world", "世界"));
g_dict.insert(std::make_pair("bit", "贼NB"));
// 2. 启动服务器
TcpEpollServer server(atoi(argv[1]));
server.Start(Translate);
return 0;
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define CHECK_RET(exp) \
if (!(exp)) \
{ \
return false; \
}
class TcpSocket
{
public:
TcpSocket() : fd_(-1) {}
TcpSocket(int fd) : fd_(fd) {}
// 以下代码添加在 TcpSocket 类中
// 非阻塞 IO 接口
bool SetNoBlock()
{
int fl = fcntl(fd_, F_GETFL);
if (fl < 0)
{
perror("fcntl F_GETFL");
return false;
}
int ret = fcntl(fd_, F_SETFL, fl | O_NONBLOCK);
if (ret < 0)
{
perror("fcntl F_SETFL");
return false;
}
return true;
}
bool RecvNoBlock(std::string *buf) const
{
// 对于非阻塞 IO 读数据, 如果 TCP 接受缓冲区为空, 就会返回错误
// 错误码为 EAGAIN 或者 EWOULDBLOCK, 这种情况也是意料之中, 需要重试
// 如果当前读到的数据长度小于尝试读的缓冲区的长度, 就退出循环
// 这种写法其实不算特别严谨(没有考虑粘包问题)
buf->clear();
char tmp[1024 * 10] = {0};
for (;;)
{
ssize_t read_size = recv(fd_, tmp, sizeof(tmp) - 1, 0);
if (read_size < 0)
{
if (errno == EWOULDBLOCK || errno == EAGAIN)
{
continue;
}
perror("recv");
return false;
}
if (read_size == 0)
{
// 对端关闭, 返回 false
return false;
}
tmp[read_size] = '\0';
*buf += tmp;
if (read_size < (ssize_t)sizeof(tmp) - 1)
{
printf("数据太少\n");
break;
}
}
return true;
}
bool SendNoBlock(const std::string &buf) const
{
// 对于非阻塞 IO 的写入, 如果 TCP 的发送缓冲区已经满了, 就会出现出错的情况
// 此时的错误号是 EAGAIN 或者 EWOULDBLOCK. 这种情况下不应放弃治疗
// 而要进行重试
ssize_t cur_pos = 0; // 记录当前写到的位置
ssize_t left_size = buf.size();
for (;;)
{
ssize_t write_size = send(fd_, buf.data() + cur_pos, left_size, 0);
if (write_size < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
// 重试写入
continue;
}
return false;
}
cur_pos += write_size;
left_size -= write_size;
// 这个条件说明写完需要的数据了
if (left_size <= 0)
{
break;
}
}
return true;
}
bool Socket()
{
fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (fd_ < 0)
{
perror("socket");
return false;
}
printf("open fd = %d\n", fd_);
return true;
}
bool Close() const
{
close(fd_);
printf("close fd = %d\n", fd_);
return true;
}
bool Bind(const std::string &ip, uint16_t port) const
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
int ret = bind(fd_, (sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("bind");
return false;
}
return true;
}
bool Listen(int num) const
{
int ret = listen(fd_, num);
if (ret < 0)
{
perror("listen");
return false;
}
return true;
}
bool Accept(TcpSocket *peer, std::string *ip = NULL, uint16_t *port = NULL) const
{
sockaddr_in peer_addr;
socklen_t len = sizeof(peer_addr);
int new_sock = accept(fd_, (sockaddr *)&peer_addr, &len);
if (new_sock < 0)
{
perror("accept");
return false;
}
printf("accept fd = %d\n", new_sock);
peer->fd_ = new_sock;
if (ip != NULL)
{
*ip = inet_ntoa(peer_addr.sin_addr);
}
if (port != NULL)
{
*port = ntohs(peer_addr.sin_port);
}
return true;
}
bool Recv(std::string *buf) const
{
buf->clear();
char tmp[1024 * 10] = {0};
// [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
// 参考 man 手册 MSG_WAITALL 节.
ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
if (read_size < 0)
{
perror("recv");
return false;
}
if (read_size == 0)
{
return false;
}
buf->assign(tmp, read_size);
return true;
}
bool Send(const std::string &buf) const
{
ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
if (write_size < 0)
{
perror("send");
return false;
}
return true;
}
bool Connect(const std::string &ip, uint16_t port) const
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = connect(fd_, (sockaddr *)&addr, sizeof(addr));
if (ret < 0)
{
perror("connect");
return false;
}
return true;
}
int GetFd() const
{
return fd_;
}
private:
int fd_;
};
tcp_epoll_server.hpp
#pragma once
#include
#include
#include
#include "tcp_socket.hpp"
typedef std::function Handler;
class Epoll
{
public:
Epoll()
{
_epoll_fd = epoll_create(10);
}
~Epoll()
{
close(_epoll_fd);
}
bool Add(const TcpSocket &sock, bool epoll_et = false) const
{
int fd = sock.GetFd();
printf("[Epoll Add] fd = %d\n", fd);
epoll_event ev;
ev.data.fd = fd;
if (epoll_et)
{
ev.events = EPOLLIN | EPOLLET;
}
else
{
ev.events = EPOLLIN;
}
int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
if (ret < 0)
{
perror("epoll_ctl ADD");
return false;
}
return true;
}
bool Del(const TcpSocket &sock) const
{
int fd = sock.GetFd();
printf("[Epoll Del] fd = %d\n", fd);
int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, NULL);
if (ret < 0)
{
perror("epoll_ctl DEL");
return false;
}
return true;
}
bool Wait(std::vector *output) const
{
output->clear();
epoll_event events[1000];
int nfds = epoll_wait(_epoll_fd, events, sizeof(events) / sizeof(events[0]), -1);
if (nfds < 0)
{
perror("epool_wait");
return false;
}
// [注意!] 此处必须是循环到 nfds, 不能多循环
for (size_t i = 0; i < nfds; ++i)
{
TcpSocket sock(events[i].data.fd);
output->push_back(sock);
}
return true;
}
private:
int _epoll_fd;
};
class TcpEpollServer
{
public:
TcpEpollServer(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
{
}
TcpEpollServer(uint16_t port) : port_(port)
{
}
bool Start(Handler handler)
{
// 1. 创建 socket
TcpSocket listen_sock;
CHECK_RET(listen_sock.Socket());
// 2. 绑定
CHECK_RET(listen_sock.Bind(ip_, port_));
// 3. 监听
CHECK_RET(listen_sock.Listen(5));
// 4. 创建 Epoll 对象, 并将 listen_sock 加入进去
Epoll epoll;
epoll.Add(listen_sock);
// 5. 进入事件循环
for (;;)
{
// 6. 进行 epoll_wait
std::vector output;
if (!epoll.Wait(&output))
{
continue;
}
// 7. 根据就绪的文件描述符的种类决定如何处理
for (size_t i = 0; i < output.size(); ++i)
{
if (output[i].GetFd() == listen_sock.GetFd())
{
// 如果是 listen_sock, 就调用 accept
TcpSocket new_sock;
listen_sock.Accept(&new_sock);
epoll.Add(new_sock,true);
}
else
{
// 如果是 new_sock, 就进行一次读写
std::string req, resp;
bool ret = output[i].RecvNoBlock(&req);
if (!ret)
{
// [注意!!] 需要把不用的 socket 关闭
// 先后顺序别搞反. 不过在 epoll 删除的时候其实就已经关闭 socket 了
epoll.Del(output[i]);
output[i].Close();
continue;
}
handler(req, &resp);
output[i].SendNoBlock(resp);
printf("[client %d] req: %s, resp: %s\n", output[i].GetFd(),
req.c_str(), resp.c_str());
} // end for
} // end for (;;)
}
return true;
}
private:
std::string ip_;
uint16_t port_;
};
server.cc
#include
#include "tcp_epoll_server.hpp"
std::unordered_map g_dict;
void Translate(const std::string &req, std::string *resp)
{
auto it = g_dict.find(req);
if (it == g_dict.end())
{
*resp = "未找到";
return;
}
*resp = it->second;
return;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("Usage ./dict_server [port]\n");
return 1;
}
// 1. 初始化词典
g_dict.insert(std::make_pair("hello", "你好"));
g_dict.insert(std::make_pair("world", "世界"));
g_dict.insert(std::make_pair("bit", "贼NB"));
// 2. 启动服务器
TcpEpollServer server(atoi(argv[1]));
server.Start(Translate);
return 0;
}