• C#Socket异步编程


    一、服务端

      由于同步的方式在处理多客户端处理时会出现多线程资源处理问题,所以在处理并发和并行问题时大多采用异步的形式。Server端只是单独的接收请求,然后将请求丢给对应的客户端对象Client进行处理,Client端则对消息进行处理

    ,将解析出来的消息传递给控制器Controller进行处理。

    二、涉及到拆包问题(客户端消息发送形式,以及服务端响应消息形式)

      (1)例如要发送的消息如下

      

    string message = "Hello Word!"

      由于使用Socket传输数据时,采用的是字节流的形式进行传递,所以字符串必须转换为byte字节数组

     

    byte[] databytes = Encoding.UTF8.GetBytes(message)

      当客户端很频繁的给服务端发送消息时,服务端接收的消息可能会存在粘包问题,所以要进行拆包,所以要自己定义一下消息的发送组成形式,我这里采用的是(包头 + 包的内容),包头为当前包的长度,所以客户端发送每一条消息时,必须先将当前要发送的消息进行长度获取,然后将长度转化为byte字节数组,代码如下:

     注意:在C#中一个Int32的整数转换成byte数组后悔占固定的四个字节的长度,利用这个特性,就能完成拆包处理,同时Encoding.UTF8.GetString()在命名空间System.Text下。

    int length = databytes.Length //获取消息的长度
    byte[] lengthbytes = BitConverter.GetBytes(length) //将消息的长度转化为字节数组
    byte[] newbytes =  lengthbytes.Concat(databytes).ToArray()//将字符串长度当做包头,要发送的消息当做包进行连接,得到一个新的包,也就是要发送给服务端的包

      (2)服务端对消息处理时,要按照上面商议的方式对包进行拆解,才能得到对应的一条完整的消息。代码过程如下:

      

    int msgLength = BitConverter.Int32(data,0)//data是一个byte类型的数组,存取的就是客户端发送给服务端的消息,BitConverter.Int32()方法能取出数组中前4个字节,并将其转换为Int32的整数,也就是消息的长度。 

      

    string retmsg = Encoding.UTF8.GetString(data,4,msgLength) //获取完整的消息,从第4个字节开始提取msgLength的长度的字节,然后将其转换为字符串。

    三、数据处理类代码如下(接收消息,拆包,包装数据):

    /// <summary>
        /// 消息的处理
        /// </summary>
        class Message
        {
            /// <summary>
            /// 存储的最大数据长度
            /// </summary>
            private const int MaxLength = 1024;
            private byte[] data = new byte[MaxLength];
            private int Index = 0;
            public byte[] GetData
            {
                get { return data; }
            }
    
            private void AddCount(int count)
            {
                Index += count;
            }
    
            public int RemainSize
            {
                get { return data.Length - Index; }
            }
    
            public int GetIndex
            {
                get { return Index; }
            }
        
            public void ReadMessage(int amount,Action<string> processDataCallBack)
            {
                AddCount(amount);
                while (true)
                {
                    if (Index < 4) return;
                    int count = BitConverter.ToInt32(data, 0);
                    if (Index - 4 >= count)
                    {
                        string str = Encoding.UTF8.GetString(data, 4, count);
                        if (processDataCallBack != null)
                        {
                            processDataCallBack.Invoke(str);//回调函数,根据消息的内容进行相应的业务处理
                        }
                        else
                        {
                            Console.WriteLine("处理请求的回调函数OnProcessMessage为空!");
                        }
                        Array.Copy(data, count + 4, data, 0, Index - 4 - count);
                        Index -= count + 4;
                    }
                    else
                    {
                        break;
                    }
                }
            }
    
            /// <summary>
            /// 对消息进行包装
            /// </summary>
            /// <param name="str"></param>
            /// <returns></returns>
            public static byte[] GetNewBytes(string str)
            {
                byte[] databytes = Encoding.UTF8.GetBytes(str);
                int length = databytes.Length;
                byte[] lengthbyts = BitConverter.GetBytes(length);
                return lengthbyts.Concat(databytes).ToArray();
            }
        }

    四、Client类代码如下(这里的Client类只负责客户端socket的处理,也就是客户端socket套接字的一个代理,每一个客户端对应一个Client对象。):

    /// <summary>
        /// 管理client连接
        /// </summary>
        class Client
        {
            public Client()
            {
    
            }
            public Client(Socket clientSocket,Server server)
            {
                this.clientSocket = clientSocket;
                this.server = server;
                Msg = new Message();
                mySqlConnection = ConnHelper.Connect();
            }
    
            private Socket clientSocket;
            private Server server;
            private Message Msg;
            private MySqlConnection mySqlConnection;//持有数据库的连接对象,这个可以加,可以不加
            public void Start()
            {
                clientSocket.BeginReceive(Msg.GetData, Msg.GetIndex, Msg.RemainSize, SocketFlags.None, ReceiveCallBack, null);
            }
    
            private void ReceiveCallBack(IAsyncResult ar)
            {
                try
                {
                    int count = clientSocket.EndReceive(ar);
                    if (count == 0)
                    {
                        Close();
                    }
                    Msg.ReadMessage(count, OnProcessMessage);//这里传递了一个回调函数,会将解析出来的消息回调出来交给server中转
                    clientSocket.BeginReceive(Msg.GetData, Msg.GetIndex, Msg.RemainSize, SocketFlags.None, ReceiveCallBack, null);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    Close();
                }
            }
    
            /// <summary>
            /// server端处理客户端的消息,这里使用了一个中介者,服务端当中介,将消息转发给对应的控制器,去执行对应的代码逻辑
            /// </summary>
            /// <param name="requestCode"></param>
            /// <param name="actionCode"></param>
            /// <param name="data"></param>
            private void OnProcessMessage(string data)
            {
                server.RequestHandle(data, this);
            }
    
            /// <summary> 
            /// 给客户端返回响应
            /// </summary>
            public void Send(string data)
            {
                byte[] databytes = Message.GetNewBytes(data);
                clientSocket.Send(databytes);
            }
            
            private void Close()
            {
                ConnHelper.CloseConnection(mySqlConnection);//关闭数据库的连接
                server.RemoveClient(this);
                if (clientSocket != null)
                clientSocket.Close();
            }
        }

    五、服务端代码如下(服务端接收客户端的连接,同时又担任了每一个客户端消息转发的角色,降低代码的耦合性)

    /// <summary>
        /// 创建服务,得到client连接
        /// </summary>
        class Server
        {
            public Server()
            {
    
            }
            public Server(string ip,int port)
            {
                this.controllerManager = new ControllerManager(this);//初始化Controller控制器,这里只是一个简单的架构,并没有具体实现
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this.ip = new IPEndPoint(IPAddress.Parse(ip), port);
                serverSocket.Bind(this.ip);
                serverSocket.Listen(0);
            }
    
            private Socket serverSocket;
            private IPEndPoint ip;
            private IPAddress port;
            private List<Client> client_list = new List<Client>();
            private ControllerManager controllerManager;
            private void Start()
            {
                serverSocket.BeginAccept(AcceptCallBack,null);//开始异步接收客户端的连接
            }
    
            private void AcceptCallBack(IAsyncResult ar)
            {
                Socket client = serverSocket.EndAccept(ar);
                Client newclient = new Client(client, this);
                newclient.Start();
                client_list.Add(newclient);
                serverSocket.BeginAccept(AcceptCallBack, null);//继续回调接收客户端的请求
            }
    
            public void RemoveClient(Client client)
            {
                lock (client_list)
                {
                    client_list.Remove(client);
                }
            }
    
            /// <summary>
            /// 给客户端返回响应
            /// </summary>
            /// <param name="client">是哪一个客户端应该得到响应</param>
            /// <param name="ret">响应体</param>
            public void SendResponse(Client client,string ret)
            {
                //给客户端发送响应
                client.Send(ret);
            }
    
            public void RequestHandle(string data, Client client)
            {
                //通过server将消息发送给Controller进行处理
                controllerManager.HandleRequest(data, client);
            }
        }
  • 相关阅读:
    leetcode 子集
    leetcode 1111. 有效括号的嵌套深度
    leetcode289 生命游戏
    关于三次握手和四次挥手,发送方和接收方处于的状态的问题。
    面试题:随意取数
    2020/3/31
    01背包和完全背包
    USACO Agri-Net 3.1
    mfc小计
    static 成员小记
  • 原文地址:https://www.cnblogs.com/yuanshuang-club/p/12021429.html
Copyright © 2020-2023  润新知