如何正常重新连接到套接字

时间:2014-11-28 04:59:42

标签: c# .net sockets tcp-ip robustness

我有一个以下方法在程序启动时连接到终点

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

我还有一个定时器,设置为每60秒触发一次,以调用CheckConnectivity,尝试将任意字节数组发送到终点,以确保连接仍然存在,如果发送失败,它将尝试重新连接。

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),我的重新连接方法中的ChannelSocket.Connect(ChannelEndPoint)也会抛出此错误

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

如果我要重新启动整个应用程序,它会成功连接。我如何调整我的重新连接方法,以便我不必重新启动应用程序以重新连接回以太网设备?

3 个答案:

答案 0 :(得分:25)

如果应用程序关闭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 -
  2. 的文档

    " 如果套接字先前已断开连接,则无法使用此(Connect)方法恢复连接。使用其中一种异步BeginConnect方法重新连接。这是底层提供商的限制。"

    文档表明必须使用BeginConnect的原因就是我上面提到的......它根本不希望能够立即建立连接..因此唯一的选择是以异步方式进行调用,当您等待连接在几分钟内建立时,请确保并计划失败。 基本上,可能不是一个理想的选择。

    1. 如果漫长的等待和不确定性是不可接受的,那么您的另一个选择是以某种方式协商客户端和服务器之间的不同端口。 (例如,理论上您可以使用UDP,无连接来协商您重新建立连接的新TCP端口)。当然,理论上使用UDP的通信本身并不能保证设计。但是大部分时间都应该工作(今天,典型组织中的网络并不是那么片状/不可靠)。 主观的情景/意见,或许比选项1更好,但更多的工作和更小但有限的机会不工作。

    2. 正如其中一条评论所述,这就是http和http服务等应用层协议具有优势的地方。如果可以的话,使用它们而不是低级插座。 如果可以接受,这是最好的选择。

    3. (PS - 仅供参考 - 对于HTTP,操作系统内置了许多特殊处理,包括Windows - 例如,有一个专用驱动程序Http.sys,专门用于处理试图在其上收听的多个应用程序端口80等。这里的细节是另一个时间的主题..重点是,当谈到HTTP

      时,有很多善良和辛勤工作。

答案 1 :(得分:4)

也许你应该切换到更高的抽象类,这更好地处理所有这些漂亮的小细节?

我将在TcpListenerTcpClient类中使用这些网络连接。这些类的使用非常简单:

客户方:

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);
    }
}

此代码有效,并且不会在客户端丢失连接时出现任何问题。如果您在服务器端丢失连接,则必须重新建立监听器,但这是另一个故事。

答案 2 :(得分:1)

在ReconnectChannel中只需配置ChannelSocket对象。

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

这对我有用。如果它不适合你,请告诉我。