Простой сокет с одним потоком — TCP-сервер

Во-первых, я не знаю, является ли Stackoverflow лучшим сайтом для публикации такого рода сообщений, но я не знаю других подобных сайтов.

Чтобы правильно понять программирование tcp на C#, я решил сделать все возможные способы с нуля. Вот что я хочу знать (не в правильном порядке): - Простой сервер сокетов с одним потоком (эта статья) - Сервер сокетов с несколькими потоками (я не знаю, как это сделать, потому что потоки сложны) - Сервер сокетов с простым потоком (поместите управление клиентом в другом потоке) - Сервер сокетов с несколькими потоками - Использование tcpListener - Использование async / Await - Использование задач Конечная цель - узнать, как сделать лучший сервер tcp, не просто копируя/вставляя некоторые части прихода, но правильно понимая все.

Итак, это моя первая часть: однопоточный tcp-сервер.

Там есть мой код, но не думаю, что кто-то что-то подправит, потому что это вполне себе копия из MSDN: http://msdn.microsoft.com/en-us/library/6y0e13d3(v=vs.110).aspx

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace SimpleOneThreadSocket
{
    public class ServerSocket
    {
        private int _iPport = -1;
        private static int BUFFER_SIZE = 1024;
        private Socket _listener = null;

        public ServerSocket(int iPort)
        {            
            // Create a TCP/IP socket.
            this._listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Save the port
            this._iPport = iPort;
        }

        public void Start()
        {
            byte[] buffer = null;
            String sDatasReceived = null;

            // Bind the socket to loopback address
            try
            {
                this._listener.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, _iPport));
                this._listener.Listen(2);
            }
            catch (Exception e)
            {
                System.Console.WriteLine(e.ToString());
            }

            // Listening
            try
            {
                Console.WriteLine("Server listening on 127.0.0.1:" + _iPport);

                while (true)
                {
                    Socket client = this._listener.Accept();
                    Console.WriteLine("Incoming connection from : " + IPAddress.Parse(((IPEndPoint)client.RemoteEndPoint).Address.ToString()) + ":" + ((IPEndPoint)client.RemoteEndPoint).Port.ToString());

                    // An incoming connection needs to be processed.
                    while (true)
                    {
                        buffer = new byte[BUFFER_SIZE];
                        int bytesRec = client.Receive(buffer);
                        sDatasReceived += Encoding.ASCII.GetString(buffer, 0, bytesRec);
                        if (sDatasReceived.IndexOf("<EOF>") > -1)
                        {
                            // Show the data on the console.
                            Console.WriteLine("Text received : {0}", sDatasReceived);

                            // Echo the data back to the client.
                            byte[] msg = Encoding.ASCII.GetBytes(sDatasReceived);

                            client.Send(msg);

                            sDatasReceived = "";
                            buffer = null;
                        }
                        else if (sDatasReceived.IndexOf("exit") > -1)
                        {
                            client.Shutdown(SocketShutdown.Both);
                            client.Close();

                            sDatasReceived = "";
                            buffer = null;

                            break;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

Но у меня есть несколько вопросов по этому поводу:

  • Метод прослушивания из сокета имеет параметр: отставание. Согласно MSDN, отставание — это количество доступных подключений. Я не знаю, почему, когда я ставлю 0, я могу подключиться к своему серверу с помощью нескольких сеансов Telnet. РЕДАКТИРОВАТЬ: 0 и 1 разрешают 2 соединения (1 текущее, 1 ожидающее), 2 разрешают 3 соединения (1 текущее, 2 ожидающих) и т. д. Так что я не очень хорошо понял значение MSDN.

  • Можете ли вы подтвердить, что метод Accept будет принимать каждое соединение одно за другим, поэтому я вижу текст из разных сеансов Telnet на моем сервере?

  • Можете ли вы подтвердить (мой сервер является библиотекой C #), я не могу убить свой сервер (с помощью такого кода), не убивая процесс? Это может быть возможно с потоками, но это придет позже.

Если что-то не так в моем коде, пожалуйста, помогите мне :)

Я скоро вернусь с простым сервером сокетов с несколькими потоками, но я не знаю, как это сделать (я думаю, что один шаг доступен перед использованием потоков или async/await).


person BaptX    schedule 11.08.2014    source источник


Ответы (1)


Во-первых, сделайте все возможное, чтобы даже не узнать об этом. Если вы можете использовать сервер SignalR, сделайте это. Не существует такой вещи, как "простой" сервер сокетов на уровне TCP/IP.

Если вы настаиваете на болезненном пути (т. е. на изучении правильного устройства TCP/IP-сервера), то вам предстоит многому научиться. Во-первых, примеры из MSDN — общеизвестно плохая отправная точка; они почти не работают и, как правило, не обрабатывают какие-либо ошибки, что абсолютно необходимо в реальном мире при работе на уровне TCP/IP. Воспринимайте их как примеры того, как вызывать методы, а не как примеры клиентов или серверов сокетов.

У меня есть часто задаваемые вопросы о TCP/IP, которые могут вам помочь , включая описание параметра backlog. Это количество подключений, которые ОС примет от вашего имени, прежде чем ваш код сможет их принять, и в любом случае это всего лишь подсказка.

Чтобы ответить на ваши другие вопросы: один вызов Accept примет одно новое подключение к сокету. Написанный код имеет бесконечный цикл, поэтому он будет работать как любой другой бесконечный цикл; он будет продолжать выполняться до тех пор, пока не столкнется с исключением или его поток не будет прерван (что происходит при завершении процесса).

Если что-то не так в моем коде, пожалуйста, помогите мне

О, да. В этом коде много чего не так. В конце концов, это пример сокета MSDN. :) С верхней части моей головы:

  1. Размер буфера является произвольным значением, довольно низким. Я бы начал с 8K, чтобы можно было получить полный пакет Ethernet за одно чтение.
  2. Bind явно использует петлевой адрес. Я думаю, это нормально для игры, но не забудьте установить это значение на IPAddress.Any в реальном мире.
  3. Параметр backlog подходит для тестирования, но должен быть int.MaxValue на реальном сервере, чтобы включить динамическое отставание в современных серверных ОС.
  4. Код провалится через первую catch и попытается выполнить Accept после неудачной попытки Bind/Listen.
  5. Если возникает какое-либо исключение (например, из Listen или Receive), то весь сервер выключается. Обратите внимание, что завершение клиентского сокета приведет к исключению, которое должно быть зарегистрировано/проигнорировано, но это остановит этот сервер.
  6. Буфер чтения перераспределяется каждый раз в цикле, даже если старый буфер больше никогда не используется.
  7. ASCII — это кодировка с потерями.
  8. Если клиент завершает работу без отправки <EOF>, то сервер входит в бесконечный цикл занятости.
  9. Полученные данные не разделяются должным образом на сообщения; возможно, что эхо-сообщение содержит все одно сообщение и часть другого. В данном конкретном примере это не имеет значения (поскольку это всего лишь эхо-сервер и он использует ASCII вместо реальной кодировки), но этот пример скрывает тот факт, что вам необходимо правильно обрабатывать кадрирование сообщений в любом реальном приложении.
  10. Декодирование должно выполняться после кадрирования сообщения. Это не обязательно для ASCII (кодировка с потерями), но требуется для любых реальных кодировок, таких как UTF8.
  11. Поскольку сервер в любой момент либо получает, либо отправляет (и никогда не и то, и другое), он не может обнаружить или восстановить ситуацию с полуоткрытым сокетом. Полуоткрытый сокет приведет к зависанию этого сервера.
  12. Сервер поддерживает только одно соединение за раз.

Это было сразу после краткого прочтения. Легко может быть больше.

person Stephen Cleary    schedule 11.08.2014
comment
Люблю необъяснимые отрицательные голоса. Кто-нибудь хочет высказаться? Вы знаете, на самом деле указать на один неверный момент в моем ответе? ;) - person Stephen Cleary; 11.08.2014
comment
Боже, этот код есть в MSDN? Это должно отвечать за часть многих вопросов TCP, на которые я отвечаю. - person usr; 12.08.2014
comment
@usr: я серьезно планирую сделать серию блогов, в которых будут разбирать примеры сокетов MSDN, по одному посту на образец. Шаг за шагом, объясняя, почему это неправильно и как сделать это правильно. Кстати, исходный пример MSDN еще хуже (привязывается к Dns.Resolve(Dns.GetHostName()))... Но я планировал эту серию блогов в течение многих лет; не стесняйтесь взять эту идею и бежать с ней! :) - person Stephen Cleary; 12.08.2014
comment
Это объясняет, почему несколько недель назад я видел Dns.Resolve(Dns.GetHostName()) в Stack Overflow. Это объясняет многое. - person usr; 12.08.2014
comment
Вы уверены, что SignalR — лучший способ сделать Tcp-сервер? Могу ли я использовать его в проекте библиотеки (все примеры с графическим интерфейсом — mvc)? Я постараюсь найти учебник по этой библиотеке. Почему это не лучший способ изучить потоки и/или асинхронное ожидание? спасибо за Ваш ответ. - person BaptX; 13.08.2014
comment
SignalR — это библиотека asp.net. Мне нужно сделать мой TCP-сервер в качестве службы Windows. поэтому я бы закодировал его в библиотеке (dll) и добавил эту dll в службу. - person BaptX; 13.08.2014
comment
SignalR не зависит от ASP.NET; вы можете самостоятельно размещать его в службе Win32 просто отлично. TCP/IP требует огромной кривой обучения, так что это очень сложный способ изучения асинхронного программирования и многопоточности. - person Stephen Cleary; 13.08.2014