14

当我的程序启动时,我有一个连接到端点的以下方法

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

我还有一个计时器,它设置为每 60 秒触发一次 call 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),我的 Reconnect 方法中的 ChannelSocket.Connect(ChannelEndPoint) 总是会抛出此错误

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

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

4

3 回答 3

30

如果应用程序关闭 TCP/IP 端口,则协议规定该端口保持TIME_WAIT状态一段时间(在 Windows 机器上默认为 24​​0 秒)。请参阅以下内容以获取参考 -

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

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

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

这对您的方案意味着什么- 您不能期望在短时间内(甚至几秒钟)关闭(愿意或不愿意)并重新打开端口。尽管您可以在互联网上找到一些注册表调整.. 该端口对于 Windows 上的任何应用程序都将不可用,至少 30 秒。(同样,默认为 24​​0 秒)

您的选择- 这里是有限的......

  1. http://msdn.microsoft.com/en-us/library/4xzx2d41(v=vs.110).aspx上的文档-

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

文档建议BeginConnect必须使用的原因是我上面提到的..它根本不希望能够立即建立连接..因此唯一的选择是异步进行调用,并且在您等待时连接将在几分钟内建立,请期待并计划它会失败。从本质上讲,可能不是一个理想的选择。

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

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

(PS - 仅供参考 - 对于 HTTP,操作系统内置了很多特殊处理,包括 windows - 例如,有一个专用驱动程序Http.sys,专门用于处理多个应用程序尝试在同一端口 80 上侦听等。详细信息here是另一个时间的话题..关键是,当谈到HTTP)

于 2014-12-02T07:49:06.733 回答
5

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

我将为这些网络连接使用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);
    }
}

此代码有效,并且不会在客户端丢失连接时产生任何问题。如果您在服务器端丢失了连接,则必须重新建立侦听器,但那是另一回事了。

于 2014-12-02T08:28:07.550 回答
3

在 ReconnectChannel 中,只需处理 ChannelSocket 对象。

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

这对我有用。让我知道它是否不适合你。

于 2017-11-29T04:41:23.200 回答