当不同的客户端(连接到同一端口)发送数据时,服务器端 TcpClient 的 stream.read 未获取任何数据

Server side TcpClient's stream.read gets no data when a different client (connected to the same port) sends data

我在 C# 中有一个服务器端应用程序 运行 它应该从连接到同一端口的两个不同客户端接收数据(我希望能够将数据发送到一个客户端走)。我有一个 while(true) 循环,它接受一个带有 TcpListener 对象的 tcp 客户端。当获得一个时,我启动一个新线程来处理这个客户端(这是绝对必要的吗?我真的很难理解 when/where 使用异步、不同的线程等)。问题是,虽然在一个客户端上一切正常,但当我从一个也连接的新客户端发送消息时,我得到的第一组字节是完全空的,尽管 networkStream.read 函数确实得到处理,因为我的windows 表单确实输出了一些文本,只是带有一个空字节数组。

这似乎发生的方式是,当第二个客户端第一次发送数据时,服务器将什么也得不到,然后它就像切换到那个客户端一样,因为下一条消息和所有其他消息之后都很好。回显也可以正常工作,除了不执行消息传递的客户端不会收到回显。事实上,当第二个客户端发送它的第一条(结束为空白)消息时,第一个客户端会收到回声,此后不会再收到任何回声(对于以下成功的消息)。空白消息尝试不会将回显发送回空白消息的发件人,但其他信使确实收到了,但只有一次。

它基本上似乎在客户端之间来回切换。

我看到在我的接收处理程序的 while(true) 循环的开头有 networkstream=client.getstream() ,所以出现问题是有道理的,但我不确定如何重组我的代码以解决这个问题。很明显,我对网络流对象一遍又一遍地重新分配来自不同线程的不同流有问题,然后这也指出了我有一个缺陷

我已经尝试了很多不同的调试方法,但现在我很清楚,在仔细阅读了代码之后,我需要想出一种方法,让不同的客户端使用同一端口,而无需重新分配networkStream 对象到不同客户端的 .streamRead 函数(新客户端正在从不同线程处理)

