由於平時使用Unity開發,所以相對其他的編程語言,對C#比較了解,所以寫了一個C/S的Socket異步通信程序。目前只是在C#中測試,未來會接入Unity3D,這個只是我個人用來進行學習用的,所以大家不必在意使用哪個語言做后端服務器開發更好
一些說明:
- 使用最簡單的TCP協議來進行通信,通信數據未做任何包裝和加工(后面會接入Protobuf-net)
- 采用異步通信結構
- 為了提高服務器的性能,使用了連接池復用的方法
1.服務器連接池

class Conn { //緩存區大小 public const int BUFFER_SIZE = 1024; //socket public Socket socket; //是否被使用 public bool isUse = false; //Buff public byte[] readBuff; public int buffCount = 0; //構造函數 public Conn() { readBuff = new byte[BUFFER_SIZE]; } //初始化 public void Init(Socket socket) { this.socket = socket; isUse = true; buffCount = 0; } //緩存區剩余的字節 public int BuffRemain() { return BUFFER_SIZE - buffCount; } //獲取客戶端的地址 public string GetAddress() { if (!isUse) return "獲取地址失敗"; return socket.RemoteEndPoint.ToString(); } //關閉 public void Close() { if (isUse) return; Console.WriteLine(GetAddress() + " 斷開連接"); socket.Close(); isUse = false; } }
2.服務器Socket

class UnityServer { //監聽套接字 public Socket listenSocket; //客戶端連接(異步) public Conn[] conns; //最大連接數 public int maxCount = 100; //獲取連接池索引 public int NewIndex() { if (conns == null) return -1; for(int i=0;i<conns.Length;i++) { if (conns[i] == null) { conns[i] = new Conn(); return i; } else if(conns[i].isUse == false) { return i; } } return -1; } //開啟服務器 public void Start(IPEndPoint iPEndPoint) { //連接對象池 conns = new Conn[maxCount]; for(int i=0;i<maxCount;i++) { conns[i] = new Conn(); } //socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //bind listenSocket.Bind(iPEndPoint); //listen listenSocket.Listen(maxCount); //Accept listenSocket.BeginAccept(AcceptCb, null); } //Accept回調函數 private void AcceptCb(IAsyncResult asyncResult) { try { Socket socket = listenSocket.EndAccept(asyncResult); int index = NewIndex(); if(index == -1) { Console.WriteLine("Conn連接池已滿!"); } else { Conn conn = conns[index]; conn.Init(socket); string addr = conn.GetAddress(); Console.WriteLine("客戶端 [" + addr + "] 連接"); Console.WriteLine("已連接 " + index + " 個客戶端"); conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn); listenSocket.BeginAccept(AcceptCb, null); } } catch(Exception ex) { Console.WriteLine("AcceptCb 失敗:" + ex.Message); } } //Receive回調函數 private void ReceiveCb(IAsyncResult asyncResult) { //獲取接收對象 Conn conn = (Conn) asyncResult.AsyncState; try { int count = conn.socket.EndReceive(asyncResult); //關閉信號 if(count <= 0) { Console.WriteLine("收到 [" + conn.GetAddress() + "] 斷開連接"); conn.Close(); return; } //數據處理 string str = conn.socket.RemoteEndPoint.ToString() +":" + Encoding.UTF8.GetString(conn.readBuff, 0, count); Console.WriteLine("[" + conn.GetAddress() + "] : " + str); byte[] sendMsg = Encoding.UTF8.GetBytes(str); //將所有的消息廣播 for(int i=0;i<maxCount;i++) { if (conns[i] == null || !conns[i].isUse) continue; conns[i].socket.Send(sendMsg); } //繼續接收 conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn); } catch (Exception ex) { Console.WriteLine("[" + conn.GetAddress() + "] 斷開連接"); conn.Close(); } } }
3.服務器Main函數

class Program { static void Main(string[] args) { int port = 1234; IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, port); Console.WriteLine("服務器開啟..."); UnityServer unityServer = new UnityServer(); unityServer.Start(iPEndPoint); while(true) { string input = Console.ReadLine(); if (input == "Q") return; } } }
4.客戶端Socket

class Program { //客戶端套接字 static Socket clientSocket; static void Main(string[] args) { string IP = "127.0.01"; IPAddress ip = IPAddress.Parse(IP); int port = 1234; clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint endPoint = new IPEndPoint(ip, port); //連接服務器 try { clientSocket.Connect(endPoint); //發送字節 string str = Console.ReadLine(); byte[] message = Encoding.UTF8.GetBytes(str); clientSocket.Send(message); //接收服務器信息 byte[] receive = new byte[1024]; clientSocket.Receive(receive); Console.WriteLine("接收消息為:" + Encoding.UTF8.GetString(receive)); } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { //關閉連接 clientSocket.Close(); } Console.ReadKey(); } }
5.測試結果
啟動服務器,注意端口號和IP地址
啟動客戶端,服務器顯示有客戶端連接上來(0個客戶端是因為是0開始計數的)
客戶端發送消息,服務器將這個消息返回到所有在線的客戶端
總結:
這只是一個簡單的Socket測試Demo,如果要用於真正的通信,就需要考慮封裝協議、加密數據、網絡異常處理、服務器負載、高並發能力等,后續會繼續學習如何完成一個小型的Unity游戲服務器