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

news2024/11/27 14:49:27

在本文中,我将向你介绍一个在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.coloradmin.cn/o/2248474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

春秋云境 CVE 复现

CVE-2022-4230 靶标介绍 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数&#xff0c;这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下&#xff0c;具有管理选项功能 (admin) 的用户可以使用受影响的功能&#xff0c;但是该插件有一个设置允许低权限用…

图论入门编程

卡码网刷题链接&#xff1a;98. 所有可达路径 一、题目简述 二、编程demo 方法①邻接矩阵 from collections import defaultdict #简历邻接矩阵 def build_graph(): n, m map(int,input().split()) graph [[0 for _ in range(n1)] for _ in range(n1)]for _ in range(m): …

Jackson库中JsonInclude的使用

简介 JsonInclude是 Jackson 库&#xff08;Java 中用于处理 JSON 数据的流行库&#xff09;中的一个注解。它用于控制在序列化 Java 对象为 JSON 时&#xff0c;哪些属性应该被包含在 JSON 输出中。这个注解提供了多种策略来决定属性的包含与否&#xff0c;帮助减少不必要的数…

鸿蒙学习自由流转与分布式运行环境-价值与架构定义(1)

文章目录 价值与架构定义1、价值2、架构定义 随着个人设备数量越来越多&#xff0c;跨多个设备间的交互将成为常态。基于传统 OS 开发跨设备交互的应用程序时&#xff0c;需要解决设备发现、设备认证、设备连接、数据同步等技术难题&#xff0c;不但开发成本高&#xff0c;还存…

【论文复现】融入模糊规则的宽度神经网络结构

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 融入模糊规则的宽度神经网络结构 论文概述创新点及贡献 算法流程讲解核心代码复现main.py文件FBLS.py文件 使用方法测试结果示例&#xff1a…

网上蛋糕售卖店管理系(Java+SpringBoot+MySQL)

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装网上蛋糕售卖店管理系统软件来发挥其高效地信息处理的作用…

Vue.js基础——贼简单易懂!!(响应式 ref 和 reactive、v-on、v-show 和 v-if、v-for、v-bind)

Vue.js是一个渐进式JavaScript框架&#xff0c;用于构建用户界面。它专门设计用于Web应用程序&#xff0c;并专注于视图层。Vue允许开发人员创建可重用的组件&#xff0c;并轻松管理状态和数据绑定。它还提供了一个虚拟DOM系统&#xff0c;用于高效地渲染和重新渲染组件。Vue以…

从 0 到 1 掌握部署第一个 Web 应用到 Kubernetes 中

文章目录 前言构建一个 hello world web 应用项目结构项目核心文件启动项目 检查项目是否构建成功 容器化我们的应用编写 Dockerfile构建 docker 镜像推送 docker 镜像仓库 使用 labs.play-with-k8s.com 构建 Kubernetes 集群并部署应用构建 Kubernetes 集群环境编写部署文件 总…

数据结构 【二叉树(上)】

谈到二叉树&#xff0c;先来谈谈树的概念。 1、树的概念及结构 树是一种非线性的数据结构&#xff0c;它的逻辑关系看起来像是一棵倒着的树&#xff0c;也就是说它是根在上&#xff0c;而叶子在下的&#xff0c; 在树这种数据结构中&#xff0c;最顶端的结点称为根结点。在树的…

Error: Invalid version flag: if 问题排查

问题描述&#xff1a; 国产化系统适配&#xff0c;arm架构的centos 在上面运行docker 启动后需要安装数据库 依赖perl 在yum install -y perl 时提示&#xff1a; “Error: Invalid version flag: if”

QML学习 —— 34、视频媒体播放器(附源码)

效果 说明 您可以单独使用MediaPlayer播放音频内容(如音频),也可以将其与VideoOutput结合使用以渲染视频。VideoOutput项支持未转换、拉伸和均匀缩放的视频演示。有关拉伸均匀缩放演示文稿的描述,请参见fillMode属性描述。 播放可能出错问题 出现的问题:      DirectS…

