Как лучше обрабатывать клиентов с помощью TCPListener?

У меня возникла проблема с слишком большим количеством подключений. После нескольких тестов я пришел к выводу, что проблема связана с МОЙ сервер. Тот факт, что порт прослушивания сервера находился слева, должен был сказать мне.

При запуске одного и того же клиентского кода с использованием сервера на другом компьютере у меня не открываются сотни портов. Когда мой сервер на моей локальной машине, я получаю> 200 подключений к моему прослушивающему порту. Мне кажется, я неправильно работаю с клиентами. Мой код ниже, весь несерверный и клиентский код удален.

{
    TcpListener server = null;
    server = new TcpListener(port);
    server.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
    server.Start();

    while (true)
    {
        var client = server.AcceptTcpClient();
        using(var stream = client.GetStream()) {
        ...
        stream.Read(...
        ...
        stream.Write(...
        } //using above should close this.
        client.Close();
    }
    server.Stop();
}

Я изменил код, чтобы использовать асинхронные соединения, и он блокируется во второй раз, когда он выполняет потоковую передачу. Читайте только во время (client.Connected)

    server = new TcpListener(port);
    server.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
    server.Start();
    server.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpClientCallback), server);
    while (true)
        Thread.Sleep(1000); //infinite loop for testing




public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
    Byte[] bytes = new Byte[1024 * 4];
    TcpListener listener = (TcpListener)ar.AsyncState;
    using (TcpClient client = listener.EndAcceptTcpClient(ar))
    {
        using (var stream = client.GetStream())
        using (var ostream = new MemoryStream())
        {
            while (client.Connected)
            {
                int i;
                while ((i = stream.Read(bytes, 0, bytes.Length)) == bytes.Length)
                    ostream.Write(bytes, 0, i);

                ostream.Write(bytes, 0, i);
                szresults = Func(ostream)
                var obuf = Encoding.UTF8.GetBytes(szresults);
                stream.Write(obuf, 0, obuf.Length);
            }
        }
        client.Close();
    }
}

person Community    schedule 23.07.2010    source источник
comment
Взгляните на NetworkStream.DataAvailable   -  person bohdan_trotsenko    schedule 23.07.2010
comment
Я бы тоже сделал while (stream.Read(...) > 0) вместо == bytes.Length   -  person bohdan_trotsenko    schedule 23.07.2010
comment
Что мне делать, если NetworkStream.DataAvailable == 0? и ›0 вызывает проблему, когда я читаю один раз и получаю 10 байт (›0), затем читаю второй раз, и он блокируется   -  person    schedule 23.07.2010
comment
если .DataAvailalble == 0 следует... дождаться данных. В моем приложении я использовал Thread.Sleep(20); ++counter;, и если этот счетчик достигает 500, я завершаю соединение; но я согласен, что это может быть не лучший подход.   -  person bohdan_trotsenko    schedule 23.07.2010
comment
== bytes.Length действительно ошибка. stream.Read() возвращает количество прочитанных байтов. Если он возвращает 100, ваш цикл while завершается без записи этих байтов в ostream.   -  person bohdan_trotsenko    schedule 23.07.2010
comment
Существует ostream.write вне while()   -  person    schedule 23.07.2010
comment
В качестве альтернативы вы можете использовать потоки и выполнять синхронизацию; из 'msdn.microsoft.com/en-us/ library/': * Операции чтения и записи могут выполняться одновременно в экземпляре класса NetworkStream без необходимости синхронизации. Пока существует один уникальный поток для операций записи и один уникальный поток для операций чтения, между потоками чтения и записи не будет перекрестного вмешательства, и синхронизация не потребуется. *   -  person bohdan_trotsenko    schedule 23.07.2010
comment
О... я согласен. Не упомянул об этом; Что ж, тогда наши подходы эквивалентны.   -  person bohdan_trotsenko    schedule 23.07.2010


Ответы (2)


Вы имеете дело с соединением в одном потоке. Это означает, что вы можете активно обрабатывать только одно соединение за раз. Самый простой подход — передать клиента в новый поток и работать с ним там.

Теперь это не очень хорошо масштабируется чрезвычайно — использование полностью асинхронных операций везде позволит вам обслуживать такое же количество клиентов с использованием меньшего количества потоков — но в большинстве случаев это будет работать достаточно хорошо, IME. Это определенно то место, куда я пойду дальше.

В идеале вы не хотите каждый раз создавать новый поток ОС - вероятно, разумно использовать для этого пул системных потоков, чтобы поток можно было повторно использовать после того, как соединение было полностью обработано. Существуют риски, связанные с заполнением пула потоков ОС, но я подозреваю, что это вряд ли вас укусит.

