Обработчик пакетов на основе TCP NetworkStream - C#

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

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

Собственно, хочу сделать так, чтобы данные читались из NetworkStream и записывались MemoryStream. Проблема в том, что я не могу узнать сколько доступно байт для чтения из NetworkStream и поэтому приходится выделить буфер типа byte[] (так как количество считанных может быть больше чем доступная память для MemoryStream). Собственно, какое решение посоветуете?
  1. Не морочить мозги и выделять буфер, так как для современных ОЗУ выделение буфера в несколько килобайт ничтожно.
  2. Сделать буфер ничтожно маленьким и использовать его многократно.
  3. Сделать общий буфер и писать в него.
  4. Использовать некоторую функцию для записи из NetworkStream в MemoryStream напрямую. (которая увеличит размер буфера MemoryStream, при необходимости).
Что я по поводу этого думаю:
  1. При большом количестве соединений может кушать памяти гораздо больше чем ожидалось.
  2. Идет нагрузка на ЦП, считать один раз проще, чем несколько раз.
  3. Невозможность многопоточного программирования.
  4. А такая есть?
Или, просто считываем из NetworkStream записывая в буфер MemoryStream, если буфер MemoryStream закончился, разширяем его? Хотя, тогда уже проще будет использовать дополнительный буфер и использовать MemoryStream.Write() ?
Собственно написал вот так:
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Collections.Generic;
 
namespace PacketManager
{
    /// <summary>
    /// Базовый интерфейс пакетов, которыми оперирует PacketManager</see>.
    /// </summary>
    public interface IPacket
    {
        /// <summary>
        /// При определении, возвращает размер в байтах, который необходим, чтобы сохранить пакет.
        /// </summary>
        ushort Size { get; }
        /// <summary>
        /// При определении, задает имеют ли фиксированный размер все пакеты данного типа
        /// </summary>
        bool FixedSize { get; }
        /// <summary>
        /// При переопределении, записывает данные пакета в бинарный поток.
        /// </summary>
        /// <param name="stream">Бинарный поток.</param>
        void Write(BinaryWriter stream);
        /// <summary>
        /// При переопределении, считывает данные пакета из бинарного потока.
        /// </summary>
        /// <param name="stream">Бинарный поток.</param>
        /// <param name="readCount">Количество данных, которое должно быть считано.</param>
        void Read(BinaryReader stream, int readCount);
    }
 
    /// <summary>
    /// Типизирует ошибки возникающие, при упаковке и распаковке пакетов.
    /// </summary>
    public class IPacketException : Exception
        {
            public IPacketException() : base() { }
            public IPacketException(string message) : base(message) { }
            public IPacketException(string message, Exception innerException) : base(message, innerException) { }
        }
 
    /// <summary>
    /// Класс для обмена сообщениями по сети.
    /// </summary>
    class PacketManager
    {
        /// <summary>
        /// Общий тип исключений генерируемых в PacketManager.
        /// </summary>
        public class PacketManagerException : Exception
        {
            public PacketManagerException() : base() { }
            public PacketManagerException(string message) : base(message) { }
            public PacketManagerException(string message, Exception innerException) : base(message, innerException) { }
        }
        /// <summary>
        /// Поток TCP соединения.
        /// </summary>
        NetworkStream Stream;
        /// <summary>
        /// Буфер записи в NetworkStream.
        /// </summary>
        MemoryStream StreamWriterBuffer;
        /// <summary>
        /// Буфер чтения из NetworkStream.
        /// </summary>
        MemoryStream StreamReaderBuffer;
        /// <summary>
        /// Бинарная запись в NetworkStream.
        /// </summary>
        BinaryWriter StreamWriter;
        /// <summary>
        /// Бинарное чтение из NetworkStream.
        /// </summary>
        BinaryReader StreamReader;
        /// <summary>
        /// Общий список всех пакетов для использования.
        /// </summary>
        public static readonly List<Type> PacketTypes;
        /// <summary>
        /// Образует список всех пакетов для использования.
        /// </summary>
        static PacketManager()
        {
            PacketTypes = new List<Type>();
        }
 
