【问题标题】:C# TCP Server stop receiving client messages, resumes when service is restartedC# TCP Server 停止接收客户端消息,服务重启后恢复
【发布时间】:2013-03-01 05:34:25
【问题描述】:

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

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

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

我发布了一些我认为相关的特定部分的代码。但是,如果需要更多的 sn-ps 来理解问题,请询问我,我将编辑帖子。删除所有 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();
        }
    }
}

【问题讨论】:

  • CheckForBrokenConnections 方法是如何触发的?
  • 这是一个System.Timers.Timer 回调,我还没有发布启动它的代码。稍后我会发布代码。
  • 您的代码几乎没有潜在的错误。例如,您尝试修改您正在使用的ConcurrentDictionary,同时对其进行迭代。这条线路负责什么:ThreadPool.QueueUserWorkItem(AcceptConnection, connectionState)?另外,您是如何定义thisLock 的?错误的锁定对象也会导致并发错误。
  • 您是否运行过 netstat 或 TCPView 之类的工具来查看连接?即使您要求服务器保持套接字连接处于活动状态,这并不意味着客户端不会关闭连接/停止通话,然后为下一次调用打开一个新连接。我们遇到了类似的情况,仅在我们进行并行化时才开始,因为在很短的时间内客户端请求的数量成倍增加,并且他们的服务用完了可用的连接来提供,因为旧的/关闭的/放弃的连接还没有超时.
  • @YavgenyP 我将更改修改字典的代码。在这种情况下,QueueUserWorkItem 方法会将连接放入队列中,由方法AcceptConnection_Handler 处理,从而允许并行处理连接。锁被定义为私有对象,在类级别实例化。

标签: c# sockets tcp windows-services


【解决方案1】:

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

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

  • (我犹豫把它放在这里,因为我不记得具体细节)有一个事件可以告诉您底层连接发生的时间,包括连接或关闭连接的错误和失败。对于我的生活,我不记得名字......这可能比每隔几秒的计时器更有效。它还为您提供了一种方法来打破卡在连接或关闭状态的连接。

【讨论】:

  • 感谢您的提示。我会在网上搜索你引用的事件。如果它对我有帮助,您将赢得赏金。
【解决方案2】:

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

此外,请注意任何没有超时的锁。应该给这些操作一个合理的 TTL。

【讨论】:

  • 在我的真实代码中,到处都有 try/catch 块,Windows 事件查看器中也有消息记录。我从此处发布的代码中删除了它们,以使其更具可读性和紧凑性。
【解决方案3】:

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

克服这个问题的唯一方法就是您所做的 - 发送保持活动状态并监控连接运行状况。一旦您发现连接已断开,您需要重新启动它。但是,在您的代码中,您不会重新启动也已损坏且无法接受新连接的侦听器套接字。这就是重启服务有帮助的原因,它会重启监听器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 2017-09-02
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    相关资源
    最近更新 更多