C#Socket BeginAccept与Select?

时间:2019-01-10 16:33:35

标签: c# sockets asyncsocket multiplexing

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

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

listenFd.BeginAccept(AcceptCallback, listenFd);

我也可以使用

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

我知道asyncselect的基本含义。

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

编辑:

实际上,我正在学习一个教程。它说使用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];
}

两个示例都应该工作良好。但是我认为Damien_The_Unbeliever所说的异步应该更好。

但是,第二版教程的作者更喜欢使用select only来表明逻辑更清晰。

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

3 个答案:

答案 0 :(得分:3)

使用Select几乎总是意味着您正在轮询-捆绑线程只是重复进行此调用,处理结果(通过某种形式的Accept),可能会睡一会儿,然后然后再做一遍。

BeginAccept使您可以告诉“系统”在发生有趣的事情时通知您。同时,您自己正在使用零处理资源,并且如果一切都经过适当设计,则no thread at all会进行任何类型的轮询或等待。

如果我遇到奇怪的情况,即执行接收的“好”时间和“坏”时间,我只会可能使用Select。这样,您可以确保在“不良”时间内没有未完成的接受呼叫。但这将是一个非常罕有的利基领域,希望您已经发现了这种情况。

答案 1 :(得分:2)

Select方法接收绑定并监听传入请求的套接字列表。当呼叫返回时,该列表将仅包含套接字,其中有待处理的传入请求等待接受,如here所示。

我可以证明的主要区别之一是使用AcceptAsync而不是Accept时在OnCompleted Event处理程序上放置的独占线程。将用于处理使用该接受结果创建的套接字。

答案 2 :(得分:1)

您可以将聊天室中1.synchronous和2.asynchronous行为与此进行比较:

想象一下您有一个拍卖行,一个有1000人的房间,潜在的买家。

  1. 同步:拍卖师使用固定的模式/顺序浏览所有席位[您的投票循环],并询问[投票]每个席位他/她是否想提高出价[检查传入的连接/传入的数据]。如果是,则拍卖师将其注册[接受连接或处理键入的聊天语句],然后转到下一个并询问,依此类推。一旦完成所有1000次,他便重复。注意,不允许讲话,即。没有人可以注册投标直到被问到为止,尽管拍卖师在得到新的投标后立即宣布了新的投标[从聊天服务器向所有客户发送新的投标]。

  2. 异步:允许通话。任何人都可以随时喊价[回调到您的回调fn],甚至同时喊叫[操作系统创建多个同时并行的线程]-拍卖师非常机警,快速而熟练,因此他听到每个出价[操作系统管理传入的消息]并以他有能力执行的最佳方式宣布消息[所有线程将数据按发生的顺序放在您的公共共享数据存储库中,并尽快发出该公共数据]并且他甚至可以同时听到200个出价[多线程]。无需走动,也没有固定的投标顺序。

在第一种情况下,您可能会从随机用户John那里得到一幅绘画作品:“为什么Lisa总是在我之前发表评论?不公平!” [您的固定投票顺序,从1到最大,Lisa坐在John之前]。并从任何名为“ X”的用户(假设您从普通服务器提供所有聊天条目,即X的条目进行服务器往返):“为什么我的聊天条目有时会立即出现[拍卖师询问X-1人,他会在一纳秒内询问X],但有时需要2秒钟?[拍卖师询问X + 1人,花了一些时间才再次出现,操作系统堆栈使X处于保留状态]“

在重负载条件下,同步替代方案是变速箱缓慢且旋转不良。有点...:-)


小附录。异步编码(不太科学)

异步很难编码,测试和调试。它需要完全不同的编码样式,尽管异步部分可能存在于其他本应同步的应用程序中(该应用程序仍由用户操作由事件驱动,例如Windows)。

异步部分可以被认为是代码内的隔离良好的电动机;它可以处理您所维护的全局和持久性内部管理数据,但是在盒子内部,您可以自由地“疯狂”,具体取决于外部世界(网络连接和数据),这些信息轰炸了盒子的最内部直接,独立于您自己的行为。这是操作系统引发事件,起源于那里的客户端。

有两个重要的功能需要理解:

  1. 该框仅包含一组代码(一些功能),但是此组可以同时触发多次。每次触发它时,它将运行一个新的隔离实例。将其与其他类似线程隔离的唯一是线程ID,该ID在每个实例中都是唯一的。实例彼此独立地工作,就像CPU / OS可以执行的一样快。但是,每个人都将在某个时间点完成工作,并且可能希望将结果(依次以任何顺序,未知的顺序)传递到您的全局数据中(例如,客户列表,总聊天记录)所有客户等)。这意味着必须有一种锁定机制,因为只有一个实例(线程)可以同时访问全局数据,否则会很混乱。您的编码语言具有用于此目的的工具(保持,锁定)。

  2. 由于这一组代码是事件驱动的,因此无法预先知道将要进行多少工作。因此,它必须具有以下能力:a)开始进行工作b)继续进行工作,并且c)当操作系统说“就这样”时,以干净的方式完成。但是,如果出现故障,如果操作系统出于某种原因永远不会说“就是这样”,那么一定会有超时来完成任务。

不用说,所有这一切都是很难做到的。如果存在一个中断一个线程的错误,其他线程是否会继续?您如何访问失败线程的内部?您如何向后走去看看为什么/如何首先生成失败的线程?等等

编码异步代码时,应该一步一步地完成;从最简单的实现开始;并具有适当的外部环境,可以馈送丰富的测试数据,从逐步速率(例如按键)到很高的自动化速率,甚至同时(例如,LAN中的其他计算机)也可以馈送数据在开始/停止进给的基础上。