当前位置: 首页 > article >正文

Unity中的简易TCP服务器/客户端

在本文中,我将向你介绍一个在Unity中实现的简单TCP服务器脚本,和一个简单的客户端脚本.

脚本 MyTcpServer 允许Unity应用创建一个TCP服务器,监听客户端的连接、异步处理客户端消息,并通过事件与Unity应用中的其他模块进行通信。

MyTcpServer 类是使用 C# 的 TcpListener 实现的自定义 TCP 服务器,该服务器监听指定端口的传入 TCP 连接,并以异步、非阻塞的方式处理与客户端的通信.

AcceptClientsAsync 方法是一个异步方法,使用 TcpListener.AcceptTcpClientAsync() 来接受客户端的连接。每当一个新客户端连接时,服务器会将其添加到 connectedClients 列表中,并为该客户端创建一个新的任务 (Task.Run()) 来处理客户端的消息。

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using System.IO;
using System.Collections.Generic;

public class MyTcpServer
{
    private static MyTcpServer instance;
    private TcpListener tcpListener;
    private CancellationTokenSource cts = new CancellationTokenSource();
    private List<TcpClient> connectedClients = new List<TcpClient>(); // 管理客户端连接
    public static bool isOnce = false;

    public static event Action<string> sendEvent;

    public static MyTcpServer Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new MyTcpServer();
            }
            return instance;
        }
    }

    public void StartServer()
    {
        try
        {
            var p = Path.Combine(Application.streamingAssetsPath, "MyPort.txt");
            if (!File.Exists(p)) return;
            var port = File.ReadAllText(p, Encoding.UTF8);
            Debug.Log($"Starting server on port {port}");
            tcpListener = new TcpListener(IPAddress.Any, int.Parse(port));
            tcpListener.Start();
            isOnce = true;

            Task.Run(() => AcceptClientsAsync(cts.Token));
        }
        catch (Exception ex)
        {
            Debug.LogError($"Error starting server: {ex.Message}");
        }
    }

    private async Task AcceptClientsAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try
            {
                TcpClient client = await tcpListener.AcceptTcpClientAsync();
                Debug.Log($"Client connected: {client.Client.RemoteEndPoint}");
                connectedClients.Add(client);
                _ = Task.Run(() => HandleClientAsync(client, token));
            }
            catch (Exception ex)
            {
                Debug.LogError($"Error accepting client: {ex.Message}");
            }
        }
    }

    private async Task HandleClientAsync(TcpClient client, CancellationToken token)
    {
        using (client)
        {
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[4096];

            while (!token.IsCancellationRequested)
            {
                try
                {
                    int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token);
                    if (bytesRead == 0) break;

                    string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                    Debug.Log($"Received: {message}");
                    sendEvent?.Invoke(message);
                }
                catch (Exception ex)
                {
                    Debug.LogError($"Error reading from client: {ex.Message}");
                    break;
                }

                await Task.Delay(10, token);
            }
        }
        // Cleanup on client disconnect
        connectedClients.Remove(client);
    }

    public void StopServer()
    {
        if (cts != null)
        {
            cts.Cancel();
            if (tcpListener != null)
            {
                tcpListener.Stop();
                tcpListener = null;
            }
            cts.Dispose();
            cts = null;
        }

        // Ensure that all connected clients are closed properly
        foreach (var client in connectedClients)
        {
            client.Close();
        }
        connectedClients.Clear();
    }
}

MyClient 类是一个简单的客户端实现,能够与 TCP 服务器进行通信。它提供了连接服务器、发送命令以及关闭连接等基本功能。

using System.Collections;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public class MyClient
{
    private TcpClient client;
    private NetworkStream stream;

    private static MyClient _ins; // 单例实例

    private static bool connected = false;

    public static MyClient ins
    {
        get
        {
            if (_ins == null)
            {
                _ins = new MyClient();
            }

            return _ins;
        }
    }

    // 私有构造函数,防止外部实例化
    private MyClient()
    {
    }

    public void Init()
    {
        if (!connected)
        {
            connected = true;
            var path = Path.Combine(Application.streamingAssetsPath, "IpFile.txt");
            var p = Path.Combine(Application.streamingAssetsPath, "MyPort.txt");
            if (!File.Exists(p) || !File.Exists(path)) return;

            Debug.Log($"Reading IP configuration from: {path}");
            Debug.Log($"Reading port from: {p}");
            var ipAddress = File.ReadAllText(path, Encoding.UTF8);
            var port = File.ReadAllText(p, Encoding.UTF8);
            Debug.Log(ipAddress);
            Debug.Log(port);

            ConnectToServer(ipAddress, int.Parse(port));
        }
    }

    private async void ConnectToServer(string ip, int port)
    {
        int maxRetryAttempts = 5; // 最大重试次数
        int retryDelayMilliseconds = 1500; // 重试间隔,单位为毫秒

        for (int attempt = 1; attempt <= maxRetryAttempts; attempt++)
        {
            try
            {
                client = new TcpClient();
                await client.ConnectAsync(ip, port); // 异步连接服务器
                stream = client.GetStream();
                Debug.Log("Connected to server.");
                return; // 成功连接则退出方法
            }
            catch (SocketException e)
            {
                Debug.LogError($"Attempt {attempt} failed to connect: {e.Message}");

                if (attempt < maxRetryAttempts)
                {
                    Debug.Log($"Retrying in {retryDelayMilliseconds / 1000} seconds...");
                    await Task.Delay(retryDelayMilliseconds); // 等待重试
                }
                else
                {
                    Debug.LogError("Max retry attempts reached. Unable to connect to server.");
                }
            }
        }
    }


    public async Task SendCommand(string command)
    {
        if (stream != null && client.Connected)
        {
            try
            {
                byte[] data = Encoding.UTF8.GetBytes(command);
                await stream.WriteAsync(data, 0, data.Length); // 发送数据到服务器
                Debug.Log($"Command sent: {command}");
            }
            catch (SocketException e)
            {
                Debug.LogError($"Error sending command: {e.Message}");
            }
        }
        else
        {
            Debug.LogWarning("Not connected to server.");
        }
    }

    public void CloseConnection()
    {
        if (stream != null)
        {
            stream.Close();
            stream = null;
        }

        if (client != null)
        {
            client.Close();
            client = null;
        }

        Debug.Log("Connection closed.");
    }
}


http://www.kler.cn/a/411842.html

相关文章:

  • 【C语言】前端项目故障处理。
  • SSM框架测试题整理(上)
  • .Net与C#
  • element-ui 中el-calendar 日历插件获取显示的第一天和最后一天【原创】
  • 2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest(ABCGJLN)
  • 超高流量多级缓存架构设计!
  • macos 14.0 Monoma 修改顶部菜单栏颜色
  • Leetcode53. 最大子数组和(HOT100)
  • numpy.digitize函数介绍
  • 缺失的第一个正数(java)
  • 挂载本地目录到k8s的pod实现持久化存储
  • [java] 什么是 Apache Felix
  • wp the_posts_pagination 与分类页面搭配使用
  • git-显示顺序与提交顺序不一致的问题
  • unity3d——基础篇2刷(Mathf练习题)
  • RabbitMQ的预取值详解
  • 泷羽sec-linux进阶
  • postman的简单使用
  • 【mac】终端左边太长处理,自定义显示名称(terminal路径显示特别长)
  • 前端小练习——星辰宇宙(JS没有上限!!!)
  • 51单片机从入门到精通:理论与实践指南(一)
  • Hadoop的MapReduce详解
  • 详细描述一下Elasticsearch更新和删除文档的过程?
  • 【Linux】Ubuntu:轻量级Xfce桌面及远程连接
  • 对比C++,Rust在内存安全上做的努力
  • shell数组 Linux分文件 make工具