Есть сервер, есть клиент. Между собой они общаются некими сообщениями. Пусть это будет класс Message, вынесенный в отдельную dll и подключенный к проекту сервер и клиента.
Класс Message[Serializable]
public class Message {
public int id;
public string title;
public string message;
}
Суть клиента простая, послать 100 сообщений серверу, одно за другим, без задержек.
Класс клиентаTcpClient client = new TcpClient("127.0.0.1",2255);//Коннект к серверу
for (int i = 0; i < 100; i++){//Шлём 100 сообщений
using (var stream = new NetworkStream(client.Client)){//Берем поток сокета
BinaryFormatter bf = new BinaryFormatter();
var msg = new Message(){//Создаем сообщение которое хотим послать на сервер
id = i,
message = "Hello world",
title = "My Title"
};
bf.Serialize(stream, msg);//сериализуем и пишем в поток
}
//Thread.Sleep(100); //Если добавить задержку то всё ок
}
Console.WriteLine("end");
Console.ReadKey();
Суть сервера чуть сложнее, получить эти 100 сообщений асинхронным методом BeginReceive и вывести их в консоль.
Класс сервераstatic void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 2255);
listener.Start(100);//Запускаем сервер, слушаем порт 2255
Console.WriteLine("Listen 2255");
while (true){
new ClientSocket(listener.AcceptSocket());//Ждем подключения клиента, при подключении создаем новый класс ClientSocket
Console.WriteLine("Socket connect");//Сокет подключен, ждем других клиентов
}
}
Класс сервера ClientSocketpublic class ClientSocket{
private Socket socket;
private MemoryStream recieveData = new MemoryStream();
public const int BufferSize = 256;
byte[] buffer = new byte[BufferSize];
public ClientSocket(Socket s){
this.socket = s;
socket.BeginReceive(this.buffer, 0, ClientSocket.BufferSize, 0, new AsyncCallback(ReceiveCallback), this);
}
private static void ReceiveCallback(IAsyncResult ar){
ClientSocket state = (ClientSocket)ar.AsyncState;
Socket client = state.socket;
int bytesRead = 0;
try{
bytesRead = client.EndReceive(ar);//количество байт записанных в буффер
}
catch (SocketException se){
if (se.ErrorCode == 10054){
Console.WriteLine("Клиент отключился");
return;
}
else
throw se;
}
if (bytesRead > 0){
state.recieveData.Write(state.buffer, 0, bytesRead);//пишет в поток принятых байт
Console.WriteLine("read {0} byte, available {1}", bytesRead, client.Available);
}
if (client.Available == 0 && state.recieveData.Length > 0){//если больше нет данных на прием и что-то есть в общем потоке
IFormatter formatter = new BinaryFormatter();
state.recieveData.Seek(0, SeekOrigin.Begin);//указать потока на 0
var m = (Message)formatter.Deserialize(state.recieveData);//десереализуем данные потока как Message
state.recieveData.SetLength(0);//сбрасываем поток...
Console.WriteLine("Recieve: {0} {1} {2}", m.id, m.title, m.message);
}
client.BeginReceive(state.buffer, 0, ClientSocket.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);//ждем еще данные
}
~ClientSocket()
{
if (recieveData != null)
{
recieveData.Close();
recieveData.Dispose();
}
}
}
Если кидать по одному или делать задержку на клиенте перед записью в поток, то более менее ок, на сервере принимаем по порядку. Если без задержки, то принимает всё и выводит последнее.
Пример вывода на сервере с задержкой в клиенте
Пример вывода на сервере БЕЗ задержки в клиенте
Но и в том и в том случае иногда все выскакивает на сервере исключение SerializationException: Конец потока обнаружен до завершения разбора или SerializationException: Недопустимый двоичный формат входного потока...
В принципе понятно условие после которого начинается разбор не совсем корректное
if (client.Available == 0 && state.recieveData.Length > 0){//если больше нет данных на прием и что-то есть в общем потоке
и вообще как-то всё не так.
Наверно можно было-бы придумать какие нибудь свои пакеты. Где например первые 4байта содержали длинну пакета, дальше сам пакет(сериализованный Message). Ну и таким образом разбираем весь этот поток входных байтов. Ну хочется как-то более красиво и правильно.
Вопрос: Как реализовать это правильно, чтобы без проблем принимать данные от клиентов, даже если клиент пишет их непрерывно потоком?
Еще важный момент, клиент не должен закрывать сокет после отправки, все должно висеть на одном сокете, как подключился клиент к серверу, так и висит и по надобности обращается. В моем примере общение только с сервером, в дальнейшем это будет в обе стороны.