Я пишу клиент-серверное приложение.Кажется, это хорошо работает при подключении с использованием адреса обратной связи, но при подключении по локальной сети или через Интернет у меня возникает очень странная проблема, которую я просто не могу понять.
Все данные, передаваемые между сервером и клиентом, содержатся в классе под названием «DataPacket».Затем этот класс сериализуется в двоичный файл, чтобы его можно было отправлять с использованием сокетов.
Перед отправкой пакета присоединяется 4-байтовый заголовок, который содержит длину пакета в байтах, чтобы получатель знал, сколько байтовожидать, прежде чем он может быть десериализован.Проблема в том, что в какой-то момент получатель считает, что он ожидает гораздо больше данных, чем фактически отправлено в заголовке.Иногда эта цифра исчисляется миллионами байтов, что вызывает серьезные проблемы.
Вот как отправляются данные:
public override void Send(DataPacket packet)
{
byte[] content = packet.Serialize();
byte[] header = BitConverter.GetBytes(content.Length);
List<Byte> bytes = new List<Byte>();
bytes.AddRange(header);
bytes.AddRange(content);
if (socket.Connected)
{
socket.BeginSend(bytes.ToArray(), 0, bytes.Count, SocketFlags.None, OnSendPacket, socket);
}
}
На стороне сервера каждому клиенту назначается StateObject- это класс, который содержит пользовательский сокет, буфер и любые данные, которые были получены до сих пор, которые являются неполными.
Вот так выглядит класс StateObject:
public class StateObject
{
public const int HeaderSize = 4;
public const int BufferSize = 8192;
public Socket Socket { get; private set; }
public List<Byte> Data { get; set; }
public byte[] Header { get; set; }
public byte[] Buffer { get; set; }
public StateObject(Socket sock)
{
Socket = sock;
Data = new List<Byte>();
Header = new byte[HeaderSize];
Buffer = new byte[BufferSize];
}
}
Вот как используется класс StateObject:
private void OnClientConnect(IAsyncResult result)
{
Socket serverSock = (Socket)result.AsyncState;
Socket clientSock = serverSock.EndAccept(result);
StateObject so = new StateObject(clientSock);
so.Socket.BeginReceive(so.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnReceiveData, so);
}
Когда некоторые данные получены от клиента,EndReceive называется.Если StateObject.Data пуст, то предполагается, что это первая часть нового пакета, поэтому заголовок проверяется.Если количество полученных байтов меньше размера, указанного в заголовке, я снова вызываю BeginReceive, повторно используя тот же StateObject.
Вот код для OnReceiveData:
private void OnReceiveData(IAsyncResult result)
{
StateObject state = (StateObject)result.AsyncState;
int bytes = state.Socket.EndReceive(result);
if (bytes > 0)
{
state.ProcessReceivedData(bytes);
if (state.CheckDataOutstanding())
{
//Wait until all data has been received.
WaitForData(state);
}
else
{
ThreadPool.QueueUserWorkItem(OnPacketReceived, state);
//Continue receiving data from client.
WaitForData(new StateObject(state.Socket));
}
}
}
Как видно выше, сначала я вызываю state.ProcessReceivedData () - здесь я отсоединяю заголовок от данных, а затем перемещаю данные из буфера:
public void ProcessReceivedData(int byteCount)
{
int offset = 0;
if (Data.Count == 0)
{
//This is the first part of the message so get the header.
System.Buffer.BlockCopy(Buffer, 0, Header, 0, HeaderSize);
offset = HeaderSize;
}
byte[] temp = new byte[byteCount - offset];
System.Buffer.BlockCopy(Buffer, offset, temp, 0, temp.Length);
Data.AddRange(temp);
}
Затем вызываю CheckDataOutstanding ()сравнить количество полученных байтов с размером в заголовке.Если они совпадают, тогда я могу безопасно десериализовать данные:
public bool CheckDataOutstanding()
{
int totalBytes = BitConverter.ToInt32(Header, 0);
int receivedBytes = Data.Count;
int outstanding = totalBytes - receivedBytes;
return outstanding > 0;
}
Проблема в том, что каким-то образом значение в заголовке оказывается не таким, как было отправлено.Это происходит случайно, иногда на сервере, а иногда на клиенте.Из-за случайности и общей сложности отладки как сервера, так и клиента по сети, я нахожу практически невозможным это выяснить.
Есть ли что-то очевидное или глупое, что я здесь упускаю?