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);
        }
    }
原文地址:https://www.cnblogs.com/yuanshuang-club/p/12021429.html