2013-02-15 208 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並不知道它,所以它不會關閉套接字。

解決此問題的唯一方法就是您所做的 - 發送保持連接並監視連接健康狀況。一旦您發現連接斷開,您需要重新啓動它。但是,在您的代碼中,您不會重新啓動偵聽器套接字,該套接字也已損壞,並且無法接受新的連接。這就是爲什麼重新啓動服務有助於重啓監聽器的原因。

相關問題