Ваша реализация получения сообщений по определенному протоколу - C#

Узнай цену своей работы

Формулировка задачи:

Третьего дня получил задание - реализовать клиентскую часть одного сетевого протокольчика. Протокол простенький по своей спецификации, я бы даже сказал примитивный, но пара моментов реализации вкупе с несколькими условиями, которые были поставлены, создали довольно интересную задачку. Настолько интересную, что захотелось поделиться ей с общественностью. Спецификация: 1. Общение клиента с сервером происходит по протоколу TCP. 2. Общение инициирует клиент, первым отсылая сообщение. 3. Протокол асинхронный, то есть сервер может как отвечать на клиентские запросы, так и слать сообщения без предупреждения (уведомления о всяких событиях). Через это клиент должен постоянно находиться в режиме приема сообщений. 4. Сообщение - это строка незашифрованного текста в кодировке UTF-8. 5. Сообщения разделяются символом \n 6. Символ \r игнорируется. 7. Сообщения состоят из параметров, разделенных пробелами. 8. Количество параметров неограничено, но их всегда как минимум два. 9. Длина каждого параметра неограничена*. Так же параметр может быть пустой строкой (два подряд идущих пробела, например). 10. Параметр может состоять из любых символов, имеющихся в таблице юникода. 11. Если в тексте параметра встречается конструкция вида {n}, где n - целое число в промежутке [0; int.MaxValue], то следующие n байтов текста после закрывающей квадратной скобки считываются как есть и являются частью этого параметра, то есть любые специальные символы не интерпретируются (напр. пробелы и переносы строки). * Основная масса сообщений - довольно короткие, в районе 100 символов. Но, отослав определенный запрос, можно получить и портянку текста длиной в несколько мегабайт. Задача: реализовать клиентскую часть, отвечающую за прием сообщений и генерирующую для каждого полученного сообщения массив строк, где каждый элемент массива является параметром сообщения. Ну и, конечно же, чтобы жизнь медом не казалась, имеется несколько обязательных к выполнению условий: 1. Максимально возможная эффективность обработки сообщений, т.к. обработчиков будет создано много (может быть в районе тысячи). 2. Вследствие первого пункта у обработчика должен быть минимальный футпринт в памяти, то есть в ходе работы объекты в куче стоит плодить только при крайней необходимости. К примеру, вполне разумно один раз создать буфер для приема данных, но весьма неразумно - создавать отдельный буфер для каждого сообщения. Если, конечно, ваше решение вообще будет использовать буферы (моё использует). Обработчик будет частью другой, довольно сложной системы, потому должен быть максимально легковесным. 3. Вследствие все того же первого пункта обязательно использование асинхронной модели для приема сообщений, т.к. тысячу потоков вам никто не предоставит. Вроде бы ничего не забыл. Если кому-то нечего делать - попытайтесь реализовать. Решение выкладывать не обязательно - это не просьба о помощи (решение уже есть). Но очень интересно было бы услышать, с какой стороны вы бы подошли к проблеме.

Решение задачи: «Ваша реализация получения сообщений по определенному протоколу»

textual
Листинг программы
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
 
