【问题标题】:Client not receiving data from Server Multithreading客户端未从服务器多线程接收数据
【发布时间】:2016-03-26 16:32:23
【问题描述】:

我想聊天。服务器是在控制台应用程序中制作的,客户端是在 winforms 中制作的。

在客户端我写了一个昵称并连接到服务器。服务器从客户端接收名称。我使用 (string)name(TcpClient)Socket 将所有连接到服务器的客户端添加到 Dictionary 列表中。之后,我想向每个客户发送客户列表。

当我在服务器上调试时,套接字出现 DualMode,EnableBroadcast 错误。在客户端,当我必须接收列表时它会停止并且不执行任何操作。

服务器

namespace MyServer
{
    class MyServer
    {
        public Dictionary<string, TcpClient> clientList = new Dictionary<string, TcpClient>();
    TcpListener server = null;
    NetworkStream stream = null;
    StreamReader streamReader = null;
    StreamWriter streamWriter = null;
    TcpClient clientSocket;

    String messageReceived;
    int number_clients = 0;
    public MyServer(TcpClient clientSocket_connect)
    {
        stream = clientSocket_connect.GetStream();
        streamReader = new StreamReader(stream);
        streamWriter = new StreamWriter(stream);

        receiveMessage(clientSocket_connect); // receive messages
        }
        public MyServer()
        {
            Thread thread = new Thread(new ThreadStart(run));
            thread.Start();
        }           
        public void receiveMessage(TcpClient client_Socket)
        {
            messageReceived = streamReader.ReadLine();             

            if (messageReceived.Substring(messageReceived.Length - 4) == "user")
            {
                String name = messageReceived.Substring(0, messageReceived.Length - 4);
                bool found = false;
                foreach (var namefound in clientList.Keys)
                {
                    if (namefound == name)
                    {
                        found = true;
                        streamWriter.WriteLine("The user already exists");
                        streamWriter.Flush();
                    }
                }
                if (!found)
                {
                    //show who's connected
                    Console.WriteLine(name + " is online");
                    number_clients++;
                    clientList.Add(name, client_Socket);

                    //send to client clientlist
                    String send = null;
                    foreach (var key in clientList.Keys)
                    {
                        send += key + ".";
                    }
                    foreach (var value in clientList.Values)
                    {
                        TcpClient trimitereclientSocket = value;
                        if (trimitereclientSocket != null)
                        {
                            NetworkStream networkStream = trimitereclientSocket.GetStream();
                            StreamWriter networkWriter = new StreamWriter(networkStream);
                            networkWriter.WriteLine(send + "connected");
                            networkWriter.Flush();
                        }
                    }
                }
            }

        }
        void run()
        {
            IPAddress ipAddress = IPAddress.Parse("127.0.0.1");
            server = new TcpListener(ipAddress, 8000);
            server.Start();
            Console.WriteLine("Server started!");
            while (true)
            {
                clientSocket = server.AcceptTcpClient();                
                new MyServer(clientSocket);
            }
        }
    }
static void Main(string[] args)
        {
            MyServer server = new MyServer();
        }
}

客户

 namespace MyClient
    {
        class MyClient
        {
            List<string> clientList = new List<string>();

            TcpClient client = null;
            NetworkStream stream = nul

l;
        StreamReader streamReader = null;
        StreamWriter streamWriter = null;

        bool connected;
        String received_message;
        public MyClient()
        {
            client = new TcpClient("127.0.0.1", 8000);
            stream = client.GetStream();
            streamReader = new StreamReader(stream);
            streamWriter = new StreamWriter(stream);     
        }
        public void sendClientName(String name)
        {
            streamWriter.WriteLine(Convert.ToString(name));
            streamWriter.Flush();
        }
        public List<ClientName> receiveClientList()
        {
            List<ClientName> val = new List<ClientName>();         
                string name = Convert.ToString(streamReader.ReadLine());
                if (name.Substring(0, name.Length - 9) == "connected")
                {
                    ClientName client = new ClientName();
                    client.Nume = name;
                    val.Add(client);
                }          
            return val;
        }

    }
}

客户表格

 public partial class Form1 : Form
{
    MyClient client = new MyClient();
    public Form1()
    {
        InitializeComponent();
        Thread receiveClients = new Thread(new ThreadStart(getMessages));
    }

    private void btnConnect_Click(object sender, EventArgs e)
    {
        client.sendClientName(txtNickname.Text + "user");
    }
    public void getMessages()
    {
        while (true)
        {
            lbClientsConnected.Items.Add(client.receiveClientList());
        }
    }
}

【问题讨论】:

  • 目前由于您的服务器似乎只使用一个StreamReader,因此只能从最新的客户端接收消息。您应该创建一个自定义类,将其添加到包含客户端的 TcpClientStreamReader/...WriterNetworkStream 的 Dictionary 中。
  • 主要问题是为每个客户端创建了一个新的服务器对象实例。可以接收所有客户端消息,但是 a) 一次只能接收一个客户端,b) 一旦接收到一个客户端的第一行文本,就不会再收到该客户端的消息,以及 c) 每个客户端都不知道其他任何消息。我在代码中看不到任何明显解释报告错误的内容(无论如何只是模糊地描述)。我同意前面的评论,您需要修复代码,以便单个对象跟踪所有客户端;当前的设计从根本上被破坏了。

标签: c# multithreading sockets tcpclient


【解决方案1】:

运行您的代码时,我无法重现任何错误。我不知道您所说的 “套接字出现 DualMode,EnableBroadcast 错误”是什么意思。也就是说,代码存在许多可修复的问题,包括一些与您的担忧直接相关的问题,即“当我必须收到列表时它会停止并且什么也不做。” p>

