Порядок фрагментов SocketAsyncEventArgs
У меня возникла проблема с порядком получения пакетов сокетов получения с помощью SocketAsyncEventArgs. Суть моей проблемы заключается в том, что когда клиент отправляет пакет на сервер, сервер получит пакет с фрагментами нестандартного размера, и они будут обработаны в произвольном порядке. Это очевидно означает, что пакет не может быть декодирован моим приложением, которое затем портит весь разговор.
Так, например, клиент отправит полный пакет, используя метод Socket.NetworkStream.Write():
[-------PACKET-------]
Сервер, использующий SocketAsyncEventArgs, получит асинхронные обратные вызовы получения для двух отдельных пакетов, но последний фрагмент пакета будет обработан первым:
First packet: ET-------]
Second packet: [-----PACK--
Это не происходит на всех пакетах, и я не смог точно воспроизвести его в зависимости от размера пакета или времени. Я реализую протоколы отправки / подтверждения связи, чтобы клиент не отправлял другой пакет, пока сервер не подтвердит, что последний пакет был успешно получен, поэтому не может быть, что я перегружаю сервер.
Самым неприятным моментом является то, что Socket.Available на сервере всегда равно нулю, что в соответствии с документами означает, что ничего не доступно для чтения
If you are using a non-blocking Socket, Available is a good way to determine whether data is
queued for reading, before calling Receive. The available data is the total amount of data
queued in the network buffer for reading. If no data is queued in the network buffer,
Available returns 0.
С доступным в нуле, SocketEventArgs.Count, кажется, не предоставляет ничего ценного, и смещение связано с буфером приема, а не там, где оно было основано на фактическом потоке данных. Я не уверен, как я могу поместить эти фрагменты в порядок.
Я предполагаю, что проблема заключается в асинхронном обратном вызове для первой части пакета, который прерывается вторым обратным вызовом, который полностью обрабатывается, а затем возвращается к первому фрагменту. Проблема в том, что я не могу синхронизировать весь обратный вызов (желательно.NET имеет синхронизированные функции, такие как Java). И даже если бы я это сделал, кажется, что это сводит на нет преимущества асинхронного обратного вызова в первую очередь.
Что я делаю неправильно, что заставляет их приходить в неправильном порядке или что я могу сделать, чтобы они обрабатывались правильно?
1 ответ
Я не совсем уверен, что вы делаете из некоторых ваших заявлений. Вы пишете, что используете SocketAsyncEventArgs
но пытаются справиться со странными вещами API, такими как .Count
или же .Available
, Если ваш тип сокета TCP, вы, вероятно, делаете что-то не так, потому что пакеты всегда будут в правильном порядке. Они могут быть фрагментированы даже до кусков размером всего в один байт, но порядок будет правильным. Это в значительной степени то, что TCP все.
Поскольку вы не предоставили никакого кода и основываясь на своем утверждении, я полагаю, что лучше всего предоставить вам некоторую SSCE, чтобы вы начали.
Образец находится на C#, но должен применяться к VB.net. Проверьте комментарии в коде, чтобы увидеть, откуда на самом деле получить полученные данные. Реализация запишет полученные данные в консоль и отправит их обратно клиенту. Эхо-серверы делают отличные образцы!
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace SaeaSample
{
public class Program
{
static void Main()
{
var server = new Server(new IPEndPoint(IPAddress.Any, 12345));
// ugly sample clients
Parallel.For(0, 4, i =>
{
using (var client = new TcpClient("localhost", 12345))
using (var stream = client.GetStream())
using (var writer = new BinaryWriter(stream))
using (var reader = new BinaryReader(stream))
{
var text = "Hello Async-Server!";
var message = Encoding.UTF8.GetBytes(text);
Console.WriteLine("s: {0}: {1}", i, text);
writer.Write(message);
var roundtrip = reader.ReadBytes(message.Length);
Console.WriteLine("r: {0}: {1}", i, Encoding.UTF8.GetString(roundtrip));
}
});
Console.ReadLine();
}
}
public class Server
{
private const int readBufferSize = 8192;
private const int sendBufferSize = readBufferSize;
// just have a fixed number of clients instead of
// pooling for the sake of being an example
private const int maxClients = 4;
private const int maxQueue = 10;
private readonly byte[] buffer = new byte[maxClients * (readBufferSize + sendBufferSize)];
private readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
public Server(IPEndPoint localEndPoint)
{
socket.Bind(localEndPoint);
socket.Listen(maxQueue);
for (int i = 0; i < maxClients; i++)
{
var client = new UserToken(i);
client.RecvArgs.Completed += completed;
client.SendArgs.Completed += completed;
Console.WriteLine("accepting on client slot {0}", client.Slot);
if (!socket.AcceptAsync(client.RecvArgs))
{
completed(this, client.RecvArgs);
}
}
}
private void completed(object sender, SocketAsyncEventArgs e)
{
var client = (UserToken)e.UserToken;
// socket operation had success
if (e.SocketError == SocketError.Success)
{
// new client connected
if (e.LastOperation == SocketAsyncOperation.Accept)
{
onAccept(client);
}
// either send or received worked
else if (e.BytesTransferred > 0)
{
if (e.LastOperation == SocketAsyncOperation.Receive)
{
onReceived(client);
}
else if (e.LastOperation == SocketAsyncOperation.Send)
{
onSend(client);
}
// should never happen, handle gracefully
else
{
onOther(client);
}
}
// don't handle anything else
else
{
onOther(client);
}
}
// socket error occured
else
{
onOther(client);
}
}
private void onAccept(UserToken client)
{
Console.WriteLine("client slot {0} connected client from {1}", client.Slot, client.RecvArgs.AcceptSocket.RemoteEndPoint);
// once accepted, start receiving
client.RecvArgs.SetBuffer(buffer, client.Slot * (readBufferSize + sendBufferSize), readBufferSize);
if (!client.RecvArgs.AcceptSocket.ReceiveAsync(client.RecvArgs))
{
completed(this, client.RecvArgs);
}
}
private void onReceived(UserToken client)
{
// echo whatever we got
var builder = new StringBuilder();
// here is the important part
for (int i = 0; i < client.RecvArgs.BytesTransferred; i++)
{
// offset the buffer and echo in hex
builder.Append(client.RecvArgs.Buffer[client.Slot * (readBufferSize + sendBufferSize) + i].ToString("x2"));
}
Console.WriteLine("received {0} bytes from client slot {1}: {2}", client.RecvArgs.BytesTransferred, client.Slot, builder.ToString());
// send data back ... this is an echo server after all
client.SendArgs.SetBuffer(client.RecvArgs.Buffer, client.Slot * (readBufferSize + sendBufferSize) + readBufferSize, client.RecvArgs.BytesTransferred);
Buffer.BlockCopy(client.RecvArgs.Buffer, client.RecvArgs.Offset, client.SendArgs.Buffer, client.SendArgs.Offset, client.RecvArgs.BytesTransferred);
if (!client.RecvArgs.AcceptSocket.SendAsync(client.SendArgs))
{
completed(this, client.SendArgs);
}
}
private void onSend(UserToken client)
{
Console.WriteLine("sent {0} bytes back to client slot {1}", client.SendArgs.BytesTransferred, client.Slot);
// start receiving again
if (!client.RecvArgs.AcceptSocket.ReceiveAsync(client.RecvArgs))
{
completed(this, client.RecvArgs);
}
}
private void onOther(UserToken client)
{
Console.WriteLine("disconnecting client slot {0}", client.Slot);
// just close the connection and accept again
client.RecvArgs.SetBuffer(null, 0, 0);
if (client.RecvArgs.AcceptSocket != null) {
client.RecvArgs.AcceptSocket.Dispose();
client.RecvArgs.AcceptSocket = null;
}
Console.WriteLine("accepting on client slot {0}", client.Slot);
if (!socket.AcceptAsync(client.RecvArgs))
{
completed(this, client.RecvArgs);
}
}
}
public class UserToken
{
public readonly int Slot;
public readonly SocketAsyncEventArgs RecvArgs = new SocketAsyncEventArgs();
public readonly SocketAsyncEventArgs SendArgs = new SocketAsyncEventArgs();
public UserToken(int slot)
{
Slot = slot;
RecvArgs.UserToken = this;
SendArgs.UserToken = this;
}
}
}
Также обратите внимание, что, поскольку этот код является асинхронным, выходные данные консоли могут быть или не быть всегда в порядке. Вы можете уменьшить константы размера буфера для чтения и записи с 8192 до 1. Пакеты будут отправляться побайтово в обоих направлениях, но все равно будут в порядке.
Для более подробных объяснений MSDN всегда является хорошей отправной точкой.