Unity3d自定义TCP消息替代UNet实现网络连接

news2024/11/25 4:26:40

以前使用UNet实现网络连接,Unity2018以后被弃用了。要将以前的老程序升到高版本,最开始打算使用Mirro,结果发现并不好用。那就只能自己写连接了。

1.TCP消息结构

(1). TCP消息是按流传输的,会发生粘包。那么在发射和接收消息时就需要对消息进行打包和解包。如果接收的消息长度不足,先不处理,继续接收。

(2).当TCP客户端断开时,服务端是收不到通知的。解决的方法是通过是否收到自定义消息来判断客户端是否在线。

这里用的消息结构如下,

第1部分为4个字节,表示消息长度,包括消息ID和消息体;

第2部分为2个字节,表示消息ID;

第3部分为n个字节,表示消息体;

2.辅助类和插件

(1). UnityThread:接收消息时在子线程中进行,处理消息后更新界面则只能在主线程中进行,这个类就是为了把实子线程中的有些操作放到主线程中。

(2). Newtonsoft.Json:这个插件可以实现Json字符串与Json对象之间的转换。

3.注意事项

(1). 将服务端和客户端设置为可后台运行Application.runInBackground = true

(2). UnityThread使用之前一定要初始化UnityThread.initUnityThread();

4.服务端代码

TcpServerScript .cs

using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;

public class TcpServerScript : MonoBehaviour
{
    public Image imageTcpServerStatus;
    public Text textConnectCount;
    public Text textPrompt;

    //上一次接收到消息时间,客户端是否在线以本数组为准
    Dictionary<int, TcpClientInfo> dictConnectId2Client;
    public Dictionary<int, float> dictConnectId2Time;
    private Dictionary<int, ClientRemainData> dictConnectId2RemainData;

    private int port = 6001;
    /// <summary>

    private TcpListener tcpListener;
    private bool running = false;
    /// <summary>
    /// Background thread for TcpServer workload.  
    /// </summary>  
    private Thread tcpListenerThread;

    int globalConnectId = 1;

    void Awake()
    {
        UnityThread.initUnityThread();
    }

    void Start()
    {
        dictConnectId2Time = new Dictionary<int, float>();
        dictConnectId2Client = new Dictionary<int, TcpClientInfo>();
        dictConnectId2RemainData = new Dictionary<int, ClientRemainData>();

        StartCoroutine(delayStartTcpServer());
    }

    IEnumerator delayStartTcpServer()
    {
        yield return new WaitForSeconds(0.5f);
        try
        {
            tcpListener = new TcpListener(IPAddress.Any, 6001);
            tcpListener.Start();
            imageTcpServerStatus.color = Color.green;
        }
        catch (Exception ex)
        {
            imageTcpServerStatus.color = Color.gray;
            Debug.Log(ex.Message);
            yield break;
        }

        tcpListenerThread = new Thread(new ThreadStart(ListenForIncommingRequests));
        tcpListenerThread.IsBackground = true;
        tcpListenerThread.Start();
    }

    private void Update()
    {
        if (Time.frameCount % 10 == 0)
        {
            List<int> keys = dictConnectId2Time.Keys.ToList();
            for (int i = 0; i < keys.Count; i++)
            {
                int connectId = keys[i];
                if (Time.time - dictConnectId2Time[connectId] > 3.0f)
                {
                    OnServerDisconnected(connectId);
                }
            }

            //StringMsg msg = new StringMsg();
            //msg.str = "hello";
            //keys = dictConnectId2Client.Keys.ToList();
            //for (int i = 0; i < keys.Count; i++)
            //{
            //    int key = keys[i];
            //    ServerSendOne(dictConnectId2Client[key].client,MessageId.MsgId_StringMsg, msg);
            //}
        }

        textConnectCount.text = string.Format("连接数:{0}", dictConnectId2Client.Count);
    }

    private void ListenForIncommingRequests()
    {
        running = true;
        ThreadPool.QueueUserWorkItem(this.ListenerWorker, null);
    }
    private void ListenerWorker(object token)
    {
        while (running)
        {
            TcpClient connectedTcpClient = tcpListener.AcceptTcpClient();
            TcpClientInfo clientInfo = new TcpClientInfo(connectedTcpClient, globalConnectId);
            UnityThread.executeInUpdate(() =>
            {
                if (!dictConnectId2Client.ContainsKey(clientInfo.connectionId))
                {
                    dictConnectId2Client.Add(clientInfo.connectionId, clientInfo);           

                    IPEndPoint endPoint = connectedTcpClient.Client.RemoteEndPoint as IPEndPoint;
                    string clientIp = endPoint.Address.ToString();
                    Debug.Log(clientIp);
                }
            });

            Debug.Log("连接:" + dictConnectId2Client.Count);
            ThreadPool.QueueUserWorkItem(this.HandleClientWorker, clientInfo);
        }
        tcpListener.Stop();
    }

