开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天,点击查看活动详情。
一、read 函数返回值
> 0: 实际读到的字节数
= 0: socket中,表示对端关闭。close()
- 1: 如果 errno == EINTR 被异常终端。 需要重启。
如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式读数据,但是没有数据。 需要,再次读。
如果 errno == ECONNRESET 说明连接被 重置。 需要 close(),移除监听队列。 错误。
二、突破 1024 文件描述符限制
cat /proc/sys/fs/file-max --> 当前计算机所能打开的最大文件个数。 受硬件影响。
ulimit -a ——> 当前用户下的进程,默认打开文件描述符个数。 缺省为 1024
修改:
打开 sudo vi /etc/security/limits.conf, 写入:
-
soft nofile 65536 --> 设置默认值, 可以直接借助命令修改。 【注销用户,使其生效】
-
hard nofile 100000 --> 命令修改上限。
三、epoll
int epoll_create(int size); 创建一棵监听红黑树
size:创建的红黑树的监听节点数量。(仅供内核参考。)
返回值:指向新创建的红黑树的根节点的 fd。
失败: -1 errno
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作监听红黑树
epfd:epoll_create 函数的返回值。 epfd
op:对该监听红黑数所做的操作。
EPOLL_CTL_ADD 添加fd到 监听红黑树
EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件。
EPOLL_CTL_DEL 将一个fd 从监听红黑树上摘下(取消监听)
fd:
待监听的fd
event: 本质 struct epoll_event 结构体 地址
成员 events:
EPOLLIN / EPOLLOUT / EPOLLERR
成员 data: 联合体(共用体):
int fd; 对应监听事件的 fd
void *ptr;
uint32_t u32;
uint64_t u64;
返回值:成功 0; 失败: -1 errno
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 阻塞监听。
epfd:epoll_create 函数的返回值。 epfd
events:传出参数,【数组】, 满足监听条件的 哪些 fd 结构体。
maxevents:数组 元素的总个数。 1024
struct epoll_event evnets[1024]
timeout:
-1: 阻塞
0: 不阻塞
>0: 超时时间 (毫秒)
返回值:
> 0: 满足监听的 总个数。 可以用作循环上限。
0: 没有fd满足监听事件
-1:失败。 errno
epoll
四、epoll实现多路IO转接思路
lfd = socket(); 监听连接事件lfd
bind();
listen();
int epfd = epoll_create(1024); epfd, 监听红黑树的树根。
struct epoll_event tep, ep[1024]; tep, 用来设置单个fd属性, ep 是 epoll_wait() 传出的满足监听事件的数组。
tep.events = EPOLLIN; 初始化 lfd的监听属性。 tep.data.fd = lfd
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep); 将 lfd 添加到监听红黑树上。
while (1) {
ret = epoll_wait(epfd, ep,1024, -1); 实施监听
for (i = 0; i < ret; i++) {
if (ep[i].data.fd == lfd) { // lfd 满足读事件,有新的客户端发起连接请求
cfd = Accept();
tep.events = EPOLLIN; 初始化 cfd的监听属性。
tep.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);
} else { cfd 们 满足读事件, 有客户端写数据来。
n = read(ep[i].data.fd, buf, sizeof(buf));
if ( n == 0) {
close(ep[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL); // 将关闭的cfd,从监听树上摘下。
} else if (n > 0) {
小--大
write(ep[i].data.fd, buf, n);
}
}
}
}
五、epoll 事件模型
ET模式:
-
边沿触发:
缓冲区剩余未读尽的数据不会导致 epoll_wait 返回。 新的事件满足,才会触发。
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
-
LT模式:
水平触发 -- 默认采用模式。
缓冲区剩余未读尽的数据会导致 epoll_wait 返回。
-
结论:
epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式。 --- 忙轮询。
struct epoll_event event; event.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event); int flg = fcntl(cfd, F_GETFL); flg |= O_NONBLOCK; fcntl(cfd, F_SETFL, flg); -
优点:
高效。突破1024文件描述符。
-
缺点:
不能跨平台。 Linux。
六epoll 反应堆模型
epoll ET模式 + 非阻塞、轮询 + void *ptr。
-
原来:
socket、bind、listen -- epoll_create 创建监听 红黑树 -- 返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)---- epoll_wait 监听 -- 对应监听fd有事件产生 -- 返回 监听满足数组。 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd 满足 -- read() --- 小->大 -- write回去。
-
反应堆:不但要监听 cfd 的读事件、还要监听cfd的写事件。
socket、bind、listen -- epoll_create 创建监听 红黑树 -- 返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)---- epoll_wait 监听 -- 对应监听fd有事件产生 -- 返回 监听满足数组。 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd 满足 -- read() --- 小->大 -- cfd从监听红黑树上摘下 -- EPOLLOUT -- 回调函数 -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑上监听写事件-- 等待 epoll_wait 返回 -- 说明 cfd 可写 -- write回去 -- cfd从监听红黑树上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑上监听读事件 -- epoll_wait 监听
-
eventset函数:
设置回调函数。
lfd --> acceptconn()cfd --> recvdata();
cfd --> senddata();
-
eventadd函数:
将一个fd, 添加到 监听红黑树。 设置监听 read事件,还是监听写事件。
-
网络编程中:
read --- recv() write --- send();