Наконец, я бы поместил TcpClient в оператор using, чтобы убедиться, что вы избавляетесь от него, даже если что-то еще не работает. Конечно, если вы передаете его другому потоку, это должен быть тот поток, который немедленно использует оператор using.

person Jon Skeet    schedule 23.07.2010
comment
Ой! потому что я закрываю соединение! вот почему так много. Интересный. Я думаю, что потоки будут работать хорошо, но я рассмотрю возможность использования ASync (BeginAcceptTcpClient). Я удивлен. TcpClient реализует одноразовые, но НЕ реализует функцию удаления. Похоже, теперь мне нужно проверить с помощью F12, а не .Dis. записывает часть 2 ниже - person ; 23.07.2010
comment
Я сделал быстрый тест, написав goto tryAgain и tryAgain: непосредственно под client.GetStream()) {. Первая строка кода под этим — while ((i = stream.Read(bytes, 0, bytes.Length)) == bytes.Length). Вы хоть представляете, почему мой код там блокируется? Используя данные тампера, я вижу, что запрос POST ajax проходит через мое приложение, но поток просто блокируется. Строка перед goto — это stream.Write(obuf, 0, obuf.Length); После этого я добавил флеш, но безуспешно. Почему эта блокировка? с быстрым тестом кажется, что клиент не получает первого ответа. (он делает почтовый запрос каждую секунду) - person ; 23.07.2010
comment
@acidzombie24: он явно реализует IDisposable. Вы по-прежнему должны иметь возможность использовать оператор using. Я не очень понимаю ваш пример кода здесь - не могли бы вы отредактировать свой вопрос, указав полный код? - person Jon Skeet; 23.07.2010
comment
Я попытался это исправить и заставил использовать асинхронные соединения. Он продолжает блокироваться во второй раз, когда я читаю из потока. Полный код сокета выше. - person ; 23.07.2010
comment
Такое ощущение, что сокет пытается сказать мне, что он должен быть закрыт (если я не закрываю буфер или сокет, клиент не получает мой ответ и чтение блокирует поток). Итак, еще раз я смущен тем, что я должен делать. Может быть, сервер должен быть перегружен таким количеством соединений/портов? я еще раз упомяну, что клиент - это javascript, использующий GM_xmlhttpRequest - person ; 23.07.2010
comment
Если вы имеете дело с HTTP, вы должны либо иметь постоянные соединения, но читать только до тех пор, пока вам говорит длина содержимого, а также записывать длину содержимого, или закрывать соединение после каждого запроса. Однако клиент должен сказать вам, хотят ли они использовать постоянные соединения. Есть ли причина, по которой вы пытаетесь реализовать это самостоятельно, а не использовать HttpListener, который может сделать все это за вас? - person Jon Skeet; 23.07.2010
comment
Изначально я планировал реального клиента. Поэтому я должен закрываться после каждого запроса. Итак, что мне делать с netstat и кучей портов TIME_WAIT? Это прекрасно? Использование TcpListener было учебным опытом. Я попытаюсь использовать HttpListener, чтобы увидеть, как меняются мои результаты. (я думаю, что теперь у меня есть базовое понимание tcplistener) - person ; 23.07.2010
comment
Да, TIME_WAIT подходит. Но да, HttpListener в целом гораздо лучше — вам не нужно выполнять всю работу с HTTP самостоятельно. - person Jon Skeet; 23.07.2010
comment
Я закончил преобразование в HttpListener. Это интересно. ‹10 строк изменения кода. Первоначальный запрос просто искал первый \r\n\r и брал остаток в виде строки json и добавлял свой json-ответ в конец двухстрочного html-ответа, который я нашел в Интернете. Он отлично работал в браузерах, которые я хотел поддерживать. HttpListener был идентичен, за исключением того, что я думаю, у меня была бы лучшая поддержка типа контента. netstat показывает мне аналогичный запрос. Считайте мой вопрос решенным. принятый. - person ; 23.07.2010

Вам нужно создать потоки для каждого соединения и правильно закрыть сокет, используя методы socket.shutdown и socket.close. Как видно из вашего кода, вы не закрываете сокет, а напрямую останавливаете сервер, из-за которого сокет остается открытым.

Я бы хотел, чтобы вы ознакомились с этой статьей один раз. Мне помог рисунок 5 этой статьи.

Вы также можете столкнуться с проблемой, с которой я столкнулся в этой статье здесь< /сильный>

person Shantanu Gupta    schedule 23.07.2010
comment
TcpClient закрывается — при условии отсутствия исключений — и поток тоже закрывается. Я ожидаю, что они должным образом отключат сокет. - person Jon Skeet; 23.07.2010