        public void SendPacket(IPacket packet)
        {
            var packetID = PacketTypes.IndexOf(packet.GetType());
            if (packetID > -1)
            {
                StreamWriter.Write((byte)packetID);
                if (!packet.FixedSize)
                    StreamWriter.Write(packet.Size);
                packet.Write(StreamWriter);
                try
                {
                    Stream.Write(StreamWriterBuffer.GetBuffer(), 0, (int)StreamWriterBuffer.Position);
                }
                catch (IOException ex)
                {
                    throw new PacketManagerException("IO exception while writing to NetworkStream occured", ex);
                }
                StreamWriterBuffer.Seek(0, SeekOrigin.Begin);
            }
            else
                throw new PacketManagerException(String.Format("Tried to send packet \"{0}\" which not listed in PacketTypes", packet));
        }
 
        readonly byte[] _tempBuffer = new byte[4096];
        readonly IPacket[] _packetBuffer = new IPacket[64];
 
        public IPacket[] RecievePackets(out int recievedCount)
        {
            recievedCount = 0;
            if (Stream.DataAvailable) // Если есть данные для чтения из NetworkStream.
            {
                try
                {
                    int readCount = Stream.Read(_tempBuffer, 0, 4096); // Читаем данные из NetworkStream.
                    if (readCount > 0) // Данные из NetworkStream прочитаны.
                    {
                        StreamReaderBuffer.Write(_tempBuffer, 0, readCount); // Запоминаем прочитанные данные
                        StreamReaderBuffer.Seek(-readCount, SeekOrigin.Current);
                        for (; (StreamReaderBuffer.Position - StreamReaderBuffer.Length > 0) && (recievedCount < 64); recievedCount++) // Пока есть данные и количество полученных пакетов < 64
                        {
                            byte packetID = StreamReader.ReadByte(); // Узнаем ID пакета.
                            if (packetID < PacketTypes.Count) // Если известный ID пакета.
                            {
                                _packetBuffer[recievedCount] = Activator.CreateInstance(PacketTypes[packetID]) as IPacket; // Создаем новый пакет необходимого типа.
                                if (_packetBuffer[recievedCount].FixedSize) // Если пакет данного типа имеет фиксированную длину.
                                {
                                    if(_packetBuffer[recievedCount].Size > 0) // Если фиксированный размер пакета больше нуля.
                                    {
                                        if (StreamReaderBuffer.Position - StreamReaderBuffer.Length >= _packetBuffer[recievedCount].Size) // Если данных достаточно для распаковки пакета.
                                        {
                                            try
                                            {
                                                _packetBuffer[recievedCount].Read(StreamReader, _packetBuffer[recievedCount].Size); // Распаковываем пакет.
                                                if (StreamReaderBuffer.Position - StreamReaderBuffer.Length == 0) // Если данных больше нет, завершаем обработку данных.
                                                {
                                                    recievedCount++;
                                                    StreamReaderBuffer.SetLength(0);
                                                    break;
                                                }
                                            }
                                            catch(IPacketException ex) // Если при распаковке пакетов возникла ошибка.
                                            {
                                                throw new PacketManagerException(String.Format("Exception occured while unpacking packet {0} with ID {1} and size {2}", _packetBuffer[recievedCount], packetID), ex);
                                            }
                                        }
                                        else // Данных не достаточно для распаковки пакета, завершаем обработку данных.
                                        {
                                            StreamReaderBuffer.Seek(-1, SeekOrigin.Current);
                                            break;
                                        }
                                    }
                                    else // Пакет прочитан (Размер фиксированный и он равен нулю)
                                    {
                                        if (StreamReaderBuffer.Position - StreamReaderBuffer.Length == 0) // Если данных больше нет, то завершаем обработку данных.
                                        {
                                            recievedCount++;
                                            StreamReaderBuffer.SetLength(0);
                                            break;
                                        }
                                    }
                                } 
                                else // Пакет имеет переменную длину.
                                {
                                    if (StreamReaderBuffer.Position - StreamReaderBuffer.Length >= 2) // Если можем считать размер пакета.
                                    {
                                        ushort packetSize = StreamReader.ReadUInt16(); // Размер пакета.
                                        if (StreamReaderBuffer.Position - StreamReaderBuffer.Length >= packetSize) // Если данных достаточно для распаковки пакета.
                                        {
                                            try
                                            {
                                                _packetBuffer[recievedCount] = Activator.CreateInstance(PacketTypes[packetID]) as IPacket; // Создаем новый пакет необходимого типа.
                                                _packetBuffer[recievedCount].Read(StreamReader, packetSize); // Распаковываем пакет.
                                                if (StreamReaderBuffer.Position - StreamReaderBuffer.Length == 0) // Если данных больше нет, то завершаем обработку данных.
                                                {
                                                    recievedCount++;
                                                    StreamReaderBuffer.SetLength(0);
                                                    break;
                                                }
                                            }
                                            catch (IPacketException ex) // Если при распаковке пакета возникла ошибка.
                                            {
                                                throw new PacketManagerException(String.Format("Exception occured while unpacking packet {0} with ID {1} and size {2}", _packetBuffer[recievedCount], packetID), ex);
                                            }
                                        }
                                        else // Данных не достаточно для распаковки, завершаем обработку данных.
                                        {
                                            StreamReaderBuffer.Seek(-3, SeekOrigin.Current);
                                            break;
                                        }
                                    }
                                    else // Не можем считать размер пакета, завершаем обработку данных.
                                    {
                                        StreamReaderBuffer.Seek(-1, SeekOrigin.Current);
                                        break;
                                    }
                                }
                            }
                            else // Неизвестный ID пакета.
                                throw new PacketManagerException(String.Format("PacketManager got packet with unknown ID:{0}", packetID));
                        }
                        if (recievedCount > 0)
                            return _packetBuffer;
                        else
                            return null;
                    }
                    else // Не смогли прочитать данные из NetworkStream.
                        throw new PacketManagerException("Recieved 0 bytes of data, looks like Socket is closed.");
                }
                catch (IOException ex) // Во время чтения данных из NetworkStream произошла ошибка.
                {
                    throw new PacketManagerException("IO exception while writing to NetworkStream occured", ex);
                }
 
            } // Данные для чтения из NetworkStream отсутствуют.
            else
                return null;
        }
        
