【问题标题】:TcpClient only connects if server starts listening before ConnectAsync calledTcpClient 仅在服务器在调用 ConnectAsync 之前开始侦听时才连接
【发布时间】:2020-06-25 15:43:59
【问题描述】:

我有一个基本的 TCP 客户端和服务器都在我的机器上本地运行。

如果我调用 ConnectAsync() 时服务器已经在监听,那么客户端连接到服务器,没问题。

如果我启动客户端并调用 ConnectAsync(),那么在监听客户端之后启动服务器,它不会建立连接。 ConnectAsync 在我收到以下错误之前挂起大约 85 秒: System.Net.Sockets.SocketException (0x80004005): Connection refused。尽管服务器已经开始监听了。

不确定这是否会影响 TcpClient,但客户端正在 Xamarin 项目中运行。

这是我的代码:

客户:

public static class DataSource
{
    private static TcpClient client;
    private static NetworkStream networkStream;

    public static bool Connected => client is null ? false : client.Connected;

    public static async Task EstablishTcpConnection(string serverIP, int port)
    {
        CloseTcpConnection();

        try
        {
            client = new TcpClient();
            await client.ConnectAsync(IPAddress.Parse(serverIP), port);
            networkStream = client.GetStream();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{nameof(EstablishTcpConnection)} Error: {ex}");
        }
    }

    public static void CloseTcpConnection()
    {
        if (networkStream != null)
        {
            networkStream.Close();
            networkStream.Dispose();
            networkStream = null;
        }

        if (client != null)
        {
            client.Close();
            client.Dispose();
            client = null;
        }
    }

    public static async Task SendTcp(string toSend)
    {
        if (client is null) return;
        if (networkStream is null) return;
        if (!Connected) return;

        if (networkStream != null && networkStream.CanWrite)
        {
            byte[] bytesToSend = ASCIIEncoding.ASCII.GetBytes(toSend);
            await networkStream.WriteAsync(bytesToSend, 0, bytesToSend.Length);
        }
    }

    public static async Task TcpListener()
    {
        while (networkStream != null && networkStream.CanRead)
        {
            if (client is null) return;
            if (networkStream is null) return;
            if (!Connected) return;

            byte[] bytesToRead = new byte[client.ReceiveBufferSize];
            int bytesRead = await networkStream.ReadAsync(bytesToRead, 0, client.ReceiveBufferSize);
            string received = Encoding.ASCII.GetString(bytesToRead, 0, bytesRead);
            Console.WriteLine($"Received: {received}");
        }
    }
}

服务器:

internal class Program
{
    private const string serverIP = "MyServerIp";
    private const int port = myPort;

    private static void Main(string[] args)
    {
        Listener();
        Console.ReadLine();
    }

    public static async Task Listener()
    {
        //---listen at the specified IP and port no.---
        TcpListener listener = new TcpListener(IPAddress.Parse(serverIP), port);
        listener.Start();
        Console.WriteLine("Listening...");

        while (true)
        {
            //---incoming client connected---
            ReceiveClient(await listener.AcceptTcpClientAsync());
        }
    }

    public static async Task ReceiveClient(TcpClient client)
    {
        if (client is null) return;
        Console.WriteLine("Client Connected");

        //---get the incoming data through a network stream---
        NetworkStream networkStream = client.GetStream();
        byte[] buffer = new byte[client.ReceiveBufferSize];

        while (client != null && client.Connected)
        {
            //---read incoming stream---
            int bytesRead = await networkStream.ReadAsync(buffer, 0, client.ReceiveBufferSize);
            if (bytesRead == 0) break;

            //---convert the data received into a string---
            string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead);
            Console.WriteLine("Received : " + dataReceived);

            //---write back the text to the client---
            Console.WriteLine("Sending back : " + dataReceived);
            networkStream.Write(buffer, 0, bytesRead);
        }

        client.Close();
        client.Dispose();
        Console.WriteLine("Client Disconnected");
    }
}

【问题讨论】:

  • 不是网络专家,但这听起来对我来说是正确的行为。应该很容易使用 telnet 或网络工具进行测试,看看它是否以相同的方式响应。
  • 我想这很好(从网络的角度来看)。因此,您可能可以设置短客户端连接超时,但在客户端多次调用ConnectAsync(检查异常代码\类型并计算连接尝试以打破循环)
  • @oleksa 是的,我认为这可能是我必须要做的。烦人的是,TcpClient 的超时时间没有暴露,所以我什至无法设置它。
  • @DavidAndrewThorpe 您可以尝试使用ConnectAsync approach 设置连接“超时”

标签: c# xamarin tcpclient


【解决方案1】:

当然 ConnectAsync() 应该仍然可以工作,即使服务器在超时之后开始侦听?

没有;这不是“超时”的意思。 “超时”意味着“重试”。

当您建立连接时,您的客户端应用程序正在访问在服务器计算机上运行的服务器应用程序。 “超时”只是客户端应用程序等待响应的时间量。如果服务器计算机正在运行,但该端口没有侦听服务器应用程序,则服务器计算机将立即发送响应,指示没有服务器应用程序在运行。这将被发送回您的客户端应用程序。这是一个有效的“响应”,因此超时不会起作用。

如果您想重试连接,等待服务器,那么您需要自己编写该逻辑。

【讨论】:

  • 谢谢,很高兴知道,但这并不能回答我的问题。尽管服务器正在侦听,但 ConnectAsync 在返回连接被拒绝异常之前会挂起大约 85 秒。这里的关键细节是服务器在调用 ConnectAsync 之后并且在此方法返回连接被拒绝异常之前开始侦听。然而,连接尚未建立。
  • 我在您的客户端代码中没有看到超时。请发布一个最小的可重现示例。
  • 忘记提及超时。我提到它只是猜测。 TcpClient 不公开用于连接的超时属性。
【解决方案2】:

如果您在同一台计算机上运行这两个程序,则 ConnectAsync() 请求很可能在服务器初始化之前被拒绝。发生这种情况是因为没有进程监听传入端口。所以操作系统拒绝连接请求。

检查抛出的异常类型。如果是 SocketException,它的 SocketErrorCode 属性将更清楚地说明连接失败的原因。

Microsoft 网站https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2 上提供了错误代码及其描述的完整列表。

【讨论】:

  • 默认超时时间很长,服务器肯定会在超时之前开始监听。我得到的错误是:System.Net.Sockets.SocketException(0x80004005):连接被拒绝。但这很奇怪,就像我说的,如果服务器已经在运行,我不会收到此错误。
  • 如果连接被拒绝,说明请求端口上没有进程监听,或者有防火墙阻止了请求。我的建议是在尝试连接并报告之前确保服务器已启动并运行。
  • 如果连接被拒绝,超时时间无关紧要。如果客户端正在等待另一端的回复,超时就会发挥作用。拒绝连接也是可接受的响应,在这种情况下超时会被取消。
猜你喜欢
  • 1970-01-01
  • 2016-05-13
  • 2018-12-05
  • 1970-01-01
  • 2012-04-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多