TCP 连接停留在 CLOSE_WAIT 状态

TCP connections stuck with CLOSE_WAIT state

我需要你们的帮助来解决未关闭 TCP 连接的问题。

基本上它工作正常,但几分钟后连接卡在 CLOSE_WAIT 状态。

代码逻辑:

代码接受第一个数据包并对其进行解析,然后将 CRC 发送回客户端。如果 CRC 有效,则客户端将主数据包发送到服务器并重复它。一旦没有数据包,客户端就会关闭连接,但有时不会。在这种情况下,服务器(下面的代码)在通信 1 分钟后关闭连接。

我想它也应该对应于 https://www.googlecloudcommunity.com/gc/Cloud-Product-Articles/TCP-states-explained/ta-p/78462

这是 C# 代码

void TCPListenerServer(object obj) {
  CancellationToken token = (CancellationToken) obj;
  if (token.IsCancellationRequested) {
    isDisposed = true;
    if (listener != null) {
      listener.Stop();
    }

    return;
  } else {
    try {
      isDisposed = false;

      try {
        var validIP = IPAddress.Parse(Properties.Settings.Default.ServerIP);
        listener = new TcpListener(validIP, Properties.Settings.Default.ServerPort);
        listener.Start();

        while (isDisposed == false || token.IsCancellationRequested == false) {
          if (token.IsCancellationRequested || isDisposed) {
            break;
          } else {

            if (!listener.Pending()) {
              Thread.Sleep(50);
              continue;
            }

            listener.Server.ReceiveTimeout = 10000;
            listener.Server.LingerState = new LingerOption(true, 0);

            var client = listener.AcceptTcpClient();

            var arrThread = new ThreadParams() {
              Client = client, Token = m_Cts.Token
            };
            var t = new Thread(ProcessClientRequests) {
              IsBackground = true,
                Name = "ClientConnectionThread",
            };
            clientThreads.Add(t);

            t.Start((object) arrThread);
          }
        }
      } catch (SocketException ex) {
        if (ex.SocketErrorCode == SocketError.Interrupted) {}
      } catch (Exception ex) {} finally {
        if (listener != null) {
          listener.Server.Close();
          listener.Stop();
        }
      }
    } catch (Exception ex) {

    }
  }
}


    private void ProcessClientRequests(object argument) {
      
      TcpClient client = ((ThreadParams) argument).Client;
      CancellationToken token = ((ThreadParams) argument).Token;
      client.SendTimeout = 10000;
      client.ReceiveTimeout = 10000;
      client.LingerState = new LingerOption(true, 0);
    
      var bufferSize = 1024;
      byte[] buffer = new byte[bufferSize];
      var isFirstPacket = true;
      var startTime = DateTime.Now;
      DateTime endTime = DateTime.Now;
    
      try { 
    
        using(NetworkStream stream = client.GetStream()) {
          do {
            Thread.Sleep(20);
          } while (!stream.DataAvailable);
    
          while ((client != null && client.Connected) && stream != null && stream.CanRead && (endTime - startTime).TotalMinutes < 1) {
            if (client == null) {
              break;
            }
    
            do {
              if (token.IsCancellationRequested) {
                return;
              }
    
              if (client == null) {
                break;
              }
    
              endTime = DateTime.Now;
    
              int streamReadBytes = 0;
              streamReadBytes = stream.Read(buffer, 0, buffer.Length);
    
              if (streamReadBytes == 0) {
                if (client != null) {
                  client.Close();
                }
                break;
              }
    
              if (buffer[0] == (byte) GalileoskyPacketHeaderEnums.FirstPacket || buffer[0] == (byte) GalileoskyPacketHeaderEnums.MainPacket) {
    
                var parserGalileosky = new Galileosky();
    
                var packetResult = parserGalileosky.ParsePacket(buffer, isFirstPacket);
                if (packetResult == null) {
                  if (client != null) {
                    client.Close();
                    client = null;
                  }
    
                  break;
                }
    
                if (packetResult.Errors.Any()) {
                  if (client != null) {
                    client.Close();
                    client = null;
                  }
                } else {
                  var imei = packetResult.Packet.IMEI;
    
                  if (isFirstPacket) {
                    isFirstPacket = false;
    
                    if (stream.CanWrite == true && packetResult.Packet.IsCrc) {
                      var answerPacket = packetResult.Packet.GetConfirmPacket();
                      stream.Write(answerPacket.Ready);
                    } else {
                      if (client != null) {
                        client.Close();
                        client = null;
                      }
                    }
                  } else // The Main Packet processing
                  {
                    // ... Some code to send the main packet to the app queue

                    if (stream.CanWrite == true && !packetResult.Errors.Any() && packetResult.Packet.IsCrc) {
                      var answerPacket = packetResult.Packet.GetConfirmPacket();
                      stream.Write(answerPacket.Ready);
                    }
    
                    if (packetResult.Packet.IsExtraData == false) {
                      if (client != null) {
                        client.Close();
                        client = null;
                        break;
                      }
                    }
    
                  }
                }
              } else {
                if (client != null) {
                  client.Close();
                  client = null;
                }
              }
              if ((endTime - startTime).TotalMinutes > 1) {
                if (client != null) {
                  client.Close();
                  client = null;
                  break;
                }
              }
    
            }
            while ((client != null && client.Connected) && stream != null && stream.CanRead && stream.DataAvailable && (endTime - startTime).TotalMinutes < 1);
          }
        }
    
        if (client != null) {
          client.Close();
          client = null;
        }
      } catch (Exception ex) {} finally {
        if (client != null) {
          client.Close();
          client = null;
        }
      }
    }

即使你的代码是正确的,你也可以解决“2个将军”的问题。没有完美的算法让双方同意关闭连接,这时可能会出现丢包。

优雅地关闭 TCP 流需要双方停止发送,读取数据直到 EOF,然后关闭。我相信如果一方关闭了连接而另一方没有,您会在 CLOSE_WAIT 中看到一个套接字。看起来您已将套接字设置为 linger。在这种情况下,操作系统将为您接管套接字的生命周期。

如果您的套接字未设置为逗留,那么提前关闭套接字会导致您认为已发送的数据丢失。

我还应该指出,您的代码过于复杂,到处都在重复错误处理。看起来它可能是在 C# 引入 async / await 之前编写的。

看来你是在假设一次读取操作等于一个数据包。但这是一个 TCP 流。 OS 允许将从一端写入的数据分段并重新组合到另一端读取的任意数量的数据中。

我建议您在互联网上搜索使用 .AcceptTcpClientAsync.ReadAsync.WriteAsync 等的示例

最后我找不到解决问题的方法来关闭我的代码中的连接。

但我添加了一个 timer 来关闭连接,效果非常好!

private void ProcessClientRequests(object argument) 

 // ...  The same code of my quetion

 Timer timerCloseConn = new(new TimerCallback((e) =>
                            {

                                if (stream != null)
                                {
                                    stream.Close();
                                    stream.Dispose();
                                }
                                if (client != null)
                                {

                                    if (client.Client.Connected)
                                    {
                                        client.Client.Shutdown(SocketShutdown.Both);
                                    }

                                    client.Close();
                                    client.Dispose();
                                    client = null;
                                }

                                Logger.Info("The connection has been closed by 
 timer's rule!");
                            }), null, 60000, Timeout.Infinite);

  // ... The same code of my quetion
}