Unity TCP协议对接AI人脸识别接口程序测试

发布于:2025-08-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

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;
}

注意事项:


网站公告

今日签到

点亮在社区的每一天
去签到