一、服务端
由于同步的方式在处理多客户端处理时会出现多线程资源处理问题,所以在处理并发和并行问题时大多采用异步的形式。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); } }