web-dev-qa-db-ja.com

正常にソケットに再接続する方法

プログラムの開始時にエンドポイントに接続する次のメソッドがあります

ChannelSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var remoteIpAddress = IPAddress.Parse(ChannelIp);
ChannelEndPoint = new IPEndPoint(remoteIpAddress, ChannelPort);
ChannelSocket.Connect(ChannelEndPoint);

また、CheckConnectivityを呼び出すために60秒ごとにトリガーするように設定されたタイマーがあります。このタイマーは、任意のバイト配列をエンドポイントに送信して、接続がまだ生きていることを確認し、送信が失敗した場合に、再接続を試みます。

public bool CheckConnectivity(bool isReconnect)
{
    if (ChannelSocket != null)
    {
        var blockingState = ChannelSocket.Blocking;
        try
        {
            var tmp = new byte[] { 0 };
            ChannelSocket.Blocking = false;
            ChannelSocket.Send(tmp);
        }
        catch (SocketException e)
        {
            try
            {
                ReconnectChannel();
            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }
    else
    {
        ConnectivityLog.Warn(string.Format("{0}:{1} is null!", ChannelIp, ChannelPort));
        return false;
    }

    return true;
} 

private void ReconnectChannel()
{
    try
    {
        ChannelSocket.Shutdown(SocketShutdown.Both);
        ChannelSocket.Disconnect(true);
        ChannelSocket.Close();
    }
    catch (Exception ex)
    {
        ConnectivityLog.Error(ex);
    }

    ChannelSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    var remoteIpAddress = IPAddress.Parse(ChannelIp);
    ChannelEndPoint = new IPEndPoint(remoteIpAddress, ChannelPort);
    ChannelSocket.Connect(ChannelEndPoint);
    Thread.Sleep(1000);

    if (ChannelSocket.Connected)
    {
        ConnectivityLog.Info(string.Format("{0}:{1} is reconnected!", ChannelIp, ChannelPort));
    }
    else
    {
        ConnectivityLog.Warn(string.Format("{0}:{1} failed to reconnect!", ChannelIp, ChannelPort));
    }
}

したがって、上記をテストする方法は、LANケーブルをイーサネットデバイスから物理的に取り外し、コードが再接続(明らかに失敗)し、LANケーブルを再接続できるようにすることです。

ただし、LANケーブルを再接続した後(ping可能)でも、ReconnectメソッドのChannelSocket.Connect(ChannelEndPoint)は常にこのエラーをスローします

No connection could be made because the target machine actively refused it 192.168.168.160:4001

アプリケーション全体を再起動すると、正常に接続されます。イーサネットデバイスに再接続するためにアプリケーションを再起動する必要がないように、再接続方法を微調整するにはどうすればよいですか?

14
Null Reference

アプリケーションがTCP/IPポートを閉じると、プロトコルはポートがTIME_WAIT一定の期間状態(Windowsマシンではデフォルトで240秒)。参考のために以下を参照してください-

http://en.wikipedia.org/wiki/Transmission_Control_Protocol

http://support.Microsoft.com/kb/137984

http://www.pctools.com/guides/registry/detail/878/

これがあなたのシナリオに意味すること-短時間でポートを閉じて(喜んでまたは思わず)再度開くことは期待できない(数秒です)。インターネットで見つけたレジストリの微調整にも関わらず、Windows上のどのアプリでも最低30秒間はポートが利用できなくなります。 (再度、デフォルトは240秒です)

あなたのオプション-ここには制限があります...

  1. http://msdn.Microsoft.com/en-us/library/4xzx2d41(v = vs.110).aspx のドキュメントから-

"ソケットが以前に切断されている場合、この(Connect)メソッドを使用して接続を復元することはできません。非同期のBeginConnectメソッドの1つを使用して再接続します。これは、基になるプロバイダーの制限です。

ドキュメントがBeginConnectを使用する必要があることを示唆している理由は、前述のとおりです。接続をすぐに確立できるとは期待できません。したがって、唯一のオプションは、非同期で呼び出しを行うことです。接続が数分で確立されるようにするには、失敗することを予期して計画してください。 本質的に、おそらく理想的なオプションではありません。

  1. 長い待機と不確実性が受け入れられない場合、他のオプションは、クライアントとサーバー間で別のポートを何らかの方法でネゴシエートすることです。 (たとえば、理論的には、connectionlessであるUDPを使用して、新しいTCPポートをネゴシエートすることができます。 d接続を再確立します。もちろん、理論的にはUDPを使用した通信自体は設計によって保証されていません。ただし、ほとんどの場合、正常に機能するはずです(今日、典型的な組織のネットワーキングは、不安定で信頼性が高くありません)。シナリオ/意見の主観的、おそらくオプション1より優れていますが、より多くの作業があり、機能しない可能性は小さいですが、有限の可能性があります。

  2. コメントの1つで示唆されているように、httpやhttpサービスなどのアプリケーションレイヤープロトコルには利点があります。可能であれば、低レベルのソケットの代わりにそれらを使用してください。 許容できる場合は、これが最適なオプションです

(PS-FYI-HTTPの場合、OSにはWindowsを含む多くの特別な処理が組み込まれています-たとえば、専用のドライバーHttp.sys、特に同じポート80でリッスンしようとする複数のアプリを処理する場合など。ここでの詳細は別のトピックのトピックです。重要なのは、HTTPに関しては、多くの善と努力が行われていることです。 )

27
Vikas Gupta

たぶん、これらの気の利いた小さな詳細すべてをより適切に処理する、より高い抽象化クラスに切り替える必要がありますか?

これらのネットワーク接続には TcpListener および TcpClient クラスを使用します。これらのクラスの使い方はとても簡単です:

クライアント側:

public void GetInformationAsync(IPAddress ipAddress)
{
    _Log.Info("Start retrieving informations from address " + ipAddress + ".");
    var tcpClient = new TcpClient();
    tcpClient.BeginConnect(ipAddress, _PortNumber, OnTcpClientConnected, tcpClient);
}

private void OnTcpClientConnected(IAsyncResult asyncResult)
{
    try
    {
        using (var tcpClient = (TcpClient)asyncResult.AsyncState)
        {
            tcpClient.EndConnect(asyncResult);
            var ipAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
            var stream = tcpClient.GetStream();
            stream.ReadTimeout = 5000;
            _Log.Debug("Connection established to " + ipAddress + ".");

            var formatter = new BinaryFormatter();
            var information = (MyInformation)formatter.Deserialize(stream);

            _Log.Info("Successfully retrieved information from address " + ipAddress + ".");
            InformationAvailable.FireEvent(this, new InformationEventArgs(information));
        }
    }
    catch (Exception ex)
    {
        _Log.Error("Error in retrieving informations.", ex);
        return;
    }
}

サーバー側:

public void Start()
{
    ThrowIfDisposed();

    if (_TcpServer != null;)
        _TcpServer.Stop();

    _TcpServer = new TcpListener(IPAddress.Any, _PortNumber);
    _TcpServer.Start();

    _TcpServer.BeginAcceptTcpClient(OnClientConnected, _TcpServer);
    _Log.Info("Start listening for incoming connections on " + _TcpServer.LocalEndpoint + ".");
}

private void OnClientConnected(IAsyncResult asyncResult)
{
    var tcpServer = (TcpListener)asyncResult.AsyncState;
    IPAddress address = IPAddress.None;

    try
    {
        if (tcpServer.Server != null
            && tcpServer.Server.IsBound)
            tcpServer.BeginAcceptTcpClient(OnClientConnected, tcpServer);

        using (var client = tcpServer.EndAcceptTcpClient(asyncResult))
        {
            address = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
            _Log.Debug("Client connected from address " + address + ".");

            var formatter = new BinaryFormatter();
            var informations = new MyInformation()
            {
                // Initialize properties with desired values.
            };

            var stream = client.GetStream();
            formatter.Serialize(stream, description);

            _Log.Debug("Sucessfully serialized information into network stream.");
        }
    }
    catch (ObjectDisposedException)
    {
        // This normally happens, when the server will be stopped
        // and their exists no other reliable way to check this state
        // before calling EndAcceptTcpClient().
    }
    catch (Exception ex)
    {
        _Log.Error(String.Format("Cannot send instance information to {0}.", address), ex);
    }
}

このコードは機能し、クライアント側の接続が失われた場合に問題を発生させません。サーバー側の接続が失われた場合、リスナーを再確立する必要がありますが、それは別の話です。

4
Oliver

ReconnectChannelでは、ChannelSocketオブジェクトを破棄するだけです。

try
    {
     `//ChannelSocket.Shutdown(SocketShutdown.Both);
        //ChannelSocket.Disconnect(true);
        //ChannelSocket.Close();
        ChannelSocket.Dispose();`   
    }

これは私のために働いています。うまくいかない場合はお知らせください。