    private void HandleClientWorker(object token)
    {
        var clientInfo = token as TcpClientInfo;
        int connId = clientInfo.connectionId;
        if (!dictConnectId2RemainData.ContainsKey(clientInfo.connectionId))
        {
            dictConnectId2RemainData.Add(clientInfo.connectionId, new ClientRemainData(clientInfo));
        }

        using (var client = clientInfo.client as TcpClient)
        using (var nwStream = client.GetStream())
        {
            while (running)
            {
                byte[] bufNumber = new byte[1000];
                int byReadNumber = nwStream.Read(bufNumber, 0, 1000);
                if (byReadNumber < 1)
                {
                    dictConnectId2Client.Remove(clientInfo.connectionId);
                    Debug.Log("断开1:" + clientInfo.connectionId);
                    break;
                }

                byte[] btsAdd = new byte[dictConnectId2RemainData[connId].lenRemain + byReadNumber];

                if (dictConnectId2RemainData[connId].lenRemain > 0)
                {
                    //拼接上次处理的字节
                    Array.Copy(dictConnectId2RemainData[connId].btsRemain, 0, btsAdd, 0, dictConnectId2RemainData[connId].lenRemain);
                }
                Array.Copy(bufNumber, 0, btsAdd, dictConnectId2RemainData[connId].lenRemain, byReadNumber);

                List<byte> listRemain = new List<byte>();
                dealwithData(clientInfo, btsAdd, listRemain);
                if (listRemain.Count > 0)
                {
                    byte[] btsTemp = listRemain.ToArray();
                    Array.Copy(btsTemp, 0, dictConnectId2RemainData[connId].btsRemain, 0, btsTemp.Length);
                    dictConnectId2RemainData[connId].lenRemain = btsTemp.Length;
                }
                else
              

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1849609.html

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

相关文章

2024 年解锁 Android 手机的 7 种简便方法

您是否忘记了 Android 手机的 Android 锁屏密码&#xff0c;并且您的手机已被锁定&#xff1f;您需要使用锁屏解锁 Android 手机&#xff1f;别担心&#xff0c;您不是唯一一个忘记密码的人。我将向您展示如何解锁 Android 手机的锁屏。 密码 PIN 可保护您的 Android 手机和 G…

【数据结构】第十九弹---C语言实现冒泡排序算法

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、冒泡排序基本思想 2、代码的初步实现 3、代码的优化 4、代码的测试 5、时空复杂度分析 6、模拟实现qsort 6.1、冒泡排序函数 6.2、交换数…

Android SurfaceFlinger——服务启动流程(二)

SurfaceFlinger 是 Android 系统中的一个核心服务&#xff0c;负责管理图形缓冲区的合成和屏幕显示&#xff0c;是 Android 图形系统的关键组件。 一、启动流程 SurfaceFlinger 作为一个系统服务&#xff0c;在 Android 启动早期由 init 进程通过 servicemanager 启动。它是作…

Vue3中的常见组件通信(超详细版)

Vue3中的常见组件通信 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-model3. $refs4. 默认…

[Django学习]前端+后端两种方式处理图片流数据

方式1&#xff1a;数据库存放图片地址,图片存放在Django项目文件中 1.首先&#xff0c;我们现在models.py文件中定义模型来存放该图片数据,前端传来的数据都会存放在Django项目文件里的images文件夹下 from django.db import modelsclass Image(models.Model):title models.C…

深度神经网络——什么是小样本学习?

引言 小样本学习是指使用极少量的训练数据来开发人工智能模型的各种算法和技术。小样本学习致力于让人工智能模型在接触相对较少的训练实例后识别和分类新数据。小样本训练与训练机器学习模型的传统方法形成鲜明对比&#xff0c;传统方法通常使用大量训练数据。小样本学习是 主…

aws的alb,多个域名绑定多个网站实践

例如首次创建的alb负载均衡只有www.xxx.com 需要添加 负载 test2.xxx.com aws的Route 53产品解析到负载均衡 www.xxx.com 添加CNAME&#xff0c;到负载均衡的dns字段axx test2.xxx.com 添加CNAME&#xff0c;到负载均衡的dns字段axx 主要介绍目标组和规则 创建alb就不介…

MacOS 中 Agent 图标删除

这个是战网没有完全卸载赶紧导致的 在访达中点击前往文件夹&#xff0c;输入&#xff1a; /Users/Shared将对应的目录删掉即可。会提示需要输入密码。

Java 从零开始写一个简单的图书管理系统

了解一下 先来了解要实现一个怎样的图书管理系统 从中可以看到有操作的 使用者 和 不同 的 功能 而不同的使用者有不同的 菜单 那要如何实现呢&#xff1f; 请继续看下去 如何实现 首先了解我们 需要什么 图书系统需要 书&#xff0c;放书的 书架 &#xff0c;用户 中有 管…

Nutch爬虫在大数据采集中的应用案例

引言 在当今信息爆炸的时代&#xff0c;大数据的价值日益凸显。网络作为信息的海洋&#xff0c;蕴藏着丰富的数据资源。Nutch&#xff0c;作为一个开源的Java编写的网络爬虫框架&#xff0c;以其高效的数据采集能力和良好的可扩展性&#xff0c;成为大数据采集的重要工具。本文…

系统烧写工具--MfgTool

系统烧写工具--MfgTool 1 介绍1.1 概述1.2 UUU 特性1.3 UUU 功能1.4 UUU 命令1.5 MFGTools 功能 2 MFGTools 目录结构及说明2.1 MFGTools 目录结构重要文件烧写自己系统 2.2 说明2.3 分析配置文件2.3.1 UiCfg.ini2.3.2 cfg.ini2.3.3 ucl2.xml 3 MfgTool 工作流程4 烧录流程4.1 …

mysql--安装跳过验证修改密码安全加固

安装mysql 配置mysql的yum源 [rootVM-0-14-rockylinux ~]# tee /etc/yum.repos.d/mysql.repo << EOF > [MYSQL] > namemysql > baseurlhttps://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql-5.7-community-el7-x86_64 > gpgcheck0 > EOF yum安装mysq…

python项目(课设)——飞机大战小游戏项目源码(pygame)

主程序 import pygame from plane_sprites import * class PlaneGame: """ 游戏类 """ def __init__(self): print("游戏初始化") # 初始化字体模块 pygame.font.init() # 创建游戏…

eclipse中svn从分支合并到主干及冲突解决

1、将分支先commit&#xff0c;然后再update&#xff0c;然后再clean一下&#xff0c;将项目多余的target都清理掉。 2、将branches切换到trunk 3、工程上右键-》Team-》合并&#xff08;或Merge&#xff09; 4、默认选项&#xff0c;点击Next 5、有未提交的改动&#xff0c;…

兴顺物流管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;驾驶员管理&#xff0c;物流资讯管理&#xff0c;车辆管理&#xff0c;基础数据管理 员工账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;物流资讯管理&…

尚品汇-(三)

maven之packaging标签 &#xff08;1&#xff09;项目创建父模块 首先设置 下Maven Maven&#xff1a;仓库地址&#xff1a;这里是腾讯云仓库 作为父模块&#xff0c;src没用&#xff0c;干掉src 这里我们是Maven创建的项目&#xff0c;想要项目变成SpringBoot的项目&#xf…

Mybatis 系列全解(1)——全网免费最细最全,手把手教,学完就可做项目!

Mybatis 系列全解&#xff08;1&#xff09; 1. 第一个小程序2. CURD 增删改查3. 模糊查询4. 配置解析4.1 核心配置文件4.2 环境配置4.3 属性4.4 类型别名4.5 设置4.6 映射器 mappers 1. 第一个小程序 1&#xff09;创建一个数据库&#xff0c;一个表&#xff0c;填入一些数据…

uni app 树状结构数据展示

树状数据展示&#xff0c;可以点击item 将点击数据给父组件 &#xff0c;满足自己需求。不喜勿喷&#xff0c;很简单可以根据自己需求改哈&#xff0c;不要问&#xff0c;点赞收藏就好。其实可以和上一篇文章uni app 自定义 带popup弹窗的input组件-CSDN博客结合使用&#xff…

LabVIEW项目管理中如何平衡成本、时间和质量

在LabVIEW项目管理中&#xff0c;平衡成本、时间和质量是实现项目成功的关键。通过制定详细的项目计划、合理分配资源、严格控制进度、进行质量保证和灵活应对变化&#xff0c;项目管理者可以有效地协调这三者的关系&#xff0c;确保项目按时、按质、按预算完成。 1. 制定详细…

Reid系列论文学习——换装Reid

今天要学习的有关Reid的论文是2019年提出的一篇名为&#xff1a;Beyond Scalar Neuron: Adopting Vector-Neuron Capsules for Long-Term Person Re-Identification. 论文链接&#xff1a;https://opus.lib.uts.edu.au/bitstream/10453/137156/4/Binder1.pdf Code链接&#x…