C#TCP服务器停止接收客户端消息,在服务重新启动时恢复

本文关键字:服务 重新启动 恢复 消息 客户端 服务器 C#TCP | 更新日期: 2023-09-27 17:59:29

我在用C#编写的托管Windows服务中工作。它不断接收来自通过TCP/IP连接的几个客户端的消息。客户端基本上是一个路由器,用于接收温度计向服务器发送的消息。服务器解析消息并将它们存储在SQL Server数据库中。

我面临的问题是,一些客户突然停止发送消息。但是,一旦服务重新启动,它们就会重新连接并恢复发送。我没有客户端的代码,因为它是第三方设备,我很确定问题出在服务器上。

我设法通过实现一个计时器来减少这个问题,该计时器可以不断检查每个客户端是否仍处于连接状态(请参阅下面的代码)。此外,我使用socket.IOControl(IOControlCode.KeepAliveValues, ...)方法向Socket添加了Keep Alive模式,但问题仍然存在。

我发布了一些我认为相关的特定部分的代码。但是,如果需要更多的片段来理解这个问题,请问我,我会编辑这篇文章。为了减少代码的弹药数量,所有的try/catch块都被删除了。

我不想要一个完美的解决方案,只要有任何指导都将不胜感激

private Socket _listener;
private ConcurrentDictionary<int, ConnectionState> _connections;
public TcpServer(TcpServiceProvider provider, int port)
{
    this._provider = provider;
    this._port = port;
    this._listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    this._connections = new ConcurrentDictionary<int, ConnectionState>();
    ConnectionReady = new AsyncCallback(ConnectionReady_Handler);
    AcceptConnection = new WaitCallback(AcceptConnection_Handler);
    ReceivedDataReady = new AsyncCallback(ReceivedDataReady_Handler);
}                
public bool Start()
{    
    this._listener.Bind(new IPEndPoint(IPAddress.Any, this._port));
    this._listener.Listen(10000);
    this._listener.BeginAccept(ConnectionReady, null);    
}
// Check every 5 minutes for clients that have not send any message in the past 30 minutes
// MSG_RESTART is a command that the devices accepts to restart
private void CheckForBrokenConnections()
{
    foreach (var entry in this._connections)
    {
        ConnectionState conn = entry.Value;
        if (conn.ReconnectAttemptCount > 3)
        {
            DropConnection(conn);
            continue;
        }
        if (!conn.Connected || (DateTime.Now - conn.LastResponse).TotalMinutes > 30)
        {
            byte[] message = HexStringToByteArray(MSG_RESTART);
            if (!conn.WaitingToRestart && conn.Write(message, 0, message.Length))
            {
                conn.WaitingToRestart = true;                    
            }
            else
            {
                DropConnection(conn);                
            }
        }
    }        
}

private void ConnectionReady_Handler(IAsyncResult ar)
{    
    lock (thisLock)
    {
        if (this._listener == null)
            return;
        ConnectionState connectionState = new ConnectionState();
        connectionState.Connection = this._listener.EndAccept(ar);
        connectionState.Server = this;
        connectionState.Provider = (TcpServiceProvider)this._provider.Clone();
        connectionState.Buffer = new byte[4];
        Util.SetKeepAlive(connectionState.Connection, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME);
        int newID = (this._connections.Count == 0 ? 0 : this._connections.Max(x => x.Key)) + 1;
        connectionState.ID = newID;
        this._connections.TryAdd(newID, connectionState);
        ThreadPool.QueueUserWorkItem(AcceptConnection, connectionState);
        this._listener.BeginAccept(ConnectionReady, null);
    }
}
private void AcceptConnection_Handler(object state)
{    
    ConnectionState st = state as ConnectionState;
    st.Provider.OnAcceptConnection(st);
    if (st.Connection.Connected)
        st.Connection.BeginReceive(st.Buffer, 0, 0, SocketFlags.None, ReceivedDataReady, st);    
}
private void ReceivedDataReady_Handler(IAsyncResult result)
{
    ConnectionState connectionState = null;
    lock (thisLock)
    {
        connectionState = result.AsyncState as ConnectionState;
        connectionState.Connection.EndReceive(result);
        if (connectionState.Connection.Available == 0)
            return;
        // Here the message is parsed
        connectionState.Provider.OnReceiveData(connectionState);
        if (connectionState.Connection.Connected)
            connectionState.Connection.BeginReceive(connectionState.Buffer, 0, 0, SocketFlags.None, ReceivedDataReady, connectionState);
    }
}
internal void DropConnection(ConnectionState connectionState)
{
    lock (thisLock)
    {
        if (this._connections.Values.Contains(connectionState))
        {
            ConnectionState conn;
            this._connections.TryRemove(connectionState.ID, out conn);
        }
        if (connectionState.Connection != null && connectionState.Connection.Connected)
        {
            connectionState.Connection.Shutdown(SocketShutdown.Both);
            connectionState.Connection.Close();
        }
    }
}

C#TCP服务器停止接收客户端消息,在服务重新启动时恢复

认为我看到的两件事。。。

  • 如果这是一个为多条消息保留的连接,那么当connectionState.Connection.Available == 0 IIRC可以接收到0长度的数据包时,您可能不应该从ReceivedDataReady_Handler返回。因此,如果连接仍然打开,那么在离开处理程序之前应该调用connectionState.Connection.BeginReceive( ... )

  • (我不太愿意把这个放在这里,因为我不记得具体细节了)当底层连接发生问题时,你可以处理一个事件,告诉你,包括连接或关闭连接的错误和故障。我一辈子都记不起这些名字。。。这可能比每隔几秒钟一次的计时器更有效率。它还为您提供了一种打破处于连接或关闭状态的连接的方法。

在所有IO调用周围添加try/catch块,并将错误写入日志文件。事实上,它无法在出现错误时恢复。

另外,要小心任何没有超时的锁。这些操作应该有一个合理的TTL。

我经历过很多次这种情况。问题可能根本不在于你的代码,而在于网络和Windows(两端)或路由器处理网络的方式。经常发生的情况是,临时网络中断会"断开"套接字,但Windows没有意识到这一点,因此不会关闭套接字。

克服这一问题的唯一方法正是你所做的——发送保持活力和监控连接健康状况。一旦你意识到连接断开,你就需要重新启动它。然而,在你的代码中,你不会重新启动侦听器套接字,它也断开了,不能接受新的连接。这就是为什么重新启动服务会有所帮助,它会重新启动侦听器。