服务器端发数据时,如果对端一直不收,怎么办?

  • 2021-01-11
  • 浏览 (30)

服务器端发数据时,如果对端一直不收,怎么办?

这类问题一般出现在跨部门尤其是与外部开发人员合作的时候。假设现在有这样一种情况,我们的服务器提供对外的服务,指定好了协议,然后对外提供服务,客户端由外部人员去开发,由于存在太多的不确定性,如果我们在给对端(客户端)发送数据时,对端因为一些问题(可能是逻辑 bug 或者其他的一些问题)一直不从 socket 系统缓冲区中收取数据,而服务器端可能定期产生一些数据需要发送给客户端,再发了一段时间后,由于 TCP 窗口太小,导致数据发送不出去,这样待发送的数据会在服务器端对应的连接的发送缓冲区中积压,如果我们不做任何处理,很快系统就会因为缓冲区过大内存耗尽,导致服务被系统杀死。

对于这种情况,我们一般建议从以下几个方面来增加一些防御措施:

  1. 设置每路发送连接的发送缓冲区大小上限(如 2 M,或者小于这个值),当某路连接上的数据发送不出去的时候,即将数据存入发送缓冲区时,先判断一下缓冲区最大剩余空间,如果剩余空间已经小于我们要放入的数据大小,也就是说缓冲区中数据大小会超过了我们规定的上限,则认为该连接出现了问题,关闭该路连接并回收相应的资源(如清空缓冲区、回收套接字资源等)。示例代码如下:
   //outputBuffer_为发送缓冲区对象
   size_t remainingLen = outputBuffer_.remainingBytes();
   //如果加入到缓冲区中的数据长度超出了发送缓冲区最大剩余量
   if (remainingLen < dataToAppend.length())
   {
   		forceClose()
   		return
   }
   
   outputBuffer_.append(static_cast<const char*>(dataToAppend.c_str()), dataToAppend.length());
  1. 还有另外一种场景,当有一部分数据已经积压在发送缓冲区了,此后服务器端未产生新的待发送的数据,此时如果不做任何处理,发送缓冲区的数据会一直积压,但是发送缓冲区的数据容量也不会超过上限。如果不做任何处理的话,该数据会一直在缓冲区中积压,白白浪费系统资源。对于这种情况一般我们会设置一个定时器,每隔一段时间(如 3 秒)去检查一下各路连接的发送缓冲区中是否还有数据未发送出去,也就是说如果一个连接超过一定时间内还存在未发送出去的数据,我们也认为该连接出现了问题,我们可以关闭该路连接并回收相应的资源(如清空缓冲区、回收套接字资源等)。示例代码如下:
   //每3秒检测一次
   const int SESSION_CHECK_INTERVAL = 3000;
   
   SetTimer(SESSION_CHECK_TIMER_ID, SESSION_CHECK_INTERVAL);
   
   void CSessionManager::OnTimer()
   {
       for (auto iter = m_mapSession.begin(); iter != m_mapSession.end(); ++iter)
       {
           if (!CheckSession(iter->value))
           {
               //关闭session,回收相关的资源
               iter->value->ForceClose();
   
               iter = m_mapSession.erase(iter);
           }
       }
   }
   
   void CSessionManager::CheckSession(CSession* pSession)
   {
       if (!pSession->GetConnection().OutputBuffer.IsEmpty())
           return false;
   
       return true;
   }

上述代码,每隔 3 秒检测所有的 Session 的对应的 Connection 对象,如果发现发送缓冲区非空,说明该连接中发送缓冲区中数据已经驻留 3 秒了,将该连接关闭并清理资源。