文章目录
本文将以Unity3D应用项目作为客户端去连接制定的服务器为例进行相关说明。
Unity官网参考资料: https://developer.unity.cn/projects/6572ea1bedbc2a001ef7df52
概述
在C#中,封装好了两个核心类,用于TCP网络编程:
TCP核心类
- TcpListener
用于创建一个监听器,监听客户端传入的TCP网络请求;作为服务器时使用。 - TcpClient
该类提供客户端连接,用于与服务器进行通信(用于发送和接收数据);作为客户端时使用。
异步机制
在Unity开发中,要注意所有与网络相关的操作都应该在协程或异步任务中执行,以避免阻塞UI线程。
C#中的异步操作使用async和await是配合使用的,async是修饰方法X的,await在被async修饰的方法里做标记,标记着一条语句y,主程序运行时候是逐方法逐语句的从上到下执行的,当主程序执行到被async修饰的X方法的时会进入该方法里一步一步的执行语句,当遇到被await标记的y语句的时分叉,主语句会跳出X方法,继续执行X方法下面的方法和语句。而y语句也是同时执行,当执行完了会继续向下执行y语句下面的语句直到X方法结束。参加下面的代码示例:
void Start() { Dosomething(); } public void Dosomething() { X_Async(); Debug.Log("正常的语句异步方法外的"); } public async void X_Async() { await y_等待3秒(); //这个方法会作用3秒时间 Debug.Log("异步方法await后的语句"); } // 上面输出的就是先输出"正常的语句异步方法外的" // 过3秒在输出"异步方法await后的语句"。
异步操作的具体说明可参见C#异步编程
Unity中创建TCP客户端
首先,新建空模型(Create Empty),如重命名为ClientNode;
其次,创建一个新的脚本文件,如命名为Client.cs,并关联到ClientNode上;
TCP客户端的主要步骤如下:
a. 创建TcpClient实例;
b. 异步连接到服务器:tcpClient.ConnectAsync(ipaddr, port);
c. 异步接受数据:tcpClient.GetStream().ReadAsync(buffer, 0, buffer.Length);
d. 发送数据:tcpClient.GetStream().WriteAsync(data, 0, data.Length);
具体代码如下
Client.cs
using System.Text; using System.Threading.Tasks; using System.Net.Sockets; using UnityEngine.Events; using UnityEngine; public class Client : MonoBehaviour { TcpClient tcpClient; //接受到新的数据事件 public UnityEvent RecvContentChanged; //接受的数据长度 public int RecvLen { get; private set; } = 0; //接受的数据 public string RecvContent { get; private set; } = string.Empty; public Client() { } public Client(TcpClient client) { tcpClient = client; } void Start() { TcpClient client = new TcpClient(); //连接到本地服务器及连接端口8090(对应服务器的IP和端口) client.Connect("127.0.0.1", 8090); } void Update() { } //连接到指定IP服务器,以及相应的端口 public async void Connect(string ipaddr, int port=8090) { try { //异步连接到指定服务器及端口 await tcpClient.ConnectAsync(ipaddr, port); Debug.Log("连接成功..."); //连接成功后接受服务器数据 Receive(); } catch (System.Exception e) { Debug.Log(e.Message); } } //接受数据 public async void Receive() { try { while (tcpClient.Connected) { byte[] buffer = new byte[2048]; //异步获取服务器数据 int length = await tcpClient.GetStream().ReadAsync(buffer, 0, buffer.Length); if (length > 0) { RecvLen = length; RecvContent = Encoding.UTF8.GetString(buffer); //发送新的数据以获取的事件 RecvContentChanged?.Invoke(); Debug.Log($"接收长度:{length}"); Debug.Log($"接收内容:{Encoding.UTF8.GetString(buffer)}"); } else //如果获取的数据长度为0,则关闭连接 { tcpClient.Close(); } } } catch (System.Exception e) { Debug.Log(e.Message); tcpClient.Close(); } } //发送数据 public async void Send(byte[] data) { try { //异步往服务器发送数据 await tcpClient.GetStream().WriteAsync(data, 0, data.Length); Debug.Log("发送成功"); } catch (System.Exception e) { Debug.Log(e.Message); tcpClient.Close(); } } }
Unity中其它脚本获取TCP客户端接受到的数据
在Client.cs的代码中,可以发现有如下的代码:
public UnityEvent RecvContentChanged;
该语句中引入了Unity.Event,它是对C#事件封装。通过绑定该事件,其它脚本文件可以来获取接受到的数据。
UnityEvent 对事件的操作提供了不一样的 API:
//绑定 public void AddListener(UnityAction call); //调用 public void Invoke(); //解绑 public void RemoveListener(UnityAction call);
1. 如何使用
首先,我们在其他脚本(如MainLogic.cs)中创建一个函数:
public void onRecvContentChanged() { string recvContent = client.RecvContent; Debug.Log($"Length:{client.RecvLen}, Context: {client.RecvContent}"); }
两种方式进行绑定:
方法一 代码中绑定
//MainLogic.cs public class MainLogic : MonoBehaviour { public Client client; void Start() { client = new Client(); client.RecvContentChanged.AddListener(onRecvContentChanged); } void Update() { } public void onRecvContentChanged() { string recvContent = client.RecvContent; Debug.Log($"Length:{client.RecvLen}, Context: {client.RecvContent}"); } }
方法二:Unity的Inspector中绑定
- 选择MainLogic关联的节点(如Train),将Client.cs关联到MainLogic中的Client变量
- 选择ClientNode,新建(+)Recv Content Changed事件的响应,之后将Train节点拖到Inspector指定位置(如图),再依次绑定到MainLogic的onRecvContentChanged函数。
后续改进
在客户端脚本Client.cs中,接受数据的时候限制了接受数据长度为2048,对于数据长度溢出的问题,后续再来改进优化。