客户端未从服务器多线程接收数据
Client not receiving data from Server Multithreading
我想聊天。服务器是在控制台应用程序中制作的,客户端是在 winforms 中制作的。
在客户端我写了一个昵称并连接到服务器。服务器从客户端接收名称。我使用 (string)name 和 (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());
}
}
}
当 运行 您的代码时,我无法重现任何 错误 。我不知道你所说的 "the Sockets appear with DualMode,EnableBroadcast error" 是什么意思。也就是说,代码中存在许多可修复的问题,包括一些与您担心 "when I have to receive the list it stops and doesn't do anything."
直接相关的问题
代码的最大问题可能是您从未启动客户端的接收线程。您需要在 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
class,实际上这个例子不需要它;一个简单的 string
值就足够了练习。此外,我还介绍了名为 _kconnected
的 const string
,以确保在每个需要的地方正确使用字符串文字,并简化使用。)
但即使解决了这两个问题,您在实际处理接收方法的 return 值的 Form
代码中仍有一些问题。首先,您将 List<T>
对象从接收方法 return 传递给 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)));
}
进行这些更改后,代码将处理客户端发送的消息,以及 return 客户端列表。那应该会让你走得更远。也就是说,您还有许多其他问题,包括一些相当基本的问题:
最大的问题是,当您在服务器中接受连接时,您会创建一个全新的服务器对象来处理该连接。这不是一个好主意的原因有很多,但主要原因是其余代码似乎在概念上假设单个服务器对象正在跟踪所有客户端,但每个连接都会产生自己的集合客户端对象的集合,每个集合只有一个成员(即那个客户端)。
请注意,一旦你解决了这个问题,你就会有多个线程都访问一个字典数据结构。您将需要学习如何使用 lock
语句来确保跨多个线程安全地共享字典。
另一个重要的问题是,您没有使用您在第一次接受连接时创建的 streamWriter
,而是创建了一个全新的 StreamWriter
对象(在局部变量中引用named 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 以及网站、博客或书籍中的教程中。即使当您以每个连接一个线程的方式编写服务器时,就像您在这里尝试做的那样,您确实需要很多小细节才能正确,而到目前为止您还没有。
但我确实希望以上内容足以让您克服当前的障碍,并解决下一个大问题。 :)
我想聊天。服务器是在控制台应用程序中制作的,客户端是在 winforms 中制作的。
在客户端我写了一个昵称并连接到服务器。服务器从客户端接收名称。我使用 (string)name 和 (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());
}
}
}
当 运行 您的代码时,我无法重现任何 错误 。我不知道你所说的 "the Sockets appear with DualMode,EnableBroadcast error" 是什么意思。也就是说,代码中存在许多可修复的问题,包括一些与您担心 "when I have to receive the list it stops and doesn't do anything."
直接相关的问题代码的最大问题可能是您从未启动客户端的接收线程。您需要在 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
class,实际上这个例子不需要它;一个简单的 string
值就足够了练习。此外,我还介绍了名为 _kconnected
的 const string
,以确保在每个需要的地方正确使用字符串文字,并简化使用。)
但即使解决了这两个问题,您在实际处理接收方法的 return 值的 Form
代码中仍有一些问题。首先,您将 List<T>
对象从接收方法 return 传递给 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)));
}
进行这些更改后,代码将处理客户端发送的消息,以及 return 客户端列表。那应该会让你走得更远。也就是说,您还有许多其他问题,包括一些相当基本的问题:
最大的问题是,当您在服务器中接受连接时,您会创建一个全新的服务器对象来处理该连接。这不是一个好主意的原因有很多,但主要原因是其余代码似乎在概念上假设单个服务器对象正在跟踪所有客户端,但每个连接都会产生自己的集合客户端对象的集合,每个集合只有一个成员(即那个客户端)。
请注意,一旦你解决了这个问题,你就会有多个线程都访问一个字典数据结构。您将需要学习如何使用lock
语句来确保跨多个线程安全地共享字典。另一个重要的问题是,您没有使用您在第一次接受连接时创建的
streamWriter
,而是创建了一个全新的StreamWriter
对象(在局部变量中引用namednetworkWriter
) 写入套接字。在这个非常简单的示例中,它工作正常,但是在缓冲和缺乏线程安全之间,这种设计不正确的代码可能会产生严重的数据损坏问题。问题较少但值得修复的是,您的服务器代码完全无法利用您将客户端存储在字典中这一事实,以及 .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 以及网站、博客或书籍中的教程中。即使当您以每个连接一个线程的方式编写服务器时,就像您在这里尝试做的那样,您确实需要很多小细节才能正确,而到目前为止您还没有。
但我确实希望以上内容足以让您克服当前的障碍,并解决下一个大问题。 :)