C # Socket BeginAccept vs Select?

2

Я учусь программированию сокетов, чтобы создать чат.

Я знаю, что я мог бы использовать асинхронный сокет, такой как

listenFd.BeginAccept(AcceptCallback, listenFd);

Также я мог бы использовать

Socket.Select(checkRead,null,null,1000);

Я знаю основное значение того, что делает async и select.

Однако я не знаю, по какому сценарию один должен быть лучше другого.

Редактировать:

На самом деле я следовал за учебником. Он сказал, что использование select лучше, чем async, потому что логика более понятна.

Вот два примера:
Одно использование выберите:

namespace Server
{
    class App
    {
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
        static string ipAddr="127.0.0.1";
        static int port=8888;
        static void Main(string[] args)
        {
            Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress iPAddress = IPAddress.Parse(ipAddr);
            IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, port);
            listenFd.Bind(iPEndPoint);
            listenFd.Listen(0);
            Console.WriteLine("Server start!");
            List<Socket>checkRead=new List<Socket>();

            while(true)
            {
                checkRead.Clear();
                checkRead.Add(listenFd);
                foreach(var clientState in clients.Values)
                {
                    checkRead.Add(clientState.socket);
                }
                Socket.Select(checkRead,null,null,1000);
                foreach(var socket in checkRead)
                {
                    if(socket==listenFd)
                    {
                        ReadListenfd(socket);
                    }
                    else
                    {
                        ReadClientfd(socket);
                    }
                }
            }


        }

        public static void ReadListenfd(Socket listenfd)
        {
            Console.WriteLine("Accept");
            Socket clientfd=listenfd.Accept();
            ClientState state=new ClientState();
            state.socket=clientfd;
            clients.Add(clientfd,state);
        }

        public static bool ReadClientfd(Socket clientfd)
        {
            ClientState state=clients[clientfd];
            int count=0;
            try
            {
                count=clientfd.Receive(state.readBuff);
            }
            catch(SocketException ex)
            {
                clientfd.Close();
                clients.Remove(clientfd);
                Console.WriteLine($"Receive Socket Exception {ex.ToString()}");
                return false;
            }
            if(count==0)
            {
                clientfd.Close();
                clients.Remove(clientfd);
                Console.WriteLine("Socket close");
                return false;
            }

            string recvStr=System.Text.Encoding.Default.GetString(state.readBuff,0,count);
            Console.WriteLine($"Rec {recvStr}");
            string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+recvStr;
            byte[]sendBytes=System.Text.Encoding.Default.GetBytes(strFromClientWithTime);
            foreach(ClientState cs in clients.Values)
            {
                cs.socket.Send(sendBytes);
            }
            return true;
        }
    }
}

Тот, кто использует Async:

namespace Server
{
    class App
    {
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
        static void Main(string[] args)
        {
            Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
            IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 8888);
            listenFd.Bind(iPEndPoint);
            listenFd.Listen(0);
            Console.WriteLine("Server start!");
            listenFd.BeginAccept(AcceptCallback, listenFd);

            while(true)
            {
                Thread.Sleep(1000);
            }
        }

        static void AcceptCallback(IAsyncResult result)
        {
            var listenfd = result.AsyncState as Socket;
            var connfd = listenfd.EndAccept(result);
            var clientState = new ClientState { socket = connfd };
            clients.Add(connfd, clientState);
            connfd.BeginReceive(clientState.readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
            Console.WriteLine($" Client connected!");
            listenfd.BeginAccept(AcceptCallback, listenfd);
        }



        static void EndReceiveCallback(IAsyncResult result)
        {
            var connfd = result.AsyncState as Socket;
            var count = connfd.EndReceive(result);
            if (count <= 0)
            {
                Console.WriteLine("Client disconnected!");
                connfd.Close();
                return;
            }

            connfd.BeginReceive(clients[connfd].readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
            string strFromClient=System.Text.Encoding.Default.GetString(clients[connfd].readBuff,0,count);
            Console.WriteLine($"string from client:{strFromClient}");
            string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+strFromClient;
            byte[] sendBuff= System.Text.Encoding.Default.GetBytes(strFromClientWithTime,0,strFromClientWithTime.Length);
            foreach(var conn in clients.Keys)
            {
                conn.BeginSend(sendBuff, 0, sendBuff.Length, 0, EndSendCallback, conn);
            }
        }

        static void EndSendCallback(IAsyncResult result)
        {
            var connfd = result.AsyncState as Socket;
            connfd.EndSend(result);
        }
    }
}

В обоих примерах Class ClientState

class ClientState
{
    public Socket socket;
    public byte[] readBuff=new byte[1024];
}

Оба примера должны работать хорошо. Но я думал, что async должен быть лучше, как сказал Damien_The_Unbeliever.

Тем не менее, автор учебника во втором издании предпочитает использовать select only, говоря, что логика более понятна.

Я провел часы исследований, но все еще в замешательстве. Это просто предпочтение или есть что-то, чего мне здесь не хватает.

  • 0
    Вам все равно придется позвонить в Accept (или аналогичный) после возврата Select . Я не уверен, почему вы думаете, что один является альтернативой другому.
  • 0
    select в Windows: просто скажите нет.
Показать ещё 2 комментария
Теги:
sockets
asyncsocket
multiplexing

3 ответа

1
Лучший ответ

Вы можете сравнить 1.synchronous и 2.asynchronous поведение в вашей комнате чата с этим:

Представьте, что у вас есть аукцион, комната на 1000 человек, потенциальные покупатели.