public class Handler
{
    string portNumber;
    TcpClient Client;
    TcpListener port;
    TextBox textbox;
    TextBox sendbox;
    Task thisTask;
    int clientnumber=0;
    CancellationTokenSource cts;
    Button button;
    Form1 form1;
    public const int BufferSize = 1024;
    public byte[] buffer = new byte[BufferSize];
    byte[] sendbytes = new byte[BufferSize];
    NetworkStream networkStream;
    public StringBuilder sb = new StringBuilder();
    public Handler(IPAddress ip, int portnum, string portnumber, Form1 
form)
    {
        port = new TcpListener(ip, portnum);
        port.Start();
        form1 = form;
        portNumber = portnumber;
        foreach (Control t in form1.Controls) if (t.Name == portnumber + 
"chatbox") textbox = (TextBox)t;
        foreach (Control s in form1.Controls) if (s.Name == portnumber + 
"sendbox") sendbox = (TextBox)s;
        foreach (Control b in form1.Controls) if (b.Name == "button" + 
portnumber[4]) button = (Button)b;
        button.Click += SendData;
        cts = new CancellationTokenSource();
        thisTask = Task.Run(listen, cts.Token);
    }
    private async Task listen()
    {
        try
        {
            while (true)
            {
            Client = await port.AcceptTcpClientAsync();
            // Start a thread to handle this client...
            SetText("A new client " + ++clientnumber + " has connected to 
" + portNumber + "\r\n");

            new Thread(() => HandleClient(Client,clientnumber)).Start();
            }

        }
        catch (OperationCanceledException) when 
(cts.Token.IsCancellationRequested)
        {
            //ignore this ex
        }
    }

    delegate void SetTextCallback(string text);

    public void HandleClient(TcpClient Client, int cnum)
    {
        while (true)
        {
            try
            {
                networkStream = Client.GetStream();
                Client.ReceiveBufferSize = 1024;
                networkStream.Read(buffer, 0, BufferSize);
                SetText("\r\n Client " + cnum.ToString() + " sent: " + 
Encoding.ASCII.GetString(buffer) + "\r\n");
                sendbytes = Encoding.ASCII.GetBytes(portNumber + " has 
processed a message from client " + cnum.ToString() + ".");
                networkStream.Write(sendbytes, 0, sendbytes.Length);
                networkStream.Flush();
                buffer = new byte[BufferSize];
            }
            catch (Exception ex)
            {
                SetText("\r\n\r\n" + ex.ToString());
            }
        }
    }

    private void SetText(string text)
    {
        if (textbox.InvokeRequired)
        {
            SetTextCallback d = new SetTextCallback(SetText);
            textbox.Invoke(d, new object[] { text });
        }
        else textbox.Text += text;
    }

我遗漏的主要内容是我使用中间人将数据从客户端传递到服务器。中间人基本上是来自 github 的 websockify.js,但我认为他们从那时起已经做了一些更新。当客户端访问某个 web 地址时,websockify 会将 websocket 连接到该客户端,并查看指定端口是否有 TcpListener,如果有,它将为该 TcpConnection 的处理程序分配一堆事件发射器到服务器,命名为 "target"。像这样: target.on('数据,味精){client.send(data.toString());}

我认为,如果我从目标获取数据,那么为两个客户端设置的 .on 事件发射器都会被触发,并且两个客户端都会获取数据,所以我很困惑两个客户端如何设法不从服务器返回回声。

第一次回复后的下一次尝试:

public class Handler
{
    string portNumber;
    List<TcpClient> Clients = new List<TcpClient>();
    TcpListener port;
    TextBox textbox;
    TextBox sendbox;
    Task thisTask;
    int clientnumber=0;
    CancellationTokenSource cts;
    Button button;
    Form1 form1;
    public const int BufferSize = 1024;
    public StringBuilder sb = new StringBuilder();
    public Handler(IPAddress ip, int portnum, string portnumber, Form1 form)
    {
        port = new TcpListener(ip, portnum);
        port.Start();
        form1 = form;
        portNumber = portnumber;
        foreach (Control t in form1.Controls) if (t.Name == portnumber + 
     "chatbox") textbox = (TextBox)t;
        foreach (Control s in form1.Controls) if (s.Name == portnumber + 
    "sendbox") sendbox = (TextBox)s;
        foreach (Control b in form1.Controls) if (b.Name == "button" + 
    portnumber[4]) button = (Button)b;
        button.Click += SendData;
        cts = new CancellationTokenSource();
        thisTask = Task.Run(listen, cts.Token);
    }
    private async Task listen()
    {
        try
        {
            while (true)
            {
                var dummyclient = await port.AcceptTcpClientAsync();
                Clients.Add(dummyclient);
                // Start a thread to handle this client...
                SetText("A new client " + clientnumber++ + " has connected 
        to " + portNumber + "\r\n");
                new Task(() => HandleClient(dummyclient, 
        clientnumber)).Start();
            }

        }
        catch (OperationCanceledException) when 
       (cts.Token.IsCancellationRequested)
        {
            //ignore this ex
        }
    }

    public void HandleClient(TcpClient client, int cnum)
    {
        client.ReceiveBufferSize = 1024;
        var networkStream = client.GetStream();
        while (true)
        {
            try
            {
                var sendbytes = new byte[BufferSize];
                var buffer = new byte[BufferSize];
                networkStream.Read(buffer, 0, BufferSize);
                SetText("\r\n Client " + cnum.ToString() + " sent: " +                                         
                Encoding.ASCII.GetString(buffer) + "\r\n");
                sendbytes = Encoding.ASCII.GetBytes(portNumber + " has                                         
                processed a message from client " + cnum.ToString() + ".");
                SendGlobally(sendbytes);                    
                networkStream.Flush();

            }
            catch (Exception ex)
            {
                SetText("\r\n\r\n" + ex.ToString());
            }
        }
    }

    delegate void SetTextCallback(string text);
    private void SetText(string text)
    {
        if (textbox.InvokeRequired)
        {
            SetTextCallback d = new SetTextCallback(SetText);
            textbox.Invoke(d, new object[] { text });
        }
        else textbox.Text += text;
    }

    public void SendData(object sender, EventArgs e)
    {
        foreach (TcpClient tc in Clients)
        {
            var networkStream = tc.GetStream();
            if (networkStream != null)
            {
                var sendbytes = Encoding.ASCII.GetBytes(sendbox.Text);
                SetText("\r\n Server sent: " + sendbox.Text + "\r\n");
                networkStream.Write(sendbytes, 0, sendbytes.Length);
                networkStream.Flush();
                sendbox.Text = "";
            }
            else SetText("\r\n A client has not connected to this port yet. 
            \r\n");
        }
    }

    public void SendGlobally(byte[] data)
    {
        foreach (TcpClient tc in Clients)
        {
            var networkStream = tc.GetStream();
            networkStream.Write(data, 0, data.Length);
            networkStream.Flush();
        }
    }

    public void close()
    {
        cts.Cancel();
        port.Stop();
        foreach (TcpClient tc in Clients)
        if (tc != null) { tc.Close(); }
        }
    }
}

竞争条件。您为每个客户端创建一个新线程,并使用 在线程之间共享的成员变量 receive/send 数据 from/to 该客户端。不要这样做。例如。尽管您正确地将 TcpClient 传递给了 HandleClient,但您在其中将其 NetworkStream 存储在一个成员变量中。现在完全有可能紧随其后的另一个客户端用自己的 NetworkStream 覆盖它。如果您有两个客户端,则有两个线程分别执行 HandleClient 并行 ,两个 reading/modifying 相同的成员变量。

除非真正需要,否则不要在 HandleClient 中使用成员变量。通常,尽可能在本地存储数据。在你的情况下 - 本地变量。如果您确实需要在客户端之间共享一些数据,您可以为此使用成员变量,但您需要通过互斥来同步对它们的访问。