@unknown3

Сокеты, передача сереализованных объектов, разделение в потоке. Как?

Есть сервер, есть клиент. Между собой они общаются некими сообщениями. Пусть это будет класс 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");//Сокет подключен, ждем других клиентов
            }
        }

Класс сервера ClientSocket
public 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();
            }
        }
    }

Если кидать по одному или делать задержку на клиенте перед записью в поток, то более менее ок, на сервере принимаем по порядку. Если без задержки, то принимает всё и выводит последнее.
Пример вывода на сервере с задержкой в клиенте
6113bffc13633219440294.png

Пример вывода на сервере БЕЗ задержки в клиенте
6113c01b14a97843760013.png

Но и в том и в том случае иногда все выскакивает на сервере исключение SerializationException: Конец потока обнаружен до завершения разбора или SerializationException: Недопустимый двоичный формат входного потока...
В принципе понятно условие после которого начинается разбор не совсем корректное
if (client.Available == 0 && state.recieveData.Length > 0){//если больше нет данных на прием и что-то есть в общем потоке

и вообще как-то всё не так.
Наверно можно было-бы придумать какие нибудь свои пакеты. Где например первые 4байта содержали длинну пакета, дальше сам пакет(сериализованный Message). Ну и таким образом разбираем весь этот поток входных байтов. Ну хочется как-то более красиво и правильно.
Вопрос: Как реализовать это правильно, чтобы без проблем принимать данные от клиентов, даже если клиент пишет их непрерывно потоком?
Еще важный момент, клиент не должен закрывать сокет после отправки, все должно висеть на одном сокете, как подключился клиент к серверу, так и висит и по надобности обращается. В моем примере общение только с сервером, в дальнейшем это будет в обе стороны.
  • Вопрос задан
  • 262 просмотра
Решения вопроса 2
sarapinit
@sarapinit Куратор тега C#
Точу водой камень
@unknown3 Автор вопроса
В общем добавил заголовки, всё стало работать чётко без проблем.
Привел в более менее понятный вид, добавил также общение сервера с клиентом. Ну и сделал всё асинхронным.
Может кому пригодится. Код оставлю в комментариях, а то ограничение 10тысяч символов.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы