【问题标题】:Server side TcpClient's stream.read gets no data when a different client (connected to the same port) sends data当不同的客户端(连接到同一个端口)发送数据时,服务器端 TcpClient 的 stream.read 没有数据
【发布时间】:2019-12-09 05:38:53
【问题描述】:

我有一个在 C# 中运行的服务器端应用程序,它应该从连接到同一端口的两个不同客户端接收数据(我希望能够一次性向两个客户端发送数据)。我有一个 while(true) 循环,它接受一个带有 TcpListener 对象的 tcp 客户端。当一个被收购时,我开始一个新线程来处理这个客户端(这绝对有必要吗?我真的很难理解何时/何地使用异步、不同的线程等)。问题是,虽然在一个客户端上一切正常,但当我从一个也连接的新客户端发送消息时,我得到的第一组字节完全是空的,尽管 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,但我认为他们从那时起已经做了一些更新。当客户端访问某个网址时,websockify 会将 websockets 连接到该客户端,并查看指定端口是否有 TcpListener,如果有,它将为该 TcpConnection 到服务器的处理程序分配一组事件发射器,命名为“目标”。像这样: target.on('data,msg){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(); }
        }
    }
}

【问题讨论】:

  • 您需要知道,当两个(此处的)客户端连接到服务器时,这两个连接是完全不同的——您不应该在它们之间共享TcpClient 实例。此外,不要在循环内获取网络流并在客户端上设置属性 - 在循环外预先执行一次。

标签: c# multithreading asynchronous tcpclient


【解决方案1】:

比赛条件。您为每个客户端创建一个新线程,并使用线程之间共享的成员变量从/向该客户端接收/发送数据。不要这样做。例如。尽管您正确地将TcpClient 传递给HandleClient,但在其中您将其NetworkStream 存储在成员变量中。现在完全有可能在此之后另一个客户端立即用自己的NetworkStream 覆盖它。如果您有两个客户端,则有两个线程分别执行HandleClient并行,都读取/修改相同的成员变量。

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

【讨论】:

  • 啊!谢谢安德烈!我已经上传了我完成的 Handler 类,它现在工作正常。我在两个客户端都得到了回声(在写入该流之前,循环遍历客户端列表和这些客户端上的 getstream)。我现在也没有我开始的问题。不仅网络流被一遍又一遍地重新分配,缓冲区也是一个成员变量,所以直到我创建了两个本地变量,问题才得以解决。
  • 对其他人来说清楚的是,字节 [] 缓冲区对象是一个成员变量(不是线程本地的),它把整个事情都扔掉了。不是网络流对象,它似乎可以是成员变量。也就是说,除非两个网络流同时写入。如果中间有足够的时间没问题,但如果他们写得非常快,或者同时他们可能会互相踩踏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-09
  • 2013-05-27
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
  • 2017-02-12
  • 2023-04-08
相关资源
最近更新 更多