应粉丝需求做一个服务端与客户端通讯的示例,需求比较简单,我们使用Socket TCP协议去构建,直接使用固定长度信息法。
一、服务端搭建:
打开Visual Studio,文件/新建/项目,创建一个控制台应用:
新建Server类与Client类:
代码如下:
代码语言:javascript复制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);
}
}
}
}
代码语言:javascript复制
代码语言:javascript复制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后我们便可以进行数据的解析,但是我们还没有定义任何数据结构,我们想要传输的数据包括图片和字符,因此这里定义如下数据结构:
代码语言:javascript复制[Serializable]
public class SimpleData
{
/// <summary>
/// 图片数据
/// </summary>
public string pic;
/// <summary>
/// 字符内容
/// </summary>
public string content;
}
引入LitJson命名空间后,解析数据:
代码语言:javascript复制//拆包:解析数据
private void Unpack(string data)
{
SimpleData simpleData = JsonMapper.ToObject<SimpleData>(data);
Console.WriteLine(simpleData.pic);
Console.WriteLine(simpleData.content);
}
此时运行我们的服务端:
二、Unity客户端搭建:
创建Client类,继承自MonoBehaviour,同时定义与服务端一致的数据结构:
代码语言:javascript复制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;
}
代码语言:javascript复制创建一个空物体为其挂载Client脚本:
运行Unity程序,回到服务端控制台窗口,可以看到我们已经成功与服务端连接:
我们找一张图片,将图片和字符数据发送给服务端测试,将它放到Assets目录中,我们通过代码读取这张图片的数据:
示例代码,将其与Client脚本挂在同一物体上:
代码语言:javascript复制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);
}
}
}
代码语言:javascript复制运行程序点击发送数据按钮,回到服务端控制台查看可以看见我们已经接收到数据:
上面是客户端发送数据到服务端的示例,下面我们尝试从服务端发送数据到客户端:
服务端将图片放于解决方案中如图所示位置,我们通过代码读取图片数据:
我们在客户端接入的时候将数据发送给客户端,因此就暂且将其写在Client构造函数里:
代码语言:javascript复制/// <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);
}
代码语言:javascript复制客户端中我们已经将服务端发送的数据存于队列中,因此从队列中将数据依次取出:
代码语言:javascript复制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);
}
}