unity下tcp協議socket異步通訊,服務端和客戶端代碼實現api
.Net API Socket類異步
https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socket?view=netframework-4.7.2socket
服務端部分:tcp
服務端的流程是:綁定IP和端口-》監聽-》接收鏈接,有客戶端連上後,服務端對該客戶端進行接收信息、發送信息操做。函數
下面開始服務端代碼的實現:this
先定義一個端口號,和一個Socket(服務端)。spa
public int port = 9090; private Socket serverSocket;
下面定義一個函數,初始化Socket。 .net
private void Init() { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPHostEntry iPHostEntry = Dns.GetHostEntry("localhost"); IPEndPoint iPEndPoint = new IPEndPoint(iPHostEntry.AddressList[1], port); serverSocket.Bind(iPEndPoint); serverSocket.Listen(5); }
其中AddressFamily的字段含義,可參考.NET的API https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.addressfamily?view=netframework-4.7.2code
其中SocketType的字段含義,可參考.NET的API https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.sockettype?view=netframework-4.7.2server
其中ProtocolType的字段含義,可參考.NET的API https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.protocoltype?view=netframework-4.7.2
Listen的參數,數字表示接受鏈接的上限數量。
以後就能夠進行接收操做了,接收鏈接的函數以下。
private void Accept() { try { serverSocket.BeginAccept(new AsyncCallback(Accept_Callback), serverSocket); } catch (Exception e) { print(e.Message); } }
這是一個異步的接收,其中的Accept_Callback是一個委託,下面會寫到,理解起來就是當有客戶端鏈接到服務端時,會自動調用Accept_Callback函數。
實現Accept_Callback的代碼以前,先定義一個類,後面傳遞參數時須要用到,後面再進行說明。
public class StateObject { public byte[] buffer; public Socket socket; public StateObject(int size, Socket socket) { buffer = new byte[size]; this.socket = socket; } }
定義可接收的數據的大小,以及一個用來儲存客戶端Socket的List。
private const int BUFFER_SIZE = 128; private List<Socket> clientSockets;
Accept_Callback函數實現以下。
private void Accept_Callback(IAsyncResult ar) { Socket socket = serverSocket.EndAccept(ar); print(socket.LocalEndPoint.ToString() + " Connected"); if (!clientSockets.Contains(socket)) { clientSockets.Add(socket); StateObject stateObject = new StateObject(BUFFER_SIZE, socket); try { socket.BeginReceive(stateObject.buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(Receive_Callback), stateObject); } catch (Exception e) { print(e.Message); } } Accept(); }
每接收到一個鏈接後,進行EndAccept,以獲取該客戶端的socket。
BeginReceive的參數定義可查看API文檔,這個地方的參數看文檔都能理解,就是最後一個參數要注意,這個地方傳遞,會在傳遞給下文Receive_Callback中,這個地方傳送的都會被轉換爲Object,而後在回調函數中能夠經過T t=(T)ar.AsyncState這樣的形式轉換回來。
下面是異步接收的回調函數。
private void Receive_Callback(IAsyncResult ar) { StateObject stateObject = (StateObject)ar.AsyncState; int read = stateObject.socket.EndReceive(ar); if (read > 0) { print(Encoding.ASCII.GetString(stateObject.buffer)); try { stateObject.socket.BeginReceive(stateObject.buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(Receive_Callback), stateObject); } catch (Exception e) { print(e.Message); } } }
這個地方要記得EndReceive,否則會接收到不少空字符串。
下面是發送給全部客戶端的函數,用了同步,若是要使用異步,使用BeginSend便可,用法和上面相似。
public void Send(string message) { byte[] msg = Encoding.ASCII.GetBytes(message); foreach (var item in clientSockets) { if (item.Connected) { try { item.Send(msg, msg.Length, 0); } catch (Exception e) { print(e.Message); } } } }
最後要記得要關閉Socket,否則即便你unity不運行了,那個socket還在後面運行。
private void OnDisable() { if (serverSocket.Connected) { try { serverSocket.Shutdown(SocketShutdown.Both); serverSocket.Close(); } catch (Exception e) { print(e.Message); } } }
下面是服務端的完整代碼。
using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; public class StateObject { public byte[] buffer; public Socket socket; public StateObject(int size, Socket socket) { buffer = new byte[size]; this.socket = socket; } } public class AsynchronousSocket : MonoBehaviour { private static AsynchronousSocket _singleton; public static AsynchronousSocket Singleton { get { if (_singleton == null) { _singleton = FindObjectOfType<AsynchronousSocket>(); } return _singleton; } } private const int BUFFER_SIZE = 128; public int port = 9090; private Socket serverSocket; private List<Socket> clientSockets; void Start() { clientSockets = new List<Socket>(); Init(); Accept(); } private void Init() { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPHostEntry iPHostEntry = Dns.GetHostEntry("localhost"); IPEndPoint iPEndPoint = new IPEndPoint(iPHostEntry.AddressList[1], port); serverSocket.Bind(iPEndPoint); serverSocket.Listen(5); } private void Accept() { try { serverSocket.BeginAccept(new AsyncCallback(Accept_Callback), serverSocket); } catch (Exception e) { print(e.Message); } } private void Accept_Callback(IAsyncResult ar) { Socket socket = serverSocket.EndAccept(ar); print(socket.LocalEndPoint.ToString() + " Connected"); if (!clientSockets.Contains(socket)) { clientSockets.Add(socket); StateObject stateObject = new StateObject(BUFFER_SIZE, socket); try { socket.BeginReceive(stateObject.buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(Receive_Callback), stateObject); } catch (Exception e) { print(e.Message); } } Accept(); } private void Receive_Callback(IAsyncResult ar) { StateObject stateObject = (StateObject)ar.AsyncState; int read = stateObject.socket.EndReceive(ar); if (read > 0) { print(Encoding.ASCII.GetString(stateObject.buffer)); try { stateObject.socket.BeginReceive(stateObject.buffer, 0, BUFFER_SIZE, 0, new AsyncCallback(Receive_Callback), stateObject); } catch (Exception e) { print(e.Message); } } } public void Send(string message) { byte[] msg = Encoding.ASCII.GetBytes(message); foreach (var item in clientSockets) { if (item.Connected) { try { item.Send(msg, msg.Length, 0); } catch (Exception e) { print(e.Message); } } } } private void OnDisable() { if (serverSocket.Connected) { try { serverSocket.Shutdown(SocketShutdown.Both); serverSocket.Close(); } catch (Exception e) { print(e.Message); } } } }
客戶端部分:
下面是客戶端的完整代碼。
using System; using System.IO; using System.Net.Sockets; using System.Text; using UnityEngine; public class SocketBehaviour : MonoBehaviour { private static SocketBehaviour _singleton; public static SocketBehaviour Singleton { get { if (_singleton == null) { _singleton = FindObjectOfType<SocketBehaviour>(); } return _singleton; } } private const int BUFFER_SIZE = 128; public string host = "127.0.0.1"; public int port = 9090; private byte[] buffer; private Socket socket; // Use this for initialization void Start() { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); Connect(); } private void Connect() { try { socket.Connect(host, port); } catch (Exception e) { print(e.Message); } if (socket.Connected) { print("Connected"); Receive(); } else { print("Connect fail"); } } private void Receive() { if (!socket.Connected) return; buffer = new byte[BUFFER_SIZE]; try { socket.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, new AsyncCallback(Receive_Callback), socket); } catch (Exception e) { print(e.Message); } } private void Receive_Callback(IAsyncResult ar) { if (!socket.Connected) { return; } int read = socket.EndReceive(ar); if (read > 0) { print(Encoding.UTF8.GetString(buffer)); Receive(); } } public void Send(string message) { if (!socket.Connected) return; byte[] msg = Encoding.ASCII.GetBytes(message); socket.Send(msg); } private void OnDisable() { if (socket.Connected) { socket.Shutdown(SocketShutdown.Both); socket.Close(); } } }
客戶端比較簡單就不解釋了,這裏的鏈接和發送都用的同步,用異步的話,參考API文檔,仍是比較好實現的。
歡迎交流,轉載註明出處:)
public IAsyncResult BeginReceive (byte[] buffer, int offset, int size, System.Net.Sockets.SocketFlags socket_flags, AsyncCallback callback, object state);