Правильная отправка и чтение данных из NetworkStream. Многопоточность. С#
Помогите правильно отправить/принять данные через NetworkStream в многопоточном приложении(подобие чата).
Есть 3 главных класса: Client, Server, ServerClient.
Client
private readonly ServerClient _serverClient;
public Client() { }
public Client(string localHostIp, int localHostPort, int id, string nickname)
{
_serverClient = new ServerClient(localHostIp, localHostPort);
ID = id;
Nickname = nickname;
}
public int ID { get; private set; }
public string Nickname { get; private set; }
public void SendMessage(string message)
{
MessageStruct clientMessage = new MessageStruct(Guid.NewGuid().ToString(), message, DateTime.Now);
_serverClient.SendMessage(this, clientMessage);
}
public void AddSubToEvent(Action<Client, MessageStruct> action) => _serverClient.MessageRecived += action;
public void RemvodeSubToEvent(Action<Client, MessageStruct> action) => _serverClient.MessageRecived -= action;
Server
private TcpListener _server;
private List<TcpClient> _tcpClients;
private Thread _threadForListeningProcess;
public override event Action<Client, MessageStruct> MessageRecived;
public Server(string localHostIp, int localHostPort) : base(localHostIp, localHostPort)
{
_server = new TcpListener(LocalHostIP, LocalHostPort);
_server.Start();
_tcpClients = new List<TcpClient>();
_threadForListeningProcess = new Thread(ListeningProcess);
_threadForListeningProcess.Start();
}
public void ListeningProcess()
{
while (true)
{
TcpClient tcpClient = _server.AcceptTcpClient();
_tcpClients.Add(tcpClient);
ThreadForReceivingMessages = new Thread(MessageReceivingProcess);
ThreadForReceivingMessages.Start(tcpClient);
}
}
public void MessageReceivingProcess(dynamic tcpClient)
{
using var client = tcpClient as TcpClient;
NetworkStream = client?.GetStream();
while (true)
{
GetMessage();
}
}
private string GetMessage()
{
var clientMessage = new MessageStruct();
var client = new Client();
do
{
if (NetworkStream.CanRead)
{
var data = new byte[StreamBufferSize];
NetworkStream.Read(data, 0, data.Length);
MyConverter.GetClientMessageFromBytes(data, ref clientMessage);
Array.Clear(data, 0, data.Length);
NetworkStream.Read(data, 0, data.Length);
client = MyConverter.GetClientFromBytes(data);
}
}
while (NetworkStream.DataAvailable);
MessageRecived?.Invoke(client, clientMessage);
return clientMessage.Message;
}
public void BroadcastMessage(string message)
{
if(_tcpClients.Count > 0)
{
MessageStruct serverMessage = new MessageStruct(Guid.NewGuid().ToString(), message, DateTime.Now);
byte[] data = MyConverter.GetBytesFromClientMessage(serverMessage);
foreach (var client in _tcpClients)
{
client.GetStream().Write(data, 0, data.Length);
}
return;
}
throw new Exception("Сurrently there are no connected clients on the server");
}
ServerClient
private TcpClient _TcpClient;
private Client _client;
public override event Action<Client, MessageStruct> MessageRecived;
public ServerClient(string localHostIp, int localHostPort) : base(localHostIp, localHostPort)
{
_TcpClient = new TcpClient();
_TcpClient.Connect(LocalHostIP, LocalHostPort);
NetworkStream = _TcpClient.GetStream();
ThreadForReceivingMessages = new Thread(MessageReceivingProcess);
ThreadForReceivingMessages.Start();
}
public void SendMessage(Client client, MessageStruct message)
{
byte[] data = MyConverter.GetBytesFromClientMessage(message);
_TcpClient?.GetStream().Write(data, 0, data.Length);
Array.Clear(data, 0, data.Length);
data = MyConverter.GetBytesFromClient(client);
_TcpClient?.GetStream().Write(data, 0, data.Length);
}
private void MessageReceivingProcess()
{
while (true)
{
var clientMessage = new MessageStruct();
do
{
if (NetworkStream.CanRead)
{
var data = new byte[StreamBufferSize];
NetworkStream.Read(data, 0, data.Length);
MyConverter.GetClientMessageFromBytes(data, ref clientMessage);
}
}
while (NetworkStream.DataAvailable);
MessageRecived?.Invoke(_client, clientMessage);
}
}
Прошу помочь с 2 моментами:
- В данный момент метод MessageReceivingProcess класса ServerClient работает не правильно, потому что я не знаю, как правильно этот класс должен знать с каким объектом класса Client он работает, чтобы при инициировании события он отправлял нужный параметр(сейчас там нигде не присваивающийся _client).
- Ну и выскакивает ошибка в методе класса MyConvert
public static void GetClientMessageFromBytes(byte[] bytes, ref MessageStruct message)
{
int len = Marshal.SizeOf(message);
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.Copy(bytes, 0, ptr, len);
message = (MessageStruct)Marshal.PtrToStructure(ptr, message.GetType());
Marshal.FreeHGlobal(ptr);
}
Метод сам по себе написан корректно(это я уже тестил), но из-за того, что я не знаю как правильно нужно или синхронизировать доступ к этому методу потокам, или еще что-то сделать, выскакивает ошибка. Сразу скажу, что пишу такого рода приложение первый раз. Приложение не будет использоваться нигде, а нужно для хоть приблизительного понимая того, как бы это выглядело на случай реального использования. Все данные хранятся в памяти, на это не обращайте внимание, БД специально здесь не использую, так как цель немного иная. Прошу доходчиво помочь с косяками.