Обработчик пакетов на основе 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;
}