代码的最大问题可能是您根本不启动客户端的接收线程。您需要在 Thread 对象创建后调用 Start() 方法:

public Form1()
{
    InitializeComponent();
    Thread receiveClients = new Thread(new ThreadStart(getMessages));

    // The receiving thread needs to be started
    receiveClients.Start();
}

现在,即使解决了这个问题,您还有一些其他问题。下一个大问题是您错误地解析了接收到的文本。在您的代码中,您应该在字符串末尾查找文本 "connected",而是提取文本的另一部分(带有客户端名称列表)。

您的 receiveClientList() 方法应如下所示:

private const string _kconnected = "connected";

public List<string> receiveClientList()
{
    List<string> val = new List<string>();
    string name = Convert.ToString(streamReader.ReadLine());

    // Need to check the *end* of the string for "connected" text,
    // not the beginning.
    if (name.EndsWith(_kconnected))
    {
        name = name.Substring(0, name.Length - _kconnected.Length);
        val.Add(name);
    }
    return val;
}

(您没有在您的问题中分享 ClientName 类,并且该示例确实不需要它;对于本练习而言,一个简单的 string 值就足够了。另外,我已经介绍了 @ 987654329@ 命名为_kconnected,以确保字符串文字在每个需要的地方都正确使用,并简化使用。)

但是即使解决了这两个问题,您仍然在Form 代码中找到了一些您实际处理接收方法的返回值的地方。首先,您将从接收方法返回的List&lt;T&gt; 对象传递给ListBox.Items.Add() 方法,这只会导致ListBox 显示对象的类型名称,而不是其元素。

其次,由于代码在拥有ListBox 对象的UI 线程之外的线程中执行,您必须将调用包装在对Control.Invoke() 的调用中。否则会出现跨线程操作异常。

解决这两个问题,你会得到:

public void getMessages()
{
    while (true)
    {
        // Need to receive the data, and the call Invoke() to add the
        // data to the ListBox. Also, if adding a List<T>, need to call
        // AddRange(), not Add().
        string[] receivedClientList = client.receiveClientList().ToArray();

        Invoke((MethodInvoker)(() => listBox1.Items.AddRange(receivedClientList)));
    }

通过这些更改,代码将处理客户端发送的消息,并返回客户端列​​表。这应该会让你走得更远。也就是说,您仍然有许多其他问题,包括一些相当基本的问题:

  1. 最大的问题是,当您在服务器中接受连接时,您会创建一个全新的服务器对象来处理该连接。这不是一个好主意有很多原因,但主要原因是其余代码似乎在概念上假设单个服务器对象正在跟踪所有客户端,但每个连接都会产生自己的集合客户端对象,每个集合只有一个成员(即该客户端)。

    请注意,一旦您解决了这个问题,您将有多个线程都访问一个字典数据结构。您将需要学习如何使用lock 语句来确保跨多个线程安全地共享字典。

  2. 另一个重要的问题是,不是使用您在第一次接受连接时创建的streamWriter,而是创建一个全新的StreamWriter 对象(在名为networkWriter 的局部变量中引用)来写入插座。在这个非常简单的示例中,它运行良好,但是在缓冲和缺乏线程安全之间,这个设计不正确的代码可能会出现严重的数据损坏问题。

  3. 问题较少,但值得修复的是,您的服务器代码完全无法利用您将客户端存储在字典中的事实,以及 .NET 具有有用的帮助函数来做事就像把一串串连在一起。我会把你服务器的receiveMessage() 方法写得更像这样:

private const string _kuser = "user";

public void receiveMessage(TcpClient client_Socket)
{
    messageReceived = streamReader.ReadLine();

    if (messageReceived.EndsWith(_kuser))
    {
        String name = messageReceived.Substring(0, messageReceived.Length - _kuser.Length);

        if (clientList.ContainsKey(name))
        {
            streamWriter.WriteLine("The user already exists");
            streamWriter.Flush();
            return;
        }

        //show who's connected
        Console.WriteLine(name + " is online");
        number_clients++;
        clientList.Add(name, client_Socket);

        string send = string.Join(".", clientList.Keys);

        foreach (var value in clientList.Values.Where(v => v != null))
        {
            // NOTE: I didn't change the problem noted in #2 above, instead just
            // left the code the way you had it, mostly. Of course, in a fully
            // corrected version of the code, your dictionary would contain not
            // just `TcpClient` objects, but some client-specific object specific
            // to your server implementation, in which the `TcpClient` object
            // is found, along with the `StreamReader` and `StreamWriter` objects
            // you've already created for that connection (and any other per-client
            // data that you need to track). Then you would write to that already-
            // existing `StreamWriter` object instead of creating a new one each
            // time here.

            NetworkStream networkStream = value.GetStream();
            StreamWriter networkWriter = new StreamWriter(networkStream);
            networkWriter.WriteLine(send + "connected");
            networkWriter.Flush();
        }
    }
}

以上内容无论如何都不是详尽无遗的。坦率地说,您可能应该花更多时间查看现有的网络感知代码示例,例如MSDN 和 Stack Overflow,以及网站、博客或书籍中的教程。即使您在此处尝试以每个连接一个线程的方式编写服务器,也有很多小细节您确实需要纠正,而到目前为止您还没有。

但我确实希望以上内容足以让您克服当前的障碍,进入下一个大问题。 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-22
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 2013-03-19
    • 1970-01-01
    相关资源
    最近更新 更多