浅谈 non-blocking I/O Multiplexing + poll/epoll 的正确使用
在前面的文章中曾经粗略讲过poll,那时是用阻塞IO实现,在发送和接收数据量都较小情况下和网络状况良好的情况下是基本没有问题的,read 不会只接收部分数据,write 也不会一直阻塞。但实际上poll IO复用经常是跟非阻塞IO一起使用的,想想如果现在内核接收缓冲区一点数据没有,read 阻塞了,或者内核发送缓冲区不够空间存放数据,write 阻塞了,那整个事件循环就会延迟响应,比如现在又有一个新连接connect上来了,也不能很快回到循环去accept 它。
在前面的文章中也曾粗略讲过epoll,使用的是ET 边沿触发模式,每次accept 返回需要将conn 设置为非阻塞,ET模式可能存在的问题是有可能只读取了部分数据,剩下的epoll_wait 就再也不会返回可读事件了。
这篇文章来谈谈如何正确使用non-blocking I/O Multiplexing + poll/epoll。
1、首先来回顾下poll / epoll 函数的原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
int epoll_create(int size);
//size
并不代表能够容纳的事件个数
int epoll_create1(int flags); // EPOLL_CLOEXEC
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
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 */
};
具体的参数介绍参考以前的文章。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
可以使用accept4 这个新的系统调用,多了一个flags 参数,可以设置以下两个标志:
to fcntl(2) to achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC
flag in open(2) for reasons why this may be useful.
注意,这两个标志是设置accept 回来的conn 标志的,当然也可以使用fcntl (F_SETFL / F_SETFD) 设置,但少了两次系统调用,可以稍微提高点性能。
7、poll 的处理流程和存在的问题
存在的问题和解决办法:
(1)、read 可能一次并没有把connfd 所对应的接收缓冲区(内核)的数据都读完(粘包问题),那么connfd 下次仍然是活跃的
应该把读到的数据保存在connfd 的应用层接收缓冲区,每次都追加在末尾。需要处理协议以区分每条消息的边界
(2)、write 可能一次并不能把所有数据都写到发送缓冲区(内核),所以应该有一个应用层发送缓冲区,将未发送完的数据添加到应用层发送缓冲区,关注connfd 的POLLOUT 事件。POLLOUT事件到来,则取出应用层发送缓冲区数据发送write,如果应用层发送缓冲区数据发送完毕,则取消关注POLLOUT事件。
POLLOUT 事件触发条件:connfd的发送缓冲区(内核)不满(可以容纳数据)
注:connfd 的接收缓冲区(内核)数据被接收后会被清空,当发出数据后接收到对方的ACK段后,发送缓冲区(内核)数据会被清空。write只是将应用层发送缓冲区数据拷贝到connfd 对应的内核发送缓冲区就返回;read 只是从connfd对应的内核接收缓冲区数据拷贝到应用层接收缓冲区就返回。
9、epoll 的两种模式处理流程和存在的问题
内核中的某个socket接收
缓冲区 为空 低电平内核中的某个socket接收缓冲区 不为空 高电平
内核中的某个socket发送缓冲区 不满 高电平内核中的某个socket发送缓冲区 满 低电平
Edge-Triggered:
与poll兼容
LT模式不会发生漏掉事件的BUG,但POLLOUT事件不能一开始就关注,否则会出现busy loop,而应该在write无法完全写入内核缓冲区的时候才关注,将未写入内核缓冲区的数据添加到应用层output buffer,直到应用层output buffer写完,停止关注POLLOUT事件。
读写的时候不必等候EAGAIN,可以节省系统调用次数,降低延迟。(注:如果用ET模式,读的时候读到EAGAIN,写的时候直到output buffer写完或者写到EAGAIN)
10、accept(2)返回EMFILE的处理(文件描述符已经用完)
(1)、调高进程文件描述符数目
(2)、死等
(3)、退出程序
(4)、关闭监听套接字。那什么时候重新打开呢?
(5)、如果是epoll模型,可以改用edge trigger。问题是如果漏掉了一次accept(2),程序再也不会收到新连接(没有状态变化)
(6)、准备一个空闲的文件描述符。遇到这种情况,先关闭这个空闲文件,获得一个文件描述符名额;再accept(2)拿到socket连接的文件描述符;随后立刻close(2),这样就优雅地断开了与客户端的连接;最后重新打开空闲文件,把“坑”填上,以备再次出现这种情况时使用。
|
1 |
int idlefd = open(
"/dev/null", O_RDONLY | O_CLOEXEC); connfd = accept4(listenfd, ( struct sockaddr *)&peeraddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC); /* if (connfd == -1) if (connfd == - |
浅谈 non-blocking I/O Multiplexing + poll/epoll 的正确使用的更多相关文章
- 浅谈 js 对象 toJSON 方法
前些天在<浅谈 JSON.stringify 方法>说了他的正确使用姿势,今天来说下 toJSON 方法吧.其实我觉得这货跟 toString 一个道理,他是给 stringify 方法字 ...
- []转帖] 浅谈Linux下的五种I/O模型
浅谈Linux下的五种I/O模型 https://www.cnblogs.com/chy2055/p/5220793.html 一.关于I/O模型的引出 我们都知道,为了OS的安全性等的考虑,进程是 ...
- 浅谈 Unix I/O 模型
原文出处:http://miaoo.in/talk-about-unix-io-model.html 在实际应用中,数据操作通常分为输入和输出,那么以输入为例,在操作系统中,一个数据的输入通常分为以下 ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- 浅谈Java线程安全
浅谈Java线程安全 - - 2019-04-25 17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...
- 浅谈Websocket、Ajax轮询和长轮询(long polling)
浅谈Websocket.Ajax轮询和长轮询(long p0ll) 最近看到了一些介绍Websocket的文章,觉得挺有用,所以在这里将自己的对其三者的理解记录一下. 1.什么是Websocket W ...
- 浅谈 Fragment 生命周期
版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...
- 浅谈 LayoutInflater
浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...
- 浅谈Java的throw与throws
转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...
随机推荐
- Linux解决xhost: unable to open display
实用技巧:在Linux下设置xhost方法步骤 第一步:用root登陆linux,启动vnc服务: 第二步:根据vnc起来的端口,设置export DISPLAY=localhost:1(1表示vnc ...
- 自定义配置文件的使用(web.config/app.config)
以下非原创作品,但都是自己看过理解并写过,记录下来,以便之后项目的使用或其它用途. (1)只需要简单配置单一属性值: <configuration> <configSections& ...
- leetcode Binary Search Tree Iterator python
# Definition for a binary tree node # class TreeNode(object): # def __init__(self, x): # self.val = ...
- Java提高学习之Object(5)
字符串形式的表现 Q1:toString() 方法实现了什么功能?A1:toString() 方法将根据调用它的对象返回其对象的字符串形式,通常用于debug. Q2:当 toString() 方法没 ...
- OSC本地库推送到远程库
1.新建远程库: 例如:http://git.oschina.net/intval/learngit 2.本地生成ssh密钥 ssh-keygen -t rsa -C "intval@163 ...
- firefox 自写扩展改版,总结
自己写的扩展,油猴功能,进一步改进,增加了许多操作.原来只是在13以下版本下面能用,主要是在13版本下面chrome代码和page下面代码能够直接互调,13版本以后就不可以了,最近考虑到新版Firef ...
- 自定义构造、description方法、SEL
[Objective-C]07-自定义构造方法和description方法 // 构造方法:用来初始化对象的方法,是个对象方法,”-"开头// 重写构造方法的目的:为了让对象创建出来,成 ...
- C/C++招聘的一些感受和经验【转】
找工作本人认为最重要的就是前期准备了. 首先.简历一定要写的切合主题.招聘单位要的是你的技能,这个只要大概能符合就可以,关键他们需要的是你的开发经验,一定要在简历中完美的体现出你之前所参与的项 ...
- SQL Server 触发器的修改与删除
修改: alter trigger trigger_name on ..... as ..... #把create 修成 alter 就可以了. 删除: drop trigger trigger ...
- J2SE知识点摘记-数据库(一)
一. 数据库连接 在JDBC的操作过程中,进行数据库连接的主要步骤如下: 通过Class.forName()加载数据库的驱动程序.首先需要利用来自Class类中的静态方法forNam ...