C#使用Poll/Select实现多路I/O复用

在实际的应用中,如果全部采用异步的操作来,会增加代码的复杂程度,某些时候使用Poll/Select来实现单线程多路的I/O复用会更合适一些

一、Poll

原型函数

public bool Poll (  int microSeconds,  SelectMode mode )

1:客户端

private void Update()
    {
        if(socket == null)
        {
            return;
        }
        if (socket.Poll(0, SelectMode.SelectRead))
        {
            byte[] readBuff = new byte[1024];
            int count = socket.Receive(readBuff);
            string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
            text.text = recvStr;
        }
    }

注:

使用socket.Poll方法检查套接字是否有可读数据,超时时间为0,即立即返回 ,如果套接字有可读数据,则执行后续操作

设置为不阻塞模式(microSeconds 为0)。比起异步程序,代码简单的多,这是Read接收,还有发送SelectWrite

2:服务端

服务端一直检测监听Socket各个客户端Socket状态,如果收到消息就分别处理

class Main
{
    static Socket listenfd;
    static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
    public static void Main(string[] args)
    {
        listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
        IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
        listenfd.Bind(ipEp);
        listenfd.Listen(0);
        Console.WriteLine("[服务器]启动成功");
        while (true)
        {
            //检查listenfd
            if (listenfd.Poll(0, SelectMode.SelectRead))
            {
                ReadListenfd(listenfd);
            }
            //检查clientfd
            foreach (ClientState s in clients.Values)
            {
                Socket clientfd = s.socket;
                if (clientfd.Poll(0, SelectMode.SelectRead))
                {
                    if (!ReadClientfd(clientfd))
                    {
                        break;
                    }
                }
            }
            //防止CPU占用过高
            System.Threading.Thread.Sleep(1);
        }
    }
    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 SocketException " +
           ex.ToString());
            return false;
        }
        //客户端数量为0
        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("Receive" + recvStr);
        string sendStr = clientfd.RemoteEndPoint.ToString() + ":" + recvStr;
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
        //循环给每个客户端发送消息
        foreach (ClientState cs in clients.Values)
        {
            cs.socket.Send(sendBytes);
        }
        return true;
    }
}

注:

若没有收到客户端数据,服务端也一直在循环,浪费了CPU。Poll客户端也是同理,没有数据的时候还总在Update中检测数据

二、Select

原型函数

public static void Select(  IList checkRead,  IList check Write,  IList checkError,  int microSeconds )

checkRead:检测是否有可读Socket列表

checkWrite:检测是否有可写Socket列表

checkError:检测是否有出错

1:客户端

List<Socket> checkRead = new List<Socket>();

private void Update()
    {
        if (socket == null)
        {
            return;
        }
        checkRead.Clear();
        checkRead.Add(socket);
        Socket.Select(checkRead, null, null, 0);
        foreach (Socket socket in checkRead)
        {
            byte[] readBuff = new byte[1024];
            int count = socket.Receive(readBuff);
            string recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);
            text.text = recvStr;
        }
    }

注:

由于程序在Update中不停地检测数据,性能较差。商业上为了做 到性能上的极致,大多使用异步(或使用多线程模拟异步程序)。

2:服务端

class Class
{
    static Socket listenfd;
    static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
    public static void Main(string[] args)
    {
        listenfd = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
        IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
        IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
        listenfd.Bind(ipEp);
        listenfd.Listen(0);
        Console.WriteLine("[服务器]启动成功");

        List<Socket> checkRead = new List<Socket>();

        while (true)
        {
            //填充checkRead列表
            checkRead.Clear();
            checkRead.Add(listenfd);
            foreach (ClientState s in clients.Values)
            {
                checkRead.Add(s.socket);
            }
            //select
            Socket.Select(checkRead, null, null, 1000);
            //检查可读对象
            foreach (Socket s in checkRead)
            {
                if (s == listenfd)
                {
                    ReadListenfd(s);
                }
                else
                {
                    ReadClientfd(s);
                }
            }
        }
    }
    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 SocketException " +
           ex.ToString());
            return false;
        }
        //客户端数量为0
        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("Receive" + recvStr);
        string sendStr = clientfd.RemoteEndPoint.ToString() + ":" + recvStr;
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
        //循环给每个客户端发送消息
        foreach (ClientState cs in clients.Values)
        {
            cs.socket.Send(sendBytes);
        }
        return true;
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laker404

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值