c# - 客户端未从服务器多线程接收数据

标签 c# multithreading sockets tcpclient

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

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

当我在服务器上调试时,套接字出现 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());
        }
    }
}

最佳答案

运行您的代码时,我无法重现任何错误。我不知道您的意思是“套接字出现DualMode,EnableBroadcast错误”。就是说,代码存在许多可修复的问题,其中包括一些直接与您有关的问题,即“当我必须接收列表时,它会停止并且不执行任何操作”。

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

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值就足够了。此外,我介绍了名为const string_kconnected,以确保字符串文字可以在需要的每个位置正确使用,以及简化用法。)

但是即使解决了这两个问题,您仍然可以在Form代码中找到一些实际处理接收方法返回值的代码。首先,您将从接收方法返回的List<T>对象传递给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)));
    }

进行这些更改后,代码将处理客户端发送的消息,并返回客户端列​​表。那应该使您更进一步。也就是说,您还有许多其他问题,包括一些相当基本的问题:
  • 最大的问题是,当您在服务器中接受连接时,您将创建一个全新的服务器对象来处理该连接。有很多原因,这不是一个好主意,但主要的原因是,其余代码在概念上似乎假设单个服务器对象正在跟踪所有客户端,但是每个连接都会产生自己的集合客户端对象,每个集合只有一个成员(即该客户端)。
    请注意,解决此问题后,将有多个线程都在访问单个字典数据结构。您将需要学习如何使用lock语句来确保在多个线程之间安全共享字典的使用。
  • 另一个重要的问题是,不是使用首次接受连接时创建的streamWriter,而是创建了一个全新的StreamWriter对象(在名为networkWriter的本地变量中引用)以写入套接字。在这个非常简单的示例中,它工作正常,但是在缓冲和缺乏线程安全性之间,此错误设计的代码可能会遇到严重的数据损坏问题。
  • 较少出现问题,但值得修复的是,您的服务器代码完全无法利用以下事实:将客户端存储在字典中,并且.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上,以及在网站,博客或书籍中的教程上。即使您似乎在这里尝试以一种“每个连接一个线程”的方式编写服务器,也确实有很多细节需要纠正,而到目前为止您还没有做到。

    但我确实希望以上内容足以使您克服当前的障碍,并继续解决下一个大问题。 :)

    关于c# - 客户端未从服务器多线程接收数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36237854/

    相关文章:

    sockets - UDP数据包不发送?

    c# - 如何使用 TSQL 语法将 .bak 恢复到新数据库?

    c# - 如何在 Blazor 中将带有参数的 onclick 添加到按钮?

    java - 如何解决 Sonar 问题 "Remove this call to "等待“或将其移至 "while"循环”?

    java - 暂时停止事件调度线程 Activity (v7)

    Java - 通过MulticastSocket发送UDP数据包来减少UDP数据包的生存时间?

    c# - 如何使用 include in 子查询进行左外连接?

    c# - 如何在 gridview 的编辑模式下从下拉列表中获取选定的值?

    c++ - boost::thread 和 std::thread 兼容性问题?

    javascript - 如何使用 Node.js 在 JavaScript 模块文件之间使用事件处理程序?