Итак, вот ответ, который поможет вам начать - это более начальный уровень, чем мой пост в блоге .
.Net имеет асинхронный шаблон, который вращается вокруг Begin * и End* вызов.Например - BeginReceive
и EndReceive
.У них почти всегда есть не асинхронный аналог (в данном случае Receive
);и достичь точно такой же цели.
Самое важное, что нужно помнить, это то, что сокеты делают больше, чем просто выполняют асинхронный вызов - они предоставляют что-то под названием IOCP (порты завершения ввода-вывода, Linux / Mono имеют эти два, ноЯ забыл название), которое крайне важно использовать на сервере;Суть того, что делает IOCP, заключается в том, что ваше приложение не потребляет поток, пока оно ожидает данных.
Как использовать шаблон начала / конца
Каждое начало* метод будет иметь ровно 2 дополнительных аргумента по сравнению с не асинхронным аналогом.Первый - AsyncCallback, второй - объект.Эти два значения означают: «вот метод, который нужно вызвать, когда вы закончите» и «вот некоторые данные, которые мне нужны внутри этого метода».Вызываемый метод всегда имеет одну и ту же сигнатуру, внутри этого метода вы вызываете аналог End *, чтобы получить то, что было бы результатом, если бы вы делали это синхронно.Так, например:
private void BeginReceiveBuffer()
{
_socket.BeginReceive(buffer, 0, buffer.Length, BufferEndReceive, buffer);
}
private void EndReceiveBuffer(IAsyncResult state)
{
var buffer = (byte[])state.AsyncState; // This is the last parameter.
var length = _socket.EndReceive(state); // This is the return value of the method call.
DataReceived(buffer, 0, length); // Do something with the data.
}
Здесь происходит то, что .Net начинает ожидать данные из сокета, как только он получает данные, он вызывает EndReceiveBuffer
и проходит через «пользовательские данные» (в данном случаеbuffer
) к нему через state.AsyncResult
.Когда вы звоните EndReceive
, он возвращает вам длину полученных данных (или выдает исключение в случае сбоя).
Лучший шаблон для сокетов
Эта форма даст вам центральную обработку ошибок - ее можно использовать в любом месте, где асинхронный шаблон оборачивает потоковую «вещь» (например, TCP приходит в том порядке, в котором он был отправлен, поэтому его можно рассматривать как объект Stream
).
private Socket _socket;
private ArraySegment<byte> _buffer;
public void StartReceive()
{
ReceiveAsyncLoop(null);
}
// Note that this method is not guaranteed (in fact
// unlikely) to remain on a single thread across
// async invocations.
private void ReceiveAsyncLoop(IAsyncResult result)
{
try
{
// This only gets called once - via StartReceive()
if (result != null)
{
int numberOfBytesRead = _socket.EndReceive(result);
if(numberOfBytesRead == 0)
{
OnDisconnected(null); // 'null' being the exception. The client disconnected normally in this case.
return;
}
var newSegment = new ArraySegment<byte>(_buffer.Array, _buffer.Offset, numberOfBytesRead);
// This method needs its own error handling. Don't let it throw exceptions unless you
// want to disconnect the client.
OnDataReceived(newSegment);
}
// Because of this method call, it's as though we are creating a 'while' loop.
// However this is called an async loop, but you can see it the same way.
_socket.BeginReceive(_buffer.Array, _buffer.Offset, _buffer.Count, SocketFlags.None, ReceiveAsyncLoop, null);
}
catch (Exception ex)
{
// Socket error handling here.
}
}
Прием нескольких соединений
Обычно вы пишете класс, который содержит ваш сокет и т. Д. (А также ваш асинхронный цикл) и создаете его.для каждого клиента.Так, например:
public class InboundConnection
{
private Socket _socket;
private ArraySegment<byte> _buffer;
public InboundConnection(Socket clientSocket)
{
_socket = clientSocket;
_buffer = new ArraySegment<byte>(new byte[4096], 0, 4096);
StartReceive(); // Start the read async loop.
}
private void StartReceive() ...
private void ReceiveAsyncLoop() ...
private void OnDataReceived() ...
}
Каждое клиентское соединение должно отслеживаться классом вашего сервера (чтобы вы могли без проблем отключать их при выключении сервера, а также искать / искать их).