Обработчик пакетов на основе TCP NetworkStream - C#
Формулировка задачи:
Собственно, хочу сделать так, чтобы данные читались из NetworkStream и записывались MemoryStream.
Проблема в том, что я не могу узнать сколько доступно байт для чтения из NetworkStream и поэтому приходится выделить буфер типа byte[] (так как количество считанных может быть больше чем доступная память для MemoryStream).
Собственно, какое решение посоветуете?
Не изобретаю ли я тут велосипед?
- Не морочить мозги и выделять буфер, так как для современных ОЗУ выделение буфера в несколько килобайт ничтожно.
- Сделать буфер ничтожно маленьким и использовать его многократно.
- Сделать общий буфер и писать в него.
- Использовать некоторую функцию для записи из NetworkStream в MemoryStream напрямую. (которая увеличит размер буфера MemoryStream, при необходимости).
- При большом количестве соединений может кушать памяти гораздо больше чем ожидалось.
- Идет нагрузка на ЦП, считать один раз проще, чем несколько раз.
- Невозможность многопоточного программирования.
- А такая есть?
Или, просто считываем из 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;
- }
ИИ поможет Вам:
- решить любую задачу по программированию
- объяснить код
- расставить комментарии в коде
- и т.д