C# SocketException 无法连接,因为目标机器已主动拒绝连接

C# SocketException Can not connect because the target machine has actively declined the connection

我正在尝试在本地用 c# 编写聊天 client/server 以熟悉套接字。

首先,我使用(非常简单的)以下代码启动服务器:

Server.cs

    private readonly MessageManager _messageManager;
    private readonly ChatServer _chatServer;

    public ChatServerSkeleton()
    {
        _messageManager = new MessageManager();
        _chatServer = new ChatServer();
        Console.WriteLine("Server is running on: " + _messageManager.MyAddress);
    }

然后我用 +- 相同的方式启动客户端,除了我将服务器地址存储在客户端中(我将服务器地址复制到提示符中)。

Client.cs

    private readonly MessageManager _messageManager;
    public ChatClient ChatClient { get; }

    public ChatClientSkeleton(IPEndPoint serverAddress, string name)
    {
        _messageManager = new MessageManager();
        ChatClient = new ChatClient(new ChatServerStub(serverAddress, _messageManager), name);

        Console.WriteLine($"IPAddress of {name} is: {_messageManager.MyAddress}");
        Console.WriteLine($"IPAddress of Server is: { serverAddress}");
    }

MessageManager.cs

    private readonly TcpListener _serverSocket;
    public IPEndPoint MyAddress { get; }

    public MessageManager()
    {
        try
        {
            //Create server socket on random port
            _serverSocket = new TcpListener(IPAddress.Any, FindFreeTcpPort());

            //Get host ip address
            IPAddress[] localIps = Dns.GetHostAddresses(Dns.GetHostName());
            IPAddress localhost = localIps.First(ip => ip.AddressFamily == AddressFamily.InterNetwork);

            //Get port of serversocket
            IPEndPoint ipEndPoint = _serverSocket.LocalEndpoint as IPEndPoint;
            int port = ipEndPoint.Port;

            //Create address
            MyAddress = new IPEndPoint(localhost, port);
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("Something went wrong with the serversocket:");
            Console.Error.WriteLine(ex);
        } 
    }

FindFreeTcp 端口来自这里:

到目前为止一切似乎都有效。假设服务器现在有 192.168.0.219:51080 和客户端 192.168.0.219:51085.

当我尝试使用 MessageManager.cs

中的此代码发送消息时出现问题
    public void Send(MethodCallMessage message, IPEndPoint address)
    {
        try
        {
            _serverSocket.Start();
            TcpClient destination = new TcpClient(address.Address.ToString(), address.Port);
            NetworkStream output = destination.GetStream();
            MessageReaderWriter.Write(message, output);
            destination.Close();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("Failed to write a message:");
            Console.Error.WriteLine(ex);
        }
        finally
        {
            _serverSocket.Stop();
        }
    }

_server.Start(); 行更具体。

有人知道我做错了什么吗?

提前致谢!

编辑: 在服务器上注册客户端时,运行 没问题 1 次。但是在那之后,如果我想发送一条消息,我会得到目标机器主动拒绝的 SocketException。

我在这里使用 AcceptTcpClient():

MessageManager.cs

    public MethodCallMessage WReceive()
    {
        MethodCallMessage result = null;

        try
        {
            //_serverSocket.Start();
            TcpClient client = _serverSocket.AcceptTcpClient();
            NetworkStream input = new NetworkStream(client.Client, true);
            result = MessageReaderWriter.Read(input);
            client.Close();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine("Failed to receive a message:");
            Console.Error.WriteLine(ex);
        }
        finally
        {
            //_serverSocket.Stop();
        }

        return result;
    }

该方法在ServerSkeleton & ClientSkeleton中使用如下:

    public void Run()
    {
        while (true)
        {
            MethodCallMessage request = _messageManager.WReceive();
            HandleRequest(request);
        }
    }

所以流程基本如下:

  1. 我启动服务器(实例化新的消息管理器,第三个片段 和 运行 serverskeleton(最后一个片段)
  2. 服务器打印IP 控制台,我复制 ip 并启动​​客户端
  3. 实例化客户端& 将服务器 ip 设置为我复制粘贴的
  4. 启动客户端框架(最后 片段)

已调用 Start() 的 TcpListener 侦听传入连接,然后将它们堆叠在队列中。一旦该队列已满,就会产生套接字异常。要从队列中删除连接,您需要使用 TcpListener 的 AcceptTcpClient or AcceptSocket 方法。这将为您提供一个可以发送和接收数据的连接。

我猜可能发生的情况是,您收到了第一个传入的客户端,但不接受并删除它以发送和接收数据,并且您的后续连接被拒绝,因为待定队列列表已满(这是只是一个猜测)。

有一个重载方法...TcpListener.Start(int backlog)...允许您设置挂起队列列表的大小(因此您可以有 5 个、10 个或更多的连接等待在TcpListener)

使用服务器 TCP 套接字的过程是将其设置为侦听本地地址和端口。然后客户端尝试连接到该端点。当他们连接时,TCP 侦听套接字接受连接,然后将其传递给套接字,该套接字是在其上传输数据的套接字。侦听套接字继续侦听新连接,它本身不传输数据。

我希望这是有道理的?

所以服务器会表现得更像这样...

_serverSocket.Start();
TcpClient myAcceptedConnection = _serverSocket.AcceptTcpClient();
// in synchronous blocking socket situation the program flow halts 
// here til a connection is established
// once you have a connection ... do stuff with myAcceptedConnection

如果您希望避免阻塞情况,您可以使用 TcpListener.Pending() 查看是否有任何连接在队列中等待

编辑 1:

好的,所以我看到的唯一奇怪的事情是您在 MessageSend 方法中调用了 _serverSocket.Start() 方法?服务器通常不会通过发送消息开始......它通常等待监听连接,接收并读取连接然后回复(或者它可能在连接时发送问候语等)。

就我个人而言,我会将服务器的监听方面与数据的发送和接收分开...将它放在自己单独的方法中,毕竟你希望你的服务器一直监听传入的连接,直到你关闭它.当您检测到传入连接(可能通过循环检查 Pending())时,您可以接受它并在新的 TcpClient 上发送和接收。当您完成该客户端上的任何数据后 transmitting/receiving 您可以将其关闭,如果这就是您想要的......您不需要每次发送消息时都关闭和打开 tcp 连接,您可以在你完成它之前保持打开状态,事实上打开和关闭 tcp 连接会在它们通过的握手协议中产生一些开销。

不过有一些注意事项……Tcp 连接可能会变成 "half open",尤其是无线连接,这可能会导致问题。进入这里有点复杂,但我推荐 Stephen Cleary 的这套精彩文章作为通读。阅读整个博客,因为里面有很多有用的信息。

所以,回到简单的,我会...

  1. 一个 serverStart() 方法,您可以在其中启动服务器侦听。
  2. 一个 serverAccept() 方法,您可以在其中检查是否有任何挂起的连接并在存在时接受它们。
  3. 连接到服务器的客户端的 clientConnect() 方法
  4. 进行数据传输的服务器和客户端的 Read() 和 Write() 方法。

正常情况下,流量是...

  1. 服务器侦听
  2. 客户端连接
  3. 服务器接受
  4. 客户端发送
  5. 服务器接收
  6. (然后服务器 sends/receives, 客户端 sends/receives)
  7. 一切都关闭并关闭