架构-微服务-服务网关

文章目录 前言一、网关介绍1. 什么是API网关2. 核心功能特性3. 解决方案 二、Gateway简介三、Gateway快速入门1. 基础版2. 增强版3. 简写版 四、Gateway核心架构1. 基本概念2. 执行流程 五、Gateway断言1. 内置路由断言工厂2. 自定义路由断言工厂 六、过滤器1. 基本概念2. 局部…

洛谷 P1722 矩阵 II C语言 记忆化搜索

题目&#xff1a; https://www.luogu.com.cn/problem/P1722 我们按照案例画一下 我们会发现&#xff0c;会出现重复的子结构。 代码如下&#xff1a; #include<iostream> using namespace std; int mem[300][300]; int n; int f[305][305]; int dfs(int x,int red,…

PICO 获取设备号 SN码

Unity版本 2020.3.42f1c1PICO SDK版本PICO Unity Integration SDK-3.0.5-20241105Pico设备pico 4ultra 注意 此api暂时只测试企业版本 pico 4ultra 代码 using Unity.XR.PICO.TOBSupport;private void Awake() {bool result PXR_Enterprise.InitEnterpriseService();Debug.L…

从 HTML 到 CSS:开启网页样式之旅(二)—— 深入探索 CSS 选择器的奥秘

从 HTML 到 CSS&#xff1a;开启网页样式之旅&#xff08;二&#xff09;—— 深入探索 CSS 选择器的奥秘 前言一、CSS基本选择器1. 通配选择器2. 元素选择器3. 类选择器4. id选择器5.基本选择器总结 二、CSS复合选择器1. 后代选择器2. 子选择器3. 相邻兄弟选择器4.交集选择器5…

解决Flink读取kafka主题数据无报错无数据打印的重大发现(问题已解决)

亦菲、彦祖们&#xff0c;今天使用idea开发的时候&#xff0c;运行flink程序&#xff08;读取kafka主题数据&#xff09;的时候&#xff0c;发现操作台什么数据都没有只有满屏红色日志输出&#xff0c;关键干嘛&#xff1f;一点报错都没有&#xff0c;一开始我觉得应该执行程序…

零基础3分钟快速掌握 ——Linux【终端操作】及【常用指令】Ubuntu

1.为啥使用Linux做嵌入式开发 能广泛支持硬件 内核比较高效稳定 原码开放、软件丰富 能够完善网络通信与文件管理机制 优秀的开发工具 2.什么是Ubuntu 是一个以桌面应用为主的Linux的操作系统&#xff0c; 内核是Linux操作系统&#xff0c; 具有Ubuntu特色的可视…

xiaolin coding 图解网络笔记——TCP篇

1. TCP 头格式有哪些&#xff1f; 序列号&#xff1a;在建立连接时由计算机生成的随机数作为其初始值&#xff0c;通过 SYN 包传给接收端主机&#xff0c;每发送一次数据&#xff0c;就【累加】一次该【数据字节数】的大小。用来解决网络包乱序问题。 确认应答号&#xff1a;指…

使用Go 语言连接并操作 MySQL 数据库

新建项目&#xff0c;我这里使用的vscode&#xff1a; 1.新建项目初始化&#xff1a; 手动创建工程文件夹go安装目录->src->projectName 在项目下创建 main.go文件&#xff1a; 在vscode中点击文件->打开文件夹&#xff0c;选择刚刚新建的文件夹。打开后&#xff0…

Jmeter中的断言

7&#xff09;断言 1--响应断言 功能特点 数据验证&#xff1a;验证响应数据是否包含或不包含特定的字符串、模式或值。多种匹配类型&#xff1a;支持多种匹配类型&#xff0c;如文本、正则表达式、文档等。灵活配置&#xff1a;可以设置多个断言条件&#xff0c;满足复杂的测…