多线程和套接字的 C# 问题
本文关键字:问题 套接字 多线程 | 更新日期: 2023-09-27 18:34:11
首先,我只是想让你知道我对编程并不陌生,应该更容易帮助我:)
我在使用 C# 中使用套接字进行的多线程聊天时遇到问题。
我有 3 个线程:
- void ListenSocketConnection :检查可以连接的套接字。连接的套接字将添加到列表中<>
- void CheckIfClientStillConnectedThread:检查套接字是否断开连接。断开连接的套接字将从列表中删除<> void 接收数据
- 侦听器:检查套接字是否接收到数据
- 这就是问题所在。如果第一个或第二个线程从列表中删除套接字<>,则"foreach (clientList 中的 ClientManager cManager("将引发异常。
- 这是第二个问题。如果套接字在此期间断开连接,"foreach ClientManager cManager in clientsList("将引发异常:DisposedException
您对我如何解决此问题有任何提示吗?
这是我的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.Threading;
namespace JAChat.Library
{
class SocketServer
{
private Socket socketServer;
private BackgroundWorker bwSocketConnectListener;
private BackgroundWorker bwCheckIfConnected;
private BackgroundWorker bwReceiveDataListener;
private List<ClientManager> clientsList;
#region Constructor
public SocketServer(int port)
{
clientsList = new List<ClientManager>();
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketServer.Bind(new IPEndPoint(IPAddress.Any, port));
socketServer.Listen(100);
bwSocketConnectListener = new BackgroundWorker();
bwSocketConnectListener.DoWork += new DoWorkEventHandler(ListenSocketConnection);
bwSocketConnectListener.RunWorkerAsync();
bwCheckIfConnected = new BackgroundWorker();
bwCheckIfConnected.DoWork += CheckIfClientStillConnectedThread;
bwCheckIfConnected.RunWorkerAsync();
bwReceiveDataListener = new BackgroundWorker();
bwReceiveDataListener.DoWork += ReceiveDataListener;
bwReceiveDataListener.RunWorkerAsync();
}
#endregion
#region Getter
public List<ClientManager> connectedClients
{
get
{
return clientsList;
}
}
#endregion
#region Public Methods
/// <summary>
/// Parse and send the command object to targets
/// </summary>
public void sendCommand(Command cmd)
{
BackgroundWorker test = new BackgroundWorker();
test.DoWork += delegate {
foreach(ClientManager cManager in clientsList){
cManager.sendCommand(cmd);
}
};
test.RunWorkerAsync();
}
/// <summary>
/// Disconnect and close the socket
/// </summary>
public void Disconnect()
{
socketServer.Disconnect(false);
socketServer.Close();
socketServer = null; //Stop some background worker
}
#endregion
#region Private Methods
private void ListenSocketConnection(object sender, DoWorkEventArgs e)
{
while (socketServer != null)
{
//Get and WAIT for new connection
ClientManager newClientManager = new ClientManager(socketServer.Accept());
clientsList.Add(newClientManager);
onClientConnect.Invoke(newClientManager);
}
}
private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e){
while(socketServer != null){
for(int i=0;i<clientsList.Count;i++){
if(clientsList[i].socket.Poll(10,SelectMode.SelectRead) && clientsList[i].socket.Available==0){
clientsList[i].socket.Close();
onClientDisconnect.Invoke(clientsList[i]);
clientsList.Remove(clientsList[i]);
i--;
}
}
Thread.Sleep(5);
}
}
private void ReceiveDataListener(object unused1, DoWorkEventArgs unused2){
while (socketServer != null){
foreach (ClientManager cManager in clientsList)
{
try
{
if (cManager.socket.Available > 0)
{
Console.WriteLine("Receive Data Listener 0");
//Read the command's Type.
byte[] buffer = new byte[4];
int readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
Console.WriteLine("Receive Data Listener 1");
if (readBytes == 0)
break;
Console.WriteLine("Receive Data Listener 2");
CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));
Console.WriteLine("Receive Data Listener 3");
//Read the sender IP size.
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int senderIPSize = BitConverter.ToInt32(buffer, 0);
//Read the sender IP.
buffer = new byte[senderIPSize];
readBytes = cManager.socket.Receive(buffer, 0, senderIPSize, SocketFlags.None);
if (readBytes == 0)
break;
IPAddress cmdSenderIP = IPAddress.Parse(System.Text.Encoding.ASCII.GetString(buffer));
//Read the sender name size.
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int senderNameSize = BitConverter.ToInt32(buffer, 0);
//Read the sender name.
buffer = new byte[senderNameSize];
readBytes = cManager.socket.Receive(buffer, 0, senderNameSize, SocketFlags.None);
if (readBytes == 0)
break;
string cmdSenderName = System.Text.Encoding.Unicode.GetString(buffer);
//Read target IP size.
string cmdTarget = "";
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int targetIPSize = BitConverter.ToInt32(buffer, 0);
//Read the command's target.
buffer = new byte[targetIPSize];
readBytes = cManager.socket.Receive(buffer, 0, targetIPSize, SocketFlags.None);
if (readBytes == 0)
break;
cmdTarget = System.Text.Encoding.ASCII.GetString(buffer);
//Read the command's MetaData size.
string cmdMetaData = "";
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int metaDataSize = BitConverter.ToInt32(buffer, 0);
//Read the command's Meta data.
buffer = new byte[metaDataSize];
readBytes = cManager.socket.Receive(buffer, 0, metaDataSize, SocketFlags.None);
if (readBytes == 0)
break;
cmdMetaData = System.Text.Encoding.Unicode.GetString(buffer);
//Create the command object
Command cmd = new Command(cmdType, cmdSenderIP, cmdSenderName, IPAddress.Parse(cmdTarget), cmdMetaData);
this.onCommandReceived(cmd);
}
}
catch (ObjectDisposedException) {/*Le socket s'est déconnectée pendant le for each. Ignore l'érreur et retourne dans le while*/ }
catch (InvalidOperationException) { /* clientsList a été modifié pendant le foreach et délanche une exception. Retour while*/}
}
}
Console.WriteLine("Receive data listener closed");
}
#endregion
#region Events
public delegate void OnClientConnectEventHandler(ClientManager client);
/// <summary>
/// Events invoked when a client connect to the server
/// </summary>
public event OnClientConnectEventHandler onClientConnect = delegate { };
public delegate void OnClientDisconnectEventHandler(ClientManager client);
/// <summary>
/// Events invoked when a client disconnect from the server
/// </summary>
public event OnClientDisconnectEventHandler onClientDisconnect = delegate { };
public delegate void OnCommandReceivedEventHandler(Command cmd);
/// <summary>
/// Events invoked when a command has been sent to the server
/// </summary>
public event OnCommandReceivedEventHandler onCommandReceived = delegate { };
#endregion
}
}
- 有多个线程,但我没有看到任何同步。这是不正确的。使用锁来保护可变共享状态。
- 与其集中管理所有套接字,轮询和检查
DataAvailable
,为什么不只使用每个套接字一个线程或异步IO?这样,您一次只能管理一个套接字。无需轮询。您只需调用Read
(或其异步版本(并等待数据到达。这是处理套接字的更好范例。基本上,如果您的套接字代码包含DataAvailable
或轮询,那么您将违反最佳实践(并且可能在某处存在错误(。想想如何在不使用两者的情况下解决这个问题。这是可能的,而且更好。 - 在
ReceiveDataListener
中,您假设,如果数据可用,则整个消息可用。这是错误的,因为TCP是面向流的。您可以以任意小块的形式接收发送的数据。按照我的观点(2(解决这个问题。
详细说(2(:这基本上是一个演员模型。每个套接字一个演员。无论是使用线程、使用 async/await
还是使用旧版异步 IO 实现执行组件都无关紧要。
希望这有帮助。请随时在下面的评论中提出后续问题。
集合正在由多个线程修改,因此每次询问时计数都可能非常多。 因此,您应该将其设置为固定金额;即在循环之前,然后遍历列表。 此外,倒计时而不是向上倒计时是更好的选择,因为您要删除元素。
考虑以下代码:
private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e)
{
while (socketServer != null)
{
int count = clientsList.Count -1;
for (int i=count; i >= 0 ; i--)
{
if (clientsList[i].socket.Poll(10, SelectMode.SelectRead) && clientsList[i].socket.Available == 0)
{
clientsList[i].socket.Close();
onClientDisconnect.Invoke(clientsList[i]);
clientsList.Remove(clientsList[i]);
}
}
Thread.Sleep(5);
}
}