  1. Синхронный: аукционист проходит по всем местам, используя фиксированный шаблон/порядок [ваш цикл опроса], и спрашивает [опросы] каждого, хочет ли он/она сделать более высокую ставку [проверяет входящее соединение/входящие данные]. Если да, аукционист регистрирует его [принимает соединение или обрабатывает набранное предложение чата], затем переходит к следующему и спрашивает, и так далее. Как только все 1000 сделаны, он повторяется. Обратите внимание, что не разрешается говорить, т.е. никто не может зарегистрировать ставку до тех пор, пока его не спросят, хотя аукционист немедленно объявляет о новых ставках по мере их получения [отправляйте все новое с сервера чата всем клиентам].

  2. Асинхронный: Разрешено говорить. Любой может выкрикнуть свою ставку в любое время [обратный вызов вашего обратного вызова fn], даже одновременно [ОС создает несколько одновременных и параллельных потоков] - аукционист очень внимателен, быстр и квалифицирован, поэтому он слышит каждую отдельную ставку [ операционная система управляет входящими сообщениями и объявляет об этом наилучшим способом, который он когда-либо мог сделать [все потоки размещают данные в вашем общем, общем хранилище данных, в порядке возрастания потоков, и вы отправляете эти общие данные как можно скорее] И он способен услышать даже 200 одновременных заявок [несколько потоков]. Нет необходимости ходить, и нет фиксированного заказа для размещения ставок.

В первом случае вы, вероятно, получите знакомого от случайного пользователя Джона: "Почему Лиза всегда комментирует меня? Несправедливо!" [ваш фиксированный порядок голосования, от 1 до макс, Лиза сидит перед Джоном]. И от любого пользователя с именем "X" (учитывая, что вы выводите все записи чата с общего сервера, т.е. Запись X делает сервер туда и обратно): "Почему моя запись чата иногда появляется сразу же [аукционист спросил X-1 человека, он спросит X в наносекунде], но иногда это занимает 2 секунды? [аукционист спросил X + 1 человека, требуется время, прежде чем он снова здесь, стек ОС удерживает X в ожидании] "

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


Небольшое дополнение к ре. асинхронное кодирование (не слишком научное)

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

Асинхронную часть можно считать хорошо изолированным двигателем внутри вашего кода; он работает с глобальными и постоянными служебными данными, которые вы поддерживаете вокруг него, но внутри коробки он может свободно "сходить с ума", в зависимости от того, что исходит из внешнего мира = сетевых подключений и данных, которые бомбардируют внутреннюю часть блока. напрямую, независимо от ваших собственных действий. Это события поднятия ОС, происходящие от клиентов.

Необходимо понять две важные особенности:

  1. Блок состоит только из одного набора кода (несколько функций), но этот набор может запускаться несколько раз одновременно. Каждый раз, когда он запускается, запускается новый изолированный экземпляр. Единственная вещь, которая изолирует его от других подобных потоков - это идентификатор потока, который уникален для каждого экземпляра. Экземпляры работают независимо друг от друга, настолько быстро, насколько могут работать процессор/ОС. Однако каждый в какой-то момент завершит работу и, возможно, захочет доставить результат (по очереди, в произвольном порядке) в ваши глобальные данные (например, список клиентов, общий чат). всех клиентов и т.д.). Это означает, что должен быть механизм блокировки, потому что только один экземпляр (поток) может одновременно обращаться к глобальным данным, иначе это будет беспорядок. Ваш язык кодирования имеет инструменты для этого (удержание, блокировка).

  2. Поскольку этот единственный набор кода управляется событиями, он не может знать заранее, сколько работы будет. Следовательно, он должен обладать способностью: а) начинать выполнять работу; б) продолжать выполнять работу; в) чистым образом завершать работу, когда ОС говорит "это все". Но в случае неудачи, если ОС по какой-то причине никогда не говорит "это было так", должен быть тайм-аут, который все равно завершает.

Само собой разумеется, что все это сложно сделать правильно. Если есть ошибка, которая останавливает один поток, другие потоки продолжатся? Как вы получаете доступ к внутренним ресурсам неисправного потока? Как вы идете назад, чтобы увидеть, почему/как эта неудавшаяся нить была создана в первую очередь? И т.п.

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

  • 0
    Это говорит о недостатках использования опроса синхронизации очень ясно. Однако в вашем ответе не упоминаются недостатки использования async. Я отмечу ваш ответ принятым, так как он действительно помогает мне хорошо это знать. Надеюсь, у тебя есть время пополнить недостающую часть.
3

Использование Select почти всегда означает, что вы опрашиваете - связываете поток, чтобы просто несколько раз выполнить этот вызов, обработать результаты (с помощью некоторой формы Accept), возможно, поспать какое-то время, а затем сделать это снова и снова.

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

Я мог бы использовать Select случае, если у меня возникла странная ситуация, когда бывают "хорошие" и "плохие" времена, когда нужно принимать. Таким образом, вы можете убедиться в отсутствии ожидающих вызовов в "плохие" времена. Но это будет довольно редкая нишевая область, и, надеюсь, вы уже определили эту ситуацию.

  • 0
    Пожалуйста, смотрите мои изменения, если у вас есть время. Благодарю.
2

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

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

  • 0
    Пожалуйста, смотрите мои изменения, если у вас есть время. Благодарю.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню