美文网首页
为什么非阻塞网络编程中应用层Buffer是必须的?

为什么非阻塞网络编程中应用层Buffer是必须的?

作者: wayyyy | 来源:发表于2021-08-04 01:25 被阅读0次
为什么非阻塞网络编程中应用层Buffer是必须的?

在 陈硕的《Linux 多线程服务端编程》中一书提到:为什么非阻塞网络编程中应用层buffer是必需的?

非阻塞IO的核心思想是避免阻塞在 read()write 或者其他IO系统调用上,这样可以最大限度地复用,让一个线程能否服务于多个socket连接,IO线程只能阻塞在IO 复用地函数上,如 select / poll / epoll_wait

  • Tcp Connection 必须要有output buffer

  • Tcp Connection 必须要有input buffer
    Tcp 是一个无边界地字节流协议,接收方必须要处理"收到的数据不构成一条完整地消息" 和 "一次收到两条消息地数据" 等情况。

epoll LT 模式下接收数据

对于 epoll 模型,可读事件标志是 EPOLLIN。当可读事件触发后,我们调用 recv 函数从 clientfd 上收取数据(这里不考虑出错的情况),对于使用 epoll 的 LT 模式(水平触发模式),我们每次可以只收取部分数据;但是对于 ET 模式(边缘触发模式),我们必须将本次收到的数据全部收完。

epoll LT 模式下发送数据

epoll 的 LT 模式,通常都不会去注册监听该 clientfd 的可写事件。
这是因为,只要对端正常收数据,一般不会出现 TCP 窗口太小导致 send 或 write 函数无法写的问题。因此大多数情况下,clientfd 都是可写的,如果注册了可写事件,会导致一直触发可写事件,而此时不一定有数据需要发送。
所以,如果有数据要发送一般都是调用 send 或者 write 函数直接发送,如果发送过程中, send 函数返回 -1,并且错误码是 EWOULDBLOCK 表明由于 TCP 窗口太小数据已经无法写入时,而仍然还剩下部分数据未发送,此时我们才注册监听可写事件,并将剩余的服务存入自定义的发送缓冲区中,等可写事件触发后再接着将发送缓冲区中剩余的数据发送出去,如果仍然有部分数据不能发出去,继续注册可写事件,当已经无数据需要发送时应该立即移除对可写事件的监听。

void TcpConnection::sendMessage(const void* data, size_t len)
{    
    int32_t nwrote = 0;
    size_t remaining = len;
    bool faultError = false;
    if (state_ == kDisconnected)
    {
        LOGW("disconnected, give up writing");
        return;
    }

    // 当前未监听可写事件,且发送缓冲区中没有遗留数据
    if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
    {
        //直接发送数据
        nwrote = sockets::write(channel_->fd(), data, len);      
        if (nwrote >= 0)
        {
            remaining = len - nwrote;           
        }
        else // nwrote < 0
        {
            nwrote = 0;
            //错误码不等于EWOULDBLOCK说明发送出错了
            if (errno != EWOULDBLOCK)
            {
                LOGSYSE("TcpConnection::sendInLoop");
                if (errno == EPIPE || errno == ECONNRESET)
                {
                    faultError = true;
                }
            }
        }
    }

    //发送未出错且还有剩余字节未发出去
    if (!faultError && remaining > 0)
    {
        //将剩余部分加入发送缓冲区
        outputBuffer_.append(static_cast<const char*>(data) + nwrote, remaining);
        if (!channel_->isWriting())
        {
            //注册可写事件
            channel_->enableWriting();
        }
    }
}

不能全部发出去监听可写事件后,可写事件触发后处理逻辑:

// 可写事件触发后会调用handleWrite()函数
void TcpConnection::handleWrite()
{  
    //将发送缓冲区中的数据发送出去
    int32_t n = sockets::write(channel_->fd(), outputBuffer_.peek(), outputBuffer_.readableBytes());
    if (n > 0)
    {
        //发送多少从发送缓冲区移除多少
        outputBuffer_.retrieve(n);
        //如果发送缓冲区中已经没有剩余,则移除监听可写事件
        if (outputBuffer_.readableBytes() == 0)
        {
            //移除监听可写事件
            channel_->disableWriting();

            if (state_ == kDisconnecting)
            {
                shutdown();
            }
        }
    }
    else
    {
        //发数据出错处理
        LOGSYSE("TcpConnection::handleWrite");           
        handleClose();
    } 
}

实际开发中,如果一段数据反复发送都不能完全发送完(例如对端先不收,后面每隔很长时间再收一个字节),我们可以设置一个最大发送次数或最大发送总时间,超过这些限定,我们可以认为对端出了问题,应该立即清空发送缓冲区并关闭连接。

相关文章

  • 为什么非阻塞网络编程中应用层Buffer是必须的?

    为什么非阻塞网络编程中应用层Buffer是必须的? 在 陈硕的《Linux 多线程服务端编程》中一书提到:为什么非...

  • muduo 源码阅读:Buffer

    为什么non-blocking 网络编程中应用层Buffer是必须的? TODO Buffer 数据结构 为什么 ...

  • NIO代码记录

    Buffer 缓冲 Channel通道 阻塞 非阻塞 管道

  • I/O模型学习小记

    基础概念 通过I/O模型学习同步/异步、阻塞/非阻塞基础概念,参考资料如下:《Unix网络编程》《网络编程释疑之:...

  • 异步IO简析

    什么是异步IO 《UNIX网络编程卷1》中的IO多路复章节总结了几种典型IO模型,包括: 阻塞IO 非阻塞IO I...

  • Kitura的IO

    背景知识 关于同步/异步,阻塞/非阻塞的解释除了参见《Unix网络编程》之外,知乎中,“愚蠢”和“大姚”分别进行了...

  • Java网络编程 - 04 实现非阻塞式的Socket通信

    导读目录 使用NIO实现非阻塞Socket通信 非阻塞通信的几大类:Buffer、Channel、Selector...

  • IO模型

    描述 本文摘自UNIX网络编程卷1:套接字联网API,描述了UNIX中五种IO模型。阻塞IO、非阻塞IO、IO复用...

  • nio 学习

    NIO 同步、非阻塞 menu Channel and Buffer Scatter and Gather Cha...

  • NIO java编程

    NIO 同步式非阻塞式IO NIO组件:Buffer channel selector Buffer 缓冲区 1....

网友评论

      本文标题:为什么非阻塞网络编程中应用层Buffer是必须的?

      本文链接:https://www.haomeiwen.com/subject/zzrbdltx.html