非阻塞I/O

最近在做服务器上协程的一些工作,遇到了一些问题,在这里分享一下.

背景:
当我们需要自己在用户空间进行协程的调度的时候,不得不对一些I/O等待型的操作进行特殊的处理,常见的这些操作有:connect/accept, read/write, send/recv等.
一种思路便是先用select/poll/epoll对相关的fd进行对应的事件询问,如果fd已经就绪就直接调用库调用,若否则进行调度,例如:
//进行调度的伪代码
int read(fd_t fd){

    //前期的准备工作...
    struct epoll_fd ep_fds;

    ...
    //将EPOLL_IN | EPOLL_ERR事件,fd装入ep_fds中
    ...

    //利用epoll对fd上的IN事件进行探测
    epoll_ctl(&ep_fds,EPOLL_CTL_ADD,...);
    int ret = epoll_wait();

    if(epoll检测出fd上面有EPOLL_IN就绪){

        //直接返回库调用
        return read(fd,...);

    }else{
        ...
        //进行用户空间的协程调度
        ...
    }
    return -1;
}

但在实际的开发中,还要处理一个的问题:当对socket设置为非阻塞后,并调用connect这个函数,或许会返回-1并且有EINTR的错误,这时其实我们需要去监测errno的错误并且做人为的重启.

Plan A:人为的重启,当遇到EINTR错误的时候,需要手动去重新调用一下该函数

当然还有别的处理方法,例如:

Plan B: 安装信号时设置 SA_RESTART属性(该方法对有的系统调用无效)
Plan C:忽略信号(让系统不产生信号中断)
参考:http://blog.chinaunix.net/uid-21501855-id-4490453.html

在stackoverflow上面有对为什么EINTR会出现这个问题进行了精彩的解释,我来搬运一下:

It would also make no sense from a logical perspective. E.g. consider you are using blocking I/O and you call read() and this call has to block, but while it was blocking, a signal is sent to your process and thus the read request is ublocked. How should the system handle this situation? Claiming that read() did succeed? That would be a lie, it did not succeed because no data was read. Claiming it did succeed, but zero bytes data were read? This wouldn’t be correct either, since a “zero read result” is used to indicate end-of-stream (or end-of-stream), so your process would to assume that no data was read, because the end of a file has been reached (or a socket/pipe has been closed at other end), which simply isn’t the case. The end-of-file (or end-of-stream) has not been reached, if you call read() again, it will be able to return more data. So that would also be a lie. You expectation is that this read call either succeeds and reads data or fails with an error. Thus the read call has to fail and return -1 in that case, but what errno value shall the system set? All the other error values indicate a critical error with the file descriptor, yet there was no critical error and indicating such an error would also be a lie. That’s why errno is set to EINTR, which means: “There was nothing wrong with the stream. Your read call just failed, because it was interrupted by a signal. If it wasn’t interrupted, it may still have succeeded, so if you still care for the data, please try again.”
原文地址:http://stackoverflow.com/questions/14134440/eintr-and-non-blocking-calls/14485305#14485305

我来翻译一下这段文字,对应大概的中文解释应该是这样的:

简单来说,read()这个调用是阻塞的,一旦你调用了read()这个调用之后,系统就开始阻塞式地执行read操作,这时如果来了一个信号需要马上处理,那么此刻操作系统遇到下面几个问题:

1.操作系统该怎么处理这个情况,直接返回这个read()调用已经成功了吗?
2.如果直接返回成功,那么返回读出多少字节呢?0字节?
3.如果直接返回失败,那么ERRNO要怎么设置才好呢?EINTR合适吗?

对于第一个问题,明显这种处理是不正确的,既然read实际上并没有读出任何数据,那么就不应该返回成功操作.
对于第二个问题,如果返回成功,但是返回0字节,这与现有的一个假设相矛盾,因为在一开始人们假设返回0字节就意味着已经读到了文件的结束位置了,显然这与实际情况并不符合,因为如果你再调用一次read(),运气好的话没有别的信号过来的话,你是可以读出更多数据的.
对于第三个问题,别的ERRNO设置其实都不合理,因为都不符合实际情况,要将ERROR设置为EINTR的原因是:

EINTR的意思就是:其实stream里面没有任何错误,调用失败其实是因为操作被信号打断了,如果没信号打断操作的话其实是有可能操作成功的,如果你真的想读点数据的话,再来一次吧!

同样的,在connect函数出现-1并且返回EINTR错误后也需要人工重新启动一下(选择这种方法是因为适用范围比较大,而且比较灵活,其他的方法有的函数不能使用).但要注意的是connect这个函数也比较特别,不能直接重新再调用一次,不然就会出现CONNECTION_REFUSED的错误.
因为当第二次调用connect时候,第一次connect发出去的请求已经被接受了,那么第二次调用理应被refuse掉,所以适当的处理方法应该是:

int m_connect(int fd){

        ......             //别的处理代码

    if(connect(fd,...)==0) //如果connect成功了就直接返回
        return 0;

    if(errno==EINTR || errno == EINPROGRESS ){
        //利用epoll检测fd的状态,若无错误则连接成功
        int ret = epoll_wait(epoll_fd,...);   
        ......
        return 0;
        }

    return -1;
}

在这个方法如果想要在协程里面使用还需要作一定的处理,思路便是将epoll_wait()放在调度的过程中.具体做法与不同调度的实现相关.

其实这只是一种方法而已,当然还有别的方法例如安装信号等,但个人认为这种方法较为简单,逻辑上也容易理解.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值