2013-02-15 210 views
1

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

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

我设法通过实现一个定时器来持续检查每个客户端是否仍然连接(见下面的代码)以减少问题。此外,我使用socket.IOControl(IOControlCode.KeepAliveValues, ...)方法向套接字添加了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(); 
     } 
    } 
} 
+0

CheckForBrokenConnections如何触发? – MarcF 2013-02-15 21:59:26

+0

这是一个'System.Timers。定时器回调,我还没有发布启动它的代码。我稍后会发布代码。 – MarcusVinicius 2013-02-16 13:17:50

+0

您的代码几乎没有潜在的错误。对于示例,您试图修改您在迭代时使用的'ConcurrentDictionary'。这行代码是什么:ThreadPool.QueueUserWorkItem(AcceptConnection,connectionState)'?另外,你是如何定义'thisLock'?一个错误的锁定对象也会导致并发错误。 – YavgenyP 2013-02-27 12:07:13

回答

2

2的事情,我觉得看看...

  • 如果这是你保持多条消息的连接,你或许不应该从ReceivedDataReady_HandlerconnectionState.Connection.Available == 0 IIRC长度为0返回数据库可以被接收。因此,如果连接仍处于打开状态,则应在离开处理程序之前调用connectionState.Connection.BeginReceive(...)

  • (我不愿意把它放在这里,因为我不记得具体细节)你可以处理的事件告诉你什么时候发生了你的底层连接,包括连接或关闭连接的错误和失败。对于我的生活,我不记得名字(s)...这可能比每隔几秒计时器更有效率。它还为您提供了一种突破连接或关闭状态中的连接的方法。

+0

感谢您的提示。我会在网上搜索你引用的事件。如果它帮助我,你赢得赏金。 – MarcusVinicius 2013-02-22 02:36:47

1

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

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

+0

在我的真实代码中,无处不在的try/catch块以及Windows事件查看器中的消息日志记录。我从这里发布的代码中删除了它们,使它更具可读性和紧凑性。 – MarcusVinicius 2013-02-28 18:31:51

1

我经历过很多次这种情况。问题可能与您的代码完全不同,除了网络以及Windows(两端)或路由器处理网络的方式。经常发生的情况是临时网络中断会“中断”套接字,但Windows并不知道它,所以它不会关闭套接字。

解决此问题的唯一方法就是您所做的 - 发送保持连接并监视连接健康状况。一旦您发现连接断开,您需要重新启动它。但是,在您的代码中,您不会重新启动侦听器套接字,该套接字也已损坏,并且无法接受新的连接。这就是为什么重新启动服务有助于重启监听器的原因。

相关问题