应粉丝需求做一个服务端与客户端通讯的示例,需求比较简单,我们使用Socket TCP协议去构建,直接使用固定长度信息法。
一、服务端搭建:
打开Visual Studio,文件/新建/项目,创建一个控制台应用:
新建Server类与Client类:
编辑代码如下:
using System.Net; using System.Net.Sockets; namespace CoderZ { public class Server { //端口 private const int port = 8008; //客户端列表 private List<Client> clients = new List<Client>(); private static void Main(string[] args) { Console.WriteLine("服务端启动..."); Server server = new Server(); server.Init(); } //服务端初始化 private void Init() { TcpListener listener = new TcpListener(IPAddress.Any, port); listener.Start(); try { while (true) { Console.WriteLine("等待客户端接入..."); TcpClient client = listener.AcceptTcpClient(); Client clientInstance = new Client(client, this); clients.Add(clientInstance); Console.WriteLine($"{client.Client.RemoteEndPoint}接入."); } } catch(Exception error) { throw new Exception(error.ToString()); } } /// <summary> /// 广播:向所有客户端发送数据 /// </summary> /// <param name="data"></param> public void Broadcast(string data) { for (int i = 0; i < clients.Count; i++) { clients[i].Send(data); } } /// <summary> /// 移除客户端 /// </summary> /// <param name="client"></param> public void Remove(Client client) { if (clients.Contains(client)) { clients.Remove(client); } } } }
using System.Text; using System.Net.Sockets; namespace CoderZ { public class Client { private Server server; private TcpClient tcpClient; private NetworkStream stream; /// <summary> /// 构造函数 /// </summary> /// <param name="tcpClient"></param> /// <param name="server"></param> public Client(TcpClient tcpClient, Server server) { this.server = server; this.tcpClient = tcpClient; //启动线程 读取数据 Thread thread = new Thread(TcpClientThread); thread.Start(); } private void TcpClientThread() { stream = tcpClient.GetStream(); //使用固定长度 byte[] buffer = new byte[1024]; try { while (true) { int length = stream.Read(buffer, 0, buffer.Length); if (length != 0) { string data = Encoding.UTF8.GetString(buffer, 0, length); //解包 Unpack(data); } } } catch(Exception error) { Console.WriteLine(error.ToString()); } finally { server.Remove(this); } } //拆包:解析数据 private void Unpack(string data) { } /// <summary> /// 发送数据 /// </summary> /// <param name="data"></param> public void Send(string data) { byte[] buffer = Encoding.UTF8.GetBytes(data); stream.Write(buffer, 0, buffer.Length); } } }
数据的解析我们这里使用LitJson.dll工具,没有该工具的可以联系我发一份,打开视图/解决方案资源管理器:
右键解决方案/添加/项目引用:
点击浏览,找到LitJson工具,点击确定进行引用:
有了LitJson后我们便可以进行数据的解析,但是我们还没有定义任何数据结构,我们想要传输的数据包括图片和字符,因此这里定义如下数据结构:
[Serializable] public class SimpleData { /// <summary> /// 图片数据 /// </summary> public string pic; /// <summary> /// 字符内容 /// </summary> public string content; }
引入LitJson命名空间后,解析数据:
//拆包:解析数据 private void Unpack(string data) { SimpleData simpleData = JsonMapper.ToObject<SimpleData>(data); Console.WriteLine(simpleData.pic); Console.WriteLine(simpleData.content); }
此时运行我们的服务端:
二、Unity客户端搭建:
创建Client类,继承自MonoBehaviour,同时定义与服务端一致的数据结构:
using System; using System.Text; using UnityEngine; using System.Threading; using System.Net.Sockets; using System.Collections.Generic; public class Client : MonoBehaviour { private string ipAddress; private int port; private bool isConnected; private Thread connectThread; private Thread readDataThread; private TcpClient tcpClient; private NetworkStream stream; //将数据存于队列 依次取出 private Queue<string> queue = new Queue<string>(); private void Start() { connectThread = new Thread(ConnectThead); connectThread.Start(); } //连接线程 private void ConnectThead() { tcpClient = new TcpClient(); tcpClient.BeginConnect(ipAddress, port, ConnectThreadCallBack, tcpClient); float waitTime = 0f; while (!isConnected) { Thread.Sleep(500); waitTime += Time.deltaTime; if (waitTime > 3f) { waitTime = 0f; throw new Exception("连接超时"); } } } private void ConnectThreadCallBack(IAsyncResult result) { tcpClient = result.AsyncState as TcpClient; if (tcpClient.Connected) { isConnected = true; tcpClient.EndConnect(result); stream = tcpClient.GetStream(); readDataThread = new Thread(ReadDataThread); readDataThread.Start(); } } //读取数据线程 private void ReadDataThread() { try { while (isConnected) { byte[] buffer = new byte[1024]; int length = stream.Read(buffer, 0, buffer.Length); string data = Encoding.UTF8.GetString(buffer, 0, length); queue.Enqueue(data); } } catch(Exception error) { throw new Exception(error.ToString()); } } //程序退出时关闭线程 private void OnApplicationQuit() { stream?.Close(); connectThread?.Abort(); readDataThread?.Abort(); } /// <summary> /// 发送数据 /// </summary> /// <param name="content"></param> public void SendData(string content) { byte[] buffer = Encoding.UTF8.GetBytes(content); stream.Write(buffer, 0, buffer.Length); } } [Serializable] public class SimpleData { /// <summary> /// 图片数据 /// </summary> public string pic; /// <summary> /// 字符内容 /// </summary> public string content; }
创建一个空物体为其挂载Client脚本:
运行Unity程序,回到服务端控制台窗口,可以看到我们已经成功与服务端连接:
我们找一张图片,将图片和字符数据发送给服务端测试,将它放到Assets目录中,我们通过代码读取这张图片的数据:
示例代码,将其与Client脚本挂在同一物体上:
using System; using System.IO; using UnityEngine; using LitJson; public class Foo : MonoBehaviour { private void OnGUI() { if (GUILayout.Button("发送数据", GUILayout.Width(200f), GUILayout.Height(50f))) { var bytes = File.ReadAllBytes(Application.dataPath + "/pic.jpg"); SimpleData simpleData = new SimpleData() { pic = Convert.ToString(bytes), content = "这是一张汽车图片" }; //使用LitJson序列化 string data = JsonMapper.ToJson(simpleData); GetComponent<Client>().SendData(data); } } }
运行程序点击发送数据按钮,回到服务端控制台查看可以看见我们已经接收到数据:
上面是客户端发送数据到服务端的示例,下面我们尝试从服务端发送数据到客户端:
服务端将图片放于解决方案中如图所示位置,我们通过代码读取图片数据:
我们在客户端接入的时候将数据发送给客户端,因此就暂且将其写在Client构造函数里:
/// <summary> /// 构造函数 /// </summary> /// <param name="tcpClient"></param> /// <param name="server"></param> public Client(TcpClient tcpClient, Server server) { this.server = server; this.tcpClient = tcpClient; //启动线程 读取数据 Thread thread = new Thread(TcpClientThread); thread.Start(); byte[] bytes = File.ReadAllBytes("pic.jpg"); SimpleData simpleData = new SimpleData() { pic = Convert.ToBase64String(bytes), content = "这是一张图片" }; string data = JsonMapper.ToJson(simpleData); Send(data); }
客户端中我们已经将服务端发送的数据存于队列中,因此从队列中将数据依次取出:
private void Update() { if (queue.Count > 0) { string data = queue.Dequeue(); //使用LitJson反序列化 SimpleData simpleData = JsonMapper.ToObject<SimpleData>(data); byte[] bytes = Convert.FromBase64String(simpleData.pic); //将图片存到Assets目录 File.WriteAllBytes(Application.dataPath + "/test.jpg", bytes); //打印字符内容 Debug.Log(simpleData.content); } }