unity下tcp协议socket异步通信,服务端和客户端代码实现
.Net API Socket类
https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socket?view=netframework-4.7.2
服务端部分:
服务端的流程是:绑定IP和端口-》监听-》接收连接,有客户端连上后,服务端对该客户端进行接收信息、发送信息操作。
下面开始服务端代码的实现:
先定义一个端口号,和一个Socket(服务端)。
public int port = 9090; private Socket serverSocket;
下面定义一个函数,初始化Socket。
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.2
其中SocketType的字段含义,可参考.NET的API https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.sockettype?view=netframework-4.7.2
其中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);