C# Socket.BeginReceive/EndReceive
В каком порядке вызываются функции Socket.BeginReceive/EndReceive?
Например, я вызываю BeginReceive дважды, один раз, чтобы узнать длину сообщения, и второй раз, чтобы получить само сообщение. Теперь сценарий таков: для каждого отправляемого сообщения я начинаю ждать его завершения (фактически подтверждение отправленного сообщения, а также ожидаю завершения действия после получения подтверждения), поэтому я вызываю BeginReceive для каждого BeginSend, но в каждый обратный вызов BeginReceive, я проверяю, получаю ли я длину или сообщение. Если я получаю сообщение и получил его полностью, я вызываю другой BeginReceive, чтобы получить завершение действия. Теперь это где вещи не синхронизированы. Потому что один из моих обратных вызовов приема получает байты, которые он интерпретирует как длину сообщения, хотя на самом деле это само сообщение.
Теперь, как мне это решить?
РЕДАКТИРОВАТЬ: Это вопрос C#.NET:)
Вот код, в основном он слишком большой, извините за это
public void Send(string message)
{
try
{
bytesSent = 0;
writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
writeDataBuffer = WrapMessage(writeDataBuffer);
messageSendSize = writeDataBuffer.Length;
clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
new AsyncCallback(SendComplete), clientSocket);
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void WaitForData()
{
try
{
if (!messageLengthReceived)
{
clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
}
}
public void Send(string message)
{
try
{
bytesSent = 0;
writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
writeDataBuffer = WrapMessage(writeDataBuffer);
messageSendSize = writeDataBuffer.Length;
clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
new AsyncCallback(SendComplete), clientSocket);
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void WaitForData()
{
try
{
if (! messageLengthReceived)
{
clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
}
else
{
clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, messageLength - bytesReceived,
SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
}
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void RecieveComplete(IAsyncResult result)
{
try
{
Socket socket = result.AsyncState as Socket;
bytesReceived = socket.EndReceive(result);
if (! messageLengthReceived)
{
if (bytesReceived != MESSAGE_LENGTH_SIZE)
{
WaitForData();
return;
}
// unwrap message length
int length = BitConverter.ToInt32(receiveDataBuffer, 0);
length = IPAddress.NetworkToHostOrder(length);
messageLength = length;
messageLengthReceived = true;
bytesReceived = 0;
// now wait for getting the message itself
WaitForData();
}
else
{
if (bytesReceived != messageLength)
{
WaitForData();
}
else
{
string message = Encoding.ASCII.GetString(receiveDataBuffer);
MessageBox.Show(message);
bytesReceived = 0;
messageLengthReceived = false;
// clear buffer
receiveDataBuffer = new byte[AsyncClient.BUFFER_SIZE];
WaitForData();
}
}
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
public void SendComplete(IAsyncResult result)
{
try
{
Socket socket = result.AsyncState as Socket;
bytesSent = socket.EndSend(result);
if (bytesSent != messageSendSize)
{
messageSendSize -= bytesSent;
socket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
new AsyncCallback(SendComplete), clientSocket);
return;
}
// wait for data
messageLengthReceived = false;
bytesReceived = 0;
WaitForData();
}
catch (SocketException socketException)
{
MessageBox.Show(socketException.Message);
}
}
5 ответов
Порядок во времени должен быть:
BeginReceive
для длины сообщенияEndReceive
для завершения № 1BeginReceive
для тела сообщенияEndReceive
для завершения № 3
Например, не используя обратные вызовы, вы могли бы иметь:
var sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res = socket.EndReceive(sync);
sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res2 = socket.EndReceive(sync);
Но тогда вам лучше использовать Receive
!
Я думаю, что вам может быть проще использовать отдельные обработчики для двух разных приемов:
... Start(....) {
sync = socket.BeginReceive(.... MessageLengthReceived, null);
}
private void MessageLengthReceived(IAsyncResult sync) {
var len = socket.EndReceive(sync);
// ... set up buffer etc. for message receive
sync = socket.BeginReceive(... MessageReceived, null);
}
private void MessageReceived(IAsyncResult sync) {
var len = socket.EndReceive(sync);
// ... process message
}
Конечное размещение всех связанных в объекте состояния и их передача (в завершение делегировать доступ через IAsyncResult.AsyncState
) из BeginReceive может упростить задачу, но она отходит от линейного мышления императивного кода и полного охвата подхода, основанного на событиях.
Приложение 2012 года:
Версия.NET 4.5
С поддержкой асинхронности в C#5 появилась новая опция. При этом компилятор генерирует ручные продолжения (отдельные методы обратного вызова) и замыкания (состояние) из встроенного кода. Однако есть две вещи, которые нужно обойти:
В то время как
System.Net.Sockets.Socket
имеет различные…Async
методы это для асинхронного шаблона, основанного на событиях, а неTask
на основе шаблона, который C#5await
использует. Решение: использоватьTaskFactory.FromAsync
чтобы получить одинTask<T>
изBegin…
End…
пара.TaskFactory.FromAsync
поддерживает передачу до трех дополнительных аргументов (в дополнение к обратному вызову и состоянию) дляBegin…
, Решение: лямбда, принимающая ноль дополнительных аргументов, имеет правильную сигнатуру, а C# даст нам правильное закрытие для передачи аргументов.
Следовательно (и более полно реализовано с Message
будучи другим типом, который обрабатывает преобразование из начальной отправки длины, закодированной в некотором фиксированном количестве байтов, тогда байты контента в длину для буфера контента):
private async Task<Message> ReceiveAMessage() {
var prefix = new byte[Message.PrefixLength];
var revcLen = await Task.Factory.FromAsync(
(cb, s) => clientSocket.BeginReceive(prefix, 0, prefix.Length, SocketFlags.None, cb, s),
ias => clientSocket.EndReceive(ias),
null);
if (revcLen != prefix.Length) { throw new ApplicationException("Failed to receive prefix"); }
int contentLength = Message.GetLengthFromPrefix(prefix);
var content = new byte[contentLength];
revcLen = await Task.Factory.FromAsync(
(cb, s) => clientSocket.BeginReceive(content, 0, content.Length, SocketFlags.None, cb, s),
ias => clientSocket.EndReceive(ias),
null);
if (revcLen != content.Length) { throw new ApplicationException("Failed to receive content"); }
return new Message(content);
}
Возможно, что вы хотите сделать, это связать ваши обратные вызовы:
псевдокод:
// read the first 2 bytes as message length
BeginReceive(msg,0,2,-,-,new AsyncCallback(LengthReceived),-)
LengthReceived(ar) {
StateObject so = (StateObject) ar.AsyncState;
Socket s = so.workSocket;
int read = s.EndReceive(ar);
msg_length = GetLengthFromBytes(so.buffer);
BeginReceive(so.buffer,0,msg_length,-,-,new AsyncCallback(DataReceived),-)
}
DataReceived(ar) {
StateObject so = (StateObject) ar.AsyncState;
Socket s = so.workSocket;
int read = s.EndReceive(ar);
ProcessMessage(so.buffer);
BeginReceive(so.buffer,0,2,-,-,new AsyncCallback(LengthReceived),-)
}
см. http://msdn.microsoft.com/en-us/library/system.asynccallback.aspx для правильных примеров
Это поможет, если вы опишите структуру сообщения, которое вы отправляете.
Пока у вас есть только один BeginReceive(), он завершится и даст вам следующие доступные байты данных в сети. Если у вас есть несколько неоплаченных одновременно, все ставки отменены, потому что.net не гарантирует, что завершение будет выполнено в любом заданном порядке.
Обычно методы BeginXXX указывают на асинхронную операцию, и вы, кажется, хотите сделать это синхронно.
Если вам действительно нужен синхронный клиент / сервер, может быть, это поможет http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html
Как уже говорили другие, не используйте глобальные переменные здесь - используйте класс для состояния сокета. Что-то вроде:
public class StateObject
{
public const int DEFAULT_SIZE = 1024; //size of receive buffer
public byte[] buffer = new byte[DEFAULT_SIZE]; //receive buffer
public int dataSize = 0; //data size to be received
public bool dataSizeReceived = false; //received data size?
public StringBuilder sb = new StringBuilder(); //received data String
public int dataRecieved = 0;
public Socket workSocket = null; //client socket.
public DateTime TimeStamp; //timestamp of data
} //end class StateObject
Перед повторной отправкой сообщения вы должны проверить сокет... у вас может быть исключение сокета.
Вы, вероятно, должны иметь возвращение; после вашего вызова WaitForData в блоке "if" объекта ReceiveComplete.
Тимоти Пратли сказал это выше, одна ошибка будет в байтах. Получена во второй раз. Каждый раз вы измеряете только bytesReceived из этого EndReceive, а затем сравниваете его с messageLength. Вам нужно сохранить сумму всех байтов, полученных.
И ваша самая большая ошибка заключается в том, что при первом вызове ReceiveComplete вы учитываете тот факт, что сообщение может (скорее всего) содержать больше данных, чем просто размер сообщения - вероятно, оно также будет содержать половину сообщения. Вам нужно очистить размер данных, а затем также сохранить оставшуюся часть сообщения в вашей переменной сообщения.