AIServer.cs:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using UnityEngine.Networking;
using Newtonsoft.Json;
public class AIServer : MonoBehaviour
{
// UI组件引用(需在Inspector中赋值)
public InputField inputField_用户名;
public Image image_用户照片;
public Button button_开始识别;
private TcpListener server;
private bool isRunning;
private List<ClientHandler> clients = new List<ClientHandler>();
private object clientLock = new object();
public int port = 9001; // 文档指定端口
private void Start()
{
StartServer();
// 绑定按钮点击事件
button_开始识别.onClick.AddListener(OnStartRecognition);
}
private void OnDestroy()
{
StopServer();
}
public void StartServer()
{
isRunning = true;
server = new TcpListener(IPAddress.Any, port);
server.Start();
Debug.Log($"服务器启动,监听端口: {port}");
new Thread(ListenForClients) { IsBackground = true }.Start();
}
private void ListenForClients()
{
while (isRunning)
{
try
{
var client = server.AcceptTcpClient();
lock (clientLock)
{
var handler = new ClientHandler(client, this);
clients.Add(handler);
handler.Start();
}
}
catch (Exception ex)
{
if (isRunning) Debug.LogError($"连接错误: {ex.Message}");
}
}
}
/// <summary>
/// 点击“开始识别”按钮触发:发送用户信息到客户端
/// </summary>
private void OnStartRecognition()
{
if (string.IsNullOrEmpty(inputField_用户名.text))
{
Debug.LogError("请输入用户名");
return;
}
if (image_用户照片.sprite == null)
{
Debug.LogError("请设置用户照片");
return;
}
// 1. 转换照片为Base64编码(文档要求白名单照片用base64传输)
string base64Photo = ConvertSpriteToBase64(image_用户照片.sprite);
if (string.IsNullOrEmpty(base64Photo))
{
Debug.LogError("照片转换失败");
return;
}
// 2. 构造白名单数据(符合文档4.4格式)
var whitelist = new WhitelistParams();
whitelist.persons.Add(new WhitelistPerson
{
id = Guid.NewGuid().ToString().Substring(0, 5), // 生成简易ID
name = inputField_用户名.text,
photo = base64Photo
});
// 3. 发送给已连接的客户端(示例:发送给第一个客户端)
lock (clientLock)
{
if (clients.Count > 0)
{
clients[0].SendWhitelist(whitelist);
Debug.Log($"已发送用户信息: {inputField_用户名.text}");
}
else
{
Debug.LogError("无连接的客户端");
}
}
}
// 修改ConvertSpriteToBase64方法,添加照片大小限制
private string ConvertSpriteToBase64(Sprite sprite)
{
try
{
Texture2D tex = sprite.texture;
// 压缩图片至合适尺寸(如200x200),减少Base64长度
Texture2D scaledTex = ScaleTexture(tex, 200, 200);
byte[] bytes = scaledTex.EncodeToPNG();
// 释放临时纹理
Destroy(scaledTex);
// 检查大小,避免过大(文档要求仅传输必要照片,)
if (bytes.Length > 1024 * 100) // 限制在100KB以内
{
Debug.LogError("照片过大,超过100KB限制");
return null;
}
return Convert.ToBase64String(bytes);
}
catch (Exception ex)
{
Debug.LogError($"照片编码错误: {ex.Message}");
return null;
}
}
// 辅助方法:缩放纹理
private Texture2D ScaleTexture(Texture2D source, int width, int height)
{
Texture2D result = new Texture2D(width, height);
Color[] rpixels = result.GetPixels(0);
float incX = (1.0f / (float)width);
float incY = (1.0f / (float)height);
for (int px = 0; px < rpixels.Length; px++)
{
float x = incX * ((float)px % width);
float y = incY * ((float)Mathf.Floor(px / width));
rpixels[px] = source.GetPixelBilinear(x, y);
}
result.SetPixels(rpixels, 0);
result.Apply();
return result;
}
public void RemoveClient(ClientHandler client)
{
lock (clientLock)
{
clients.Remove(client);
}
}
public void StopServer()
{
isRunning = false;
server?.Stop();
lock (clientLock)
{
clients.ForEach(c => c.Stop());
clients.Clear();
}
}
}
// 客户端处理器及数据模型(保持原逻辑,新增白名单相关类)
public class ClientHandler
{
private TcpClient client;
private NetworkStream stream;
private AIServer server;
private Thread thread;
private bool isConnected;
private byte[] buffer = new byte[4096];
public string AiServiceId { get; private set; }
public ClientHandler(TcpClient client, AIServer server)
{
this.client = client;
this.server = server;
stream = client.GetStream();
isConnected = true;
}
public void Start()
{
thread = new Thread(HandleClient) { IsBackground = true };
thread.Start();
}
private void HandleClient()
{
while (isConnected)
{
try
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead <= 0) break;
string json = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Debug.Log($"收到客户端消息: {json}");
// 解析客户端请求(注册、心跳等,保持原逻辑)
}
catch (Exception ex)
{
if (isConnected) Debug.LogError($"客户端处理错误: {ex.Message}");
break;
}
}
Stop();
server.RemoveClient(this);
}
// 在ClientHandler的SendWhitelist方法中,确保完整发送
public void SendWhitelist(WhitelistParams whitelist)
{
try
{
var request = new RequestBase
{
id = Guid.NewGuid().ToString(),
jsonrpc = "2.0",
method = "sendWhitelist",
@params = whitelist.persons
};
// 使用Newtonsoft.Json序列化,确保Base64字符串正确转义
string json = JsonConvert.SerializeObject(request);
// 添加明确的结束标记(如"\r\n"),便于客户端识别完整消息
json += "\r\n";
byte[] data = Encoding.UTF8.GetBytes(json);
// 分块发送大数据(避免缓冲区不足导致截断)
int bytesSent = 0;
while (bytesSent < data.Length)
{
int chunkSize = Math.Min(1024, data.Length - bytesSent);
stream.Write(data, bytesSent, chunkSize);
bytesSent += chunkSize;
}
stream.Flush();
Debug.Log($"发送完整白名单,长度: {data.Length}字节");
}
catch (Exception ex)
{
Debug.LogError($"发送白名单失败: {ex.Message}");
}
}
public void Stop()
{
isConnected = false;
stream?.Dispose();
client?.Close();
thread?.Abort();
}
}
[Serializable]
public class RequestBase
{
public string id;
public string jsonrpc = "2.0";
public string method;
public List<WhitelistPerson> @params;
}
[Serializable]
public class WhitelistParams
{
public List<WhitelistPerson> persons = new List<WhitelistPerson>();
}
[Serializable]
public class WhitelistPerson
{
public string id;
public string name;
public string photo; // Base64编码照片
}
AIClient.cs:
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
public class AIClient : MonoBehaviour
{
// 可在Inspector中设置
public string serverIp = "127.0.0.1";
public int serverPort = 9001;
public string deviceId = "d01";
public string aiServiceId = "a01";
// 用于显示接收的用户信息(可选,需在Inspector赋值)
public Text text_接收信息;
private TcpClient client;
private NetworkStream stream;
private Thread receiveThread;
private bool isConnected;
private byte[] buffer = new byte[4096];
private Queue<Action> mainThreadActions = new Queue<Action>();
private void Update()
{
// 主线程处理队列
lock (mainThreadActions)
{
while (mainThreadActions.Count > 0)
{
mainThreadActions.Dequeue().Invoke();
}
}
}
private void Start()
{
ConnectToServer();
}
private void OnDestroy()
{
Disconnect();
}
public void ConnectToServer()
{
try
{
client = new TcpClient();
client.BeginConnect(serverIp, serverPort, OnConnectComplete, null);
Log("正在连接服务器...");
}
catch (Exception ex)
{
LogError($"连接失败: {ex.Message}");
}
}
private void OnConnectComplete(IAsyncResult ar)
{
try
{
client.EndConnect(ar);
isConnected = true;
stream = client.GetStream();
Log("连接服务器成功");
// 发送注册请求(文档3.2通信流程)
SendRegeditRequest();
// 启动接收线程
receiveThread = new Thread(ReceiveMessages) { IsBackground = true };
receiveThread.Start();
// 启动心跳(每10秒)
RunOnMainThread(() => StartCoroutine(SendHeartbeatLoop()));
}
catch (Exception ex)
{
LogError($"连接错误: {ex.Message}");
}
}
// 修改接收逻辑,累积缓冲区数据
private string receivedData = ""; // 用于累积数据
private void ReceiveMessages()
{
while (isConnected)
{
try
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead <= 0) break;
// 累积数据,直到出现结束标记
receivedData += Encoding.UTF8.GetString(buffer, 0, bytesRead);
if (receivedData.Contains("\r\n"))
{
// 分割出完整消息
string[] messages = receivedData.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
// 处理第一个完整消息,剩余数据留待下次
string completeJson = messages[0];
receivedData = string.Join("\r\n", messages, 1, messages.Length - 1);
Log($"收到完整消息: {completeJson}");
RunOnMainThread(() => ParseUserInfo(completeJson));
}
}
catch (Exception ex)
{
if (isConnected) LogError($"接收错误: {ex.Message}");
break;
}
}
Disconnect();
}
// 修改ParseUserInfo方法的解析逻辑
private void ParseUserInfo(string json)
{
try
{
// 使用Newtonsoft.Json解析,支持复杂结构
var request = JsonConvert.DeserializeObject<RequestBase>(json);
if (request.method == "sendWhitelist") // 匹配文档方法名()
{
foreach (var person in request.@params)
{
string info = $"收到用户信息: ID={person.id}, 姓名={person.name}, 照片长度={person.photo.Length}";
Log(info);
if (text_接收信息 != null)
text_接收信息.text = info;
}
}
}
catch (Exception ex)
{
LogError($"解析用户信息错误: {ex.Message},原始JSON: {json}");
}
}
// 以下为原有通信逻辑(注册、心跳等,保持与文档一致)
private void SendRegeditRequest()
{
var request = new RegeditRequest
{
id = Guid.NewGuid().ToString(),
jsonrpc = "2.0",
method = "regedit",
@params = new RegeditParams { deviceid = deviceId, ai_service_id = aiServiceId }
};
SendRequest(JsonUtility.ToJson(request));
}
private IEnumerator SendHeartbeatLoop()
{
while (isConnected)
{
yield return new WaitForSeconds(10);
var request = new HeartbeatRequest
{
id = Guid.NewGuid().ToString(),
jsonrpc = "2.0",
method = "online",
@params = new HeartbeatParams { deviceid = deviceId, ai_service_id = aiServiceId }
};
SendRequest(JsonUtility.ToJson(request));
}
}
private void SendRequest(string json)
{
try
{
byte[] data = Encoding.UTF8.GetBytes(json + "\n");
stream.Write(data, 0, data.Length);
stream.Flush();
Log($"发送请求: {json}");
}
catch (Exception ex)
{
LogError($"发送请求错误: {ex.Message}");
}
}
public void Disconnect()
{
isConnected = false;
receiveThread?.Abort();
stream?.Dispose();
client?.Close();
Log("已断开连接");
}
// 主线程调度工具
private void RunOnMainThread(Action action)
{
lock (mainThreadActions)
{
mainThreadActions.Enqueue(action);
}
}
private void Log(string message) => RunOnMainThread(() => Debug.Log($"[客户端] {message}"));
private void LogError(string message) => RunOnMainThread(() => Debug.LogError($"[客户端] {message}"));
}
// 客户端数据模型(与服务端对应,遵循文档格式)
[Serializable]
public class RequestBase
{
public string id;
public string jsonrpc = "2.0";
public string method;
public List<WhitelistPerson> @params;
}
[Serializable]
public class RegeditRequest
{
public string id;
public string jsonrpc = "2.0";
public string method;
public RegeditParams @params;
}
[Serializable]
public class RegeditParams
{
public string deviceid;
public string ai_service_id;
}
[Serializable]
public class HeartbeatRequest
{
public string id;
public string jsonrpc = "2.0";
public string method;
public HeartbeatParams @params;
}
[Serializable]
public class HeartbeatParams
{
public string deviceid;
public string ai_service_id;
}
[Serializable]
public class WhitelistPerson
{
public string id;
public string name;
public string photo;
}
注意事项: