1

私は、C# で記述されたマネージド Windows サービスで作業しています。TCP/IP 経由で接続された複数のクライアントからメッセージを受信し続けます。クライアントは基本的に、温度計からサーバーへのメッセージを受信および再送信するルーターです。サーバーはメッセージを解析し、S​​QL Server データベースに保存します。

私が直面している問題は、一部のクライアントが突然メッセージの送信を停止することです。ただし、サービスが再開されるとすぐに、再び接続して送信を再開します。クライアントはサードパーティのデバイスであるため、クライアントのコードはありません。問題はサーバーにあると確信しています。

各クライアントがまだ接続されているかどうかをチェックし続けるタイマーを実装することで、問題を軽減することができました (以下のコードを参照)。また、メソッドを使用してキープアライブモードをソケットに追加しましたsocket.IOControl(IOControlCode.KeepAliveValues, ...)が、問題はまだ発生しています。

関連すると思われる特定の部分からいくつかのコードを投稿しています。ただし、問題を理解するためにさらにスニペットが必要な場合は、私に尋ねてください。投稿を編集します。コードの量を減らすために、すべての 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();
        }
    }
}
4

3 に答える 3

2

私が見ていると思う2つのこと...

  • これが複数のメッセージに対して保持する接続である場合ReceivedDataReady_HandlerconnectionState.Connection.Available == 0 IIRC が 0 の長さのデータ パケットを受信できるようになった時点から戻るべきではありません。したがって、接続がまだ開いている場合はconnectionState.Connection.BeginReceive( ... )、ハンドラーを終了する前に呼び出す必要があります。

  • (詳細を覚えていないので、ここに置くのをためらっています)エラーや接続の失敗、接続の失敗など、基礎となる接続にいつ何が起こったのかを知らせる、処理できるイベントがあります。私の人生では、名前を思い出せません...これは、数秒ごとのタイマーよりも効率的です。また、接続中または終了状態でスタックしている接続から抜け出す方法も提供します。

于 2013-02-22T00:02:50.123 に答える
1

すべての IO 呼び出しに try/catch ブロックを追加し、エラーをログ ファイルに書き込みます。このままでは、エラーで回復できません。

また、タイムアウトのないロックには注意してください。これらの操作には、妥当な TTL を指定する必要があります。

于 2013-02-28T18:20:22.267 に答える
1

私はこのような状況を何度も経験しました。問題はおそらくあなたのコードではなく、ネットワークと、Windows (両端) またはルーターがネットワークを処理する方法にあります。よくあることは、一時的なネットワークの停止によってソケットが「壊れる」ことですが、Windows はそれを認識しないため、ソケットを閉じません。

これを克服する唯一の方法は、キープアライブを送信し、接続の状態を監視することです。接続がダウンしていることを認識したら、再起動する必要があります。ただし、コードでは、壊れているリスナーソケットを再起動せず、新しい接続を受け入れることができません。そのため、サービスを再起動するとリスナーが再起動されます。

于 2013-02-28T21:34:25.087 に答える