        /// <summary>
        /// Создает новый экземпляр объекта на основе существующего подключения.
        /// </summary>
        /// <param name="netStream">Поток открытого TCP подключения.</param>
        public PacketManager(NetworkStream netStream)
        {
            Stream = netStream;
            StreamWriterBuffer = new MemoryStream(4096);
            StreamReaderBuffer = new MemoryStream(4096);
            StreamReader = new BinaryReader(StreamReaderBuffer);
            StreamWriter = new BinaryWriter(StreamWriterBuffer);
        }
    }
 
    class Exec
    {
        static int Main(string[] args)
        {
            Console.ReadKey();
            return 0;
        }
    }
}
Не изобретаю ли я тут велосипед?
Я вот думаю, может, рациональнее будет добавить в интерфейс пакетов свойство PacketID, чтобы пакет сам назначал себе ID (тогда можно писать структуры пакетов не в данной сборке). И сделать нечто вроде PacketManager.RegisterPacket(IPacket)?

Решение задачи: «Обработчик пакетов на основе TCP NetworkStream»

textual
Листинг программы
public IPacket[] RecievePackets(out int recievedCount)
        {
            recievedCount = 0;
            if (Stream.DataAvailable) // Если есть данные для чтения из NetworkStream.
            {
                try
                {
                    int readCount = Stream.Read(_tempBuffer, 0, 4096); // Читаем данные из NetworkStream.
                    if (readCount > 0) // Данные из NetworkStream прочитаны.
                    {
                        long readStart = StreamReaderBuffer.Position; // Запоминаем позицию каретки
                        StreamReaderBuffer.Seek(0, SeekOrigin.End); // Ставим картеку в конец.
                        StreamReaderBuffer.Write(_tempBuffer, 0, readCount); // Запоминаем прочитанные данные.
                        StreamReaderBuffer.Seek(readStart, SeekOrigin.Begin); // Ставим каретку в позицию для чтения.
                        for (; (StreamReaderBuffer.Position - StreamReaderBuffer.Length > 0) && (recievedCount < 64); recievedCount++) // Пока есть данные и количество полученных пакетов < 64
                        {
                            byte packetID = StreamReader.ReadByte(); // Узнаем ID пакета.
                            if (RegisteredPackets[packetID] != null) // Если известный ID пакета.
                            {
                                _packetBuffer[recievedCount] = Activator.CreateInstance(RegisteredPackets[packetID]) as IPacket; // Создаем новый пакет необходимого типа.
                                if (_packetBuffer[recievedCount].FixedSize) // Если пакет данного типа имеет фиксированную длину.
                                {
                                    if(_packetBuffer[recievedCount].Size > 0) // Если фиксированный размер пакета больше нуля.
                                    {
                                        if (StreamReaderBuffer.Position - StreamReaderBuffer.Length >= _packetBuffer[recievedCount].Size) // Если данных достаточно для распаковки пакета.
                                        {
                                            try
                                            {
                                                _packetBuffer[recievedCount].Read(StreamReader, _packetBuffer[recievedCount].Size); // Распаковываем пакет.
                                                if (StreamReaderBuffer.Position - StreamReaderBuffer.Length == 0) // Если данных больше нет, завершаем обработку данных.
                                                {
                                                    recievedCount++;
                                                    StreamReaderBuffer.SetLength(0);
                                                    break;
                                                }
                                            }
                                            catch(IPacketException ex) // Если при распаковке пакетов возникла ошибка.
                                            {
                                                throw new PacketManagerException(String.Format("Exception occured while unpacking packet {0} with ID {1} and size {2}", _packetBuffer[recievedCount], packetID), ex);
                                            }
                                        }
                                        else // Данных не достаточно для распаковки пакета, завершаем обработку данных.
                                        {
                                            StreamReaderBuffer.Seek(-1, SeekOrigin.Current);
                                            break;
                                        }
                                    }
                                    else // Пакет прочитан (Размер фиксированный и он равен нулю)
                                    {
                                        if (StreamReaderBuffer.Position - StreamReaderBuffer.Length == 0) // Если данных больше нет, то завершаем обработку данных.
                                        {
                                            recievedCount++;
                                            StreamReaderBuffer.SetLength(0);
                                            break;
                                        }
                                    }
                                } 
                                else // Пакет имеет переменную длину.
                                {
                                    if (StreamReaderBuffer.Position - StreamReaderBuffer.Length >= 2) // Если можем считать размер пакета.
                                    {
                                        ushort packetSize = StreamReader.ReadUInt16(); // Размер пакета.
                                        if (StreamReaderBuffer.Position - StreamReaderBuffer.Length >= packetSize) // Если данных достаточно для распаковки пакета.
                                        {
                                            try
                                            {
                                                _packetBuffer[recievedCount] = Activator.CreateInstance(RegisteredPackets[packetID]) as IPacket; // Создаем новый пакет необходимого типа.
                                                _packetBuffer[recievedCount].Read(StreamReader, packetSize); // Распаковываем пакет.
                                                if (StreamReaderBuffer.Position - StreamReaderBuffer.Length == 0) // Если данных больше нет, то завершаем обработку данных.
                                                {
                                                    recievedCount++;
                                                    StreamReaderBuffer.SetLength(0);
                                                    break;
                                                }
                                            }
                                            catch (IPacketException ex) // Если при распаковке пакета возникла ошибка.
                                            {
                                                throw new PacketManagerException(String.Format("Exception occured while unpacking packet {0} with ID {1} and size {2}", _packetBuffer[recievedCount], packetID), ex);
                                            }
                                        }
                                        else // Данных не достаточно для распаковки, завершаем обработку данных.
                                        {
                                            StreamReaderBuffer.Seek(-3, SeekOrigin.Current);
                                            break;
                                        }
                                    }
                                    else // Не можем считать размер пакета, завершаем обработку данных.
                                    {
                                        StreamReaderBuffer.Seek(-1, SeekOrigin.Current);
                                        break;
                                    }
                                }
                            }
                            else // Неизвестный ID пакета.
                                throw new PacketManagerException(String.Format("PacketManager got packet with unknown ID:{0}", packetID));
                        }
                        if (recievedCount > 0)
                            return _packetBuffer;
                        else
                            return null;
                    }
                    else // Не смогли прочитать данные из NetworkStream.
                        throw new PacketManagerException("Recieved 0 bytes of data, looks like Socket is closed.");
                }
                catch (IOException ex) // Во время чтения данных из NetworkStream произошла ошибка.
                {
                    throw new PacketManagerException("IO exception while writing to NetworkStream occured", ex);
                }
 
            } // Данные для чтения из NetworkStream отсутствуют.
            else
                return null;
        }

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


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

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

5   голосов , оценка 4 из 5