网络协议收发数据问题

1 如何收取数据?

       对于收数据,当接受连接成功得到 clientfd 后,我们会将该 clientfd 绑定到相应的 IO 复用函数上并监听其可读事件。当可读事件触发后,调用 recv 函数从 clientfd 上收取数据(这里不考虑出错的情况),根据不同的网络模式我们可能会收取部分或一次性收完。收取到的数据我们会放入接收缓冲区内,然后做解包操作。对于使用 epoll 的 LT 模式(水平触发模式),我们每次可以只收取部分数据;但是对于 ET 模式(边缘触发模式),我们必须将本次收到的数据全部收完。

  • ET 模式收完的标志是 recv 或者 read 函数的返回值是 -1,错误码是 EAGAIN。

       linux进行非阻塞的socket接收数据时会出现Resource temporarily unavailable,errno代码为11(EAGAIN),表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返回这个错误,这个错误不会破坏socket的同步,不用管它,下次循环接着recv就可以。

2 如何发送数据?

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

直接尝试发送消息处理逻辑:

/**
 *@param data 待发送的数据
 *@param len  待发送数据长度
 */
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();
	} 
}

 LT和ET模式需要注意如下问题:

  • epoll LT 模式:注册监听一次可写事件后,可写事件触发后,尝试发送数据,如果数据此时还不能全部发送完,不用再次注册可写事件;
  • epoll  ET 模式:注册监听可写事件后,可写事件触发后,尝试发送数据,如果数据此时还不能全部发送完,需要再次注册可写事件以便让可写事件下次再次触发(给予再次发数据的机会);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值