首页 > 解决方案 > C# Socket BeginAccept vs Select?

问题描述

我正在学习 Socket 编程来建立一个聊天室。

我知道我可以使用异步套接字,例如

listenFd.BeginAccept(AcceptCallback, listenFd);

我也可以使用

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

我知道什么asyncselect做什么的基本含义。

但是,我不知道在什么情况下应该比另一种更好。

编辑:

实际上我正在关注一个教程。它说使用 select 比 async 更好,因为逻辑更清晰。

下面是两个例子:
一个使用 select:

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;
        }
    }
}

一个使用异步:

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 只告诉逻辑更清晰。

我已经进行了数小时的研究,但仍然感到困惑。这只是一种偏好还是我在这里想念的东西。

标签: c#socketsasyncsocketmultiplexing

解决方案


使用Select几乎总是意味着您正在轮询 - 占用一个线程以重复进行此调用,处理结果(通过某种形式的Accept),可能会休眠一段时间,然后重新执行。

BeginAccept让您告诉“系统”在发生有趣的事情时通知您。同时,您自己使用的是零处理资源,如果一切都设计得当,则根本没有线程进行任何类型的轮询或等待。

如果我遇到一个奇怪的情况,即有“好”时间和“坏”时间来执行接受,只会使用它。Select通过这种方式,您可以确保在“糟糕”时期没有未完成的接受呼叫。但这将是一个非常罕见的利基领域,希望您已经确定了这种情况。


推荐阅读