namespace dbtest1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.ReadKey();
        }
 
 
        //это обычный калбек в асинхронных сокетах, где-то его вызвали первый раз
        /*
         * state = new ReceiveState(socket, ...);
         *                        
         * socket.BeginReceive(state.Buffer.Data, state.Buffer.TotalReceived,
         *          state.Buffer.AvailableSpace,
         *          SocketFlags.None,
         *          ReceiveCallback, state);
         */
        private void ReceiveCallback(IAsyncResult ar)
        {
            var state = (ReceiveState) ar.AsyncState;
 
            Socket socket = state.Connection;
            ReceiveBuffer buffer = state.Buffer;
 
            SocketError error;
            Exception ex = null;
            bool disposed = false;
            int received = 0;
 
            try
            {
                received = socket.EndReceive(ar, out error);
            }
            catch (ObjectDisposedException exception)
            {
                ex = exception;
                disposed = true;
            }
            catch (Exception exception)
            {
                ex = exception;
            }
            finally
            {
                if (received > 0 && ex == null)
                {
                    state.Buffer.TotalReceived += received;
 
                    Queue<Message> messages = buffer.GetMessages();
 
                    if (messages.Count > 0)
                    {
                        foreach (Message message in messages)
                        {
                            //у нас есть сообщение, можно вызвать событие или еще что-то
 
                            // OnReceiveMessage(new MessageReceivedEventAgrs(state.Peer, msg));
                        }                      
                    }
 
 
  socket.BeginReceive(state.Buffer.Data, state.Buffer.TotalReceived,
                            state.Buffer.AvailableSpace,
                            SocketFlags.None,
                            ReceiveCallback,
                            state);
                }
                else
                {
                    if (!disposed)
                    {
                        // OnDisconnected
                    }
                }
            }
        }
    }
 
    internal class ReceiveState
    {
        public ReceiveBuffer Buffer;
        public Socket Connection { get; set; }
    }
 
    public class Message
    {
        public Message(byte[] data, int offset, int length)
        {
            //на этом этапе у нас есть всё сообщение целиком в data начиная с индекса offset и длиной length
            // отсюда можно обрабатывать его как угодно
            //разделять на куски, заполнять поля и все такое, главное у нас весь кусок данных
            throw new NotImplementedException();
        }
    }
 
    public class ReceiveBuffer
    {
        private static byte messageSplitToken = (byte) '\n';
 
        private static byte rawDataSplitTokenStart = (byte) '{';
        private static byte rawDataSplitTokenEnd = (byte) '}';
 
 
        public byte[] Data;
        public int TotalReceived;
 
        public int AvailableSpace
        {
            get { return Data.Length - TotalReceived; }
        }
 
 
        public Queue<Message> GetMessages()
        {
            var messages = new Queue<Message>();
 
            int totalSize = 0;
            Message message;
 
            int offset = 0;
            if (TryParse(out message, ref offset))
            {
                totalSize += offset;
                messages.Enqueue(message);
            }
 
            MoveData(totalSize);
 
            if (AvailableSpace <= 0)
                Array.Resize(ref Data, Data.Length*2);
 
            return messages;
        }
 
        private void MoveData(int size)
        {
            //пеерместить данные в 0, все сообщение что были в буфере мы прочитали
// а остаток будет лежать уже с 0 
            Buffer.BlockCopy(Data, 0, Data, 0, size);
 
            TotalReceived -= size;
        }
 
        private bool TryParse(out Message message, ref int offset)
        {
            message = null;
 
            //у нас блок данных от offset до TotalReceived
            //может быть есть целое сообщение, может и нету
 
            //ищем конец сообщения
            int messageEnd = FindMessageEnd(offset);
 
            if (messageEnd > 0)
            {               
                message = new Message(Data, offset, TotalReceived - messageEnd);
                offset = messageEnd;
                return true;
            }
 
            return false;
        }
 
        private int FindMessageEnd(int offset)
        {
            if (TotalReceived - offset < 0)
            {
                return -1;
            }
 
            //ищем конец сообщения 
            int msgEnd = Array.IndexOf(Data, messageSplitToken, offset, TotalReceived - offset);
 
            if (msgEnd >= 0)
            {
                //похоже есть конец, но конец ли это? может это параметр с rawdata
 
                //ищем признак параметра с данными - скобка {
                int rawDataStart = Array.IndexOf(Data, rawDataSplitTokenStart, offset, TotalReceived - msgEnd);
                if (rawDataStart >= 0)
                {
                    //это был нифига не конец сообщения
 
                    //пытаемся получить размер данных между скобками
 
                    int sizeDataSize; // это размер куска данных где лежит размер - "{100}"
 
                    int dataSize = TryGetRawDataSize(Data, rawDataStart, TotalReceived - rawDataStart, out sizeDataSize);
 
                    if (dataSize > 0) //удалось получить размер данных
                    {
                        //есть размер, значит считаем где будет конец данных
                        int endRawData = offset + rawDataStart + dataSize + sizeDataSize;
 
                        //есть ли у нас столько данных вообще в буфере, может еще не принято столько?
                        if (endRawData < TotalReceived - offset)
                        {
                            //данных достаточно, значит ищем дальше конец сообщения уже с конца данных
                            //рекурсия, да
                            msgEnd = FindMessageEnd(endRawData);
                        }
                        else //нет конца данных, не принято еще сообщение целиком
                        {
                            return -1;
                        }
                    }
                    else //не получили размер данных, не принято еще похоже
                    {
                        return -1;
                    }
                }
                else
                {
                    //все ок, параметров с raw данными не нашлось
                    //значит у нас в msgEnd настоящий конец сообщения, его и возвращаем
                }
            }
 
            return msgEnd;
        }
 
        private int TryGetRawDataSize(byte[] data, int dataSizeStart, int count, out int sizeDataSize)
        {
            sizeDataSize = -1;
 
            //ищем закрывающую скобку }
            int sizeDataEnd = Array.IndexOf(data, rawDataSplitTokenEnd, dataSizeStart, count);
 
            if (sizeDataEnd >= 0)
            {
                //надо в отладчике посмотреть что тут происходит, чтоб скобки не попадали в строку :)
                sizeDataSize = sizeDataEnd - dataSizeStart + 1;
 
                //нашли закрывающую скобку - }
                //теперь берем число между { и } 
                string sizeString = Encoding.UTF8.GetString(data, dataSizeStart + 1, sizeDataSize);
 
                int value;
                if (int.TryParse(sizeString, out value))
                {
                    return value;
                }
                else
                {
                    return -1;
                }
            }
            return -1;
        }
    }
}

ИИ поможет Вам:


  • решить любую задачу по программированию
  • объяснить код
  • расставить комментарии в коде
  • и т.д
Попробуйте бесплатно

Оцени полезность:

10   голосов , оценка 4.1 из 5
Похожие ответы