Обработчик пакетов на основе 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; }
ИИ поможет Вам:
- решить любую задачу по программированию
- объяснить код
- расставить комментарии в коде
- и т.д