C # Socket BeginAccept vs Select? - PullRequest
       15

C # Socket BeginAccept vs Select?

0 голосов
/ 10 января 2019

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

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

listenFd.BeginAccept(AcceptCallback, listenFd);

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

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

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

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

Edit:

На самом деле я следовал учебному пособию. Он сказал, что использование 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];
}

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

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

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

Ответы [ 3 ]

0 голосов
/ 11 января 2019

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

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

0 голосов
/ 19 января 2019

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

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

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

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

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

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


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

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

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

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

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

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

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

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

0 голосов
/ 11 января 2019

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

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

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

...