Лучший способ принять несколько клиентов TCP? - PullRequest
5 голосов
/ 18 августа 2011

У меня есть клиент-серверная инфраструктура.В настоящее время они используют TcpClient и TcpListener для отправки полученных данных между всеми клиентами и сервером.

То, что я сейчас делаю, - это когда данные принимаются (в своем собственном потоке), они помещаются в очередь для обработки другим потоком, чтобы освободить сокет, чтобы он был готов и открыт для получения новых данных.

                // Enter the listening loop.
                while (true)
                {
                    Debug.WriteLine("Waiting for a connection... ");

                    // Perform a blocking call to accept requests.
                    using (client = server.AcceptTcpClient())
                    {
                        data = new List<byte>();

                        // Get a stream object for reading and writing
                        using (NetworkStream stream = client.GetStream())
                        {
                            // Loop to receive all the data sent by the client.
                            int length;

                            while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
                            {
                                var copy = new byte[length];
                                Array.Copy(bytes, 0, copy, 0, length);
                                data.AddRange(copy);
                            }
                        }
                    }

                    receivedQueue.Add(data);
                }

Однако я хотел выяснить, есть ли лучший способ сделать это.Например, если есть 10 клиентов, и все они хотят одновременно отправлять данные на сервер, один из них пройдет, в то время как все остальные потерпят неудачу. Или, если один клиент имеет медленное соединение и перегружает сокет, все остальные коммуникации будут остановлены.,

Нет ли способа получить данные от всех клиентов одновременно и добавить полученные данные в очередь для обработки после завершения загрузки?

Ответы [ 4 ]

15 голосов
/ 18 августа 2011

Итак, вот ответ, который поможет вам начать - это более начальный уровень, чем мой пост в блоге .

.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() ...
}

Каждое клиентское соединение должно отслеживаться классом вашего сервера (чтобы вы могли без проблем отключать их при выключении сервера, а также искать / искать их).

1 голос
/ 18 августа 2011

Вы должны использовать асинхронный метод чтения данных, например:

// Enter the listening loop.
while (true)
{
    Debug.WriteLine("Waiting for a connection... ");

    client = server.AcceptTcpClient();

    ThreadPool.QueueUserWorkItem(new WaitCallback(HandleTcp), client);
}

private void HandleTcp(object tcpClientObject)
{
    TcpClient client = (TcpClient)tcpClientObject;
    // Perform a blocking call to accept requests.

    data = new List<byte>();

    // Get a stream object for reading and writing
    using (NetworkStream stream = client.GetStream())
    {
        // Loop to receive all the data sent by the client.
        int length;

        while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
        {
            var copy = new byte[length];
            Array.Copy(bytes, 0, copy, 0, length);
            data.AddRange(copy);
        }
    }

    receivedQueue.Add(data);
} 

Также вам следует рассмотреть возможность использования AutoResetEvent или ManualResetEvent, чтобы получать уведомления при добавлении новых данных в коллекцию, чтобы поток, обрабатывающий данные, знал, когда данные получены, и если вы используете 4.0, вам лучше переключитесь на использование BlockingCollection вместо Queue.

1 голос
/ 18 августа 2011

Для достижения этой цели вы должны использовать асинхронное программирование сокетов. Взгляните на пример , предоставленный MSDN.

0 голосов
/ 18 августа 2011

Обычно я использую пул потоков с несколькими потоками. При каждом новом соединении я запускаю обработку соединения (в вашем случае - все, что вы делаете в предложении using) в одном из потоков пула.

Тем самым вы достигаете обе производительности, поскольку разрешаете несколько одновременно принятых соединений, а также ограничиваете количество ресурсов (потоков и т. Д.), Которые вы выделяете для обработки входящих соединений.

У вас есть хороший пример здесь

Удачи

...