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

以前使用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
              

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RockChip Android12 System之MultipleUsers

一:概述 System中的MultipleUsers不同于其他Preference采用system_dashboard_fragment.xml文件进行加载,而是采用自身独立的xml文件user_settings.xml加载。 二:Multiple Users 1、Activity packages/apps/Settings/AndroidManifest.xml <activityandroid:name="S…

计算机毕设JAVA——学习考试管理系统(基于SpringBoot+Vue前后端分离的项目)

学习考试管理系统 概要系统架构技术运行环境系统功能项目演示图片 概要 网络上许多计算机毕设项目开发前端界面设计复杂、不美观&#xff0c;而且功能结构十分单一&#xff0c;存在很多雷同的项目&#xff1a;页面基本上就是套用固定模板&#xff0c;换个颜色、改个文字&#…

服装连锁实体店bC一体化运营方案

一、引言 随着互联网的快速发展和消费者购物习惯的变化&#xff0c;传统服装连锁实体店在面对新的市场环境下亟需转型升级。BC&#xff08;Business to Consumer&#xff09;一体化运营方案的实施将成为提升服装连锁实体店竞争力和顾客体验的关键举掖。商淘云详细介绍服装连锁…

迅狐多商户直播商城系统源码:电商领域的创新融合

随着直播技术的兴起和电子商务的蓬勃发展&#xff0c;迅狐多商户直播商城系统源码应运而生&#xff0c;为商家和消费者提供了一个全新的互动购物平台。 多商户直播商城系统源码概述 迅狐多商户直播商城系统源码是一个高度集成的解决方案&#xff0c;它结合了直播的即时性和电…

Java | Leetcode Java题解之第165题比较版本号

题目&#xff1a; 题解&#xff1a; class Solution {public int compareVersion(String version1, String version2) {int n version1.length(), m version2.length();int i 0, j 0;while (i < n || j < m) {int x 0;for (; i < n && version1.charAt(…

RN组件库 - Button 组件

从零构建 React Native 组件库&#xff0c;作为一个前端er~谁不想拥有一个自己的组件库呢 1、定义 Button 基本类型 type.ts import type {StyleProp, TextStyle, ViewProps} from react-native; import type {TouchableOpacityProps} from ../TouchableOpacity/type; import…

Python web 开发 flask 实践

1、前言 前文已经介绍了很多关于 python 的算法和脚本的写法&#xff0c;在本文将开启python的 web 的开发&#xff0c;和java 类似的&#xff0c;对于 web 开发也需要引入框架&#xff0c;对于 python 的 web 开发来说常见的有 flask 和 django 两种&#xff0c;在本文中将要…

通过阿里云OOS定时升级Redis实例临时带宽

功能背景 在数据驱动的现代业务环境中&#xff0c;Redis以其卓越的性能和灵活性&#xff0c;已成为众多企业关键基础设施的重要组成部分。Redis广泛应用于处理缓存、会话管理、消息队列等多种数据密集型和响应敏感型的场景&#xff0c;对业务连续性和用户体验贡献极大。然而&a…

transdreamer 论文阅读笔记

这篇文章是对dreamer系列的改进&#xff0c;是一篇world model 的论文改进点在于&#xff0c;dreamer用的是循环神经网络&#xff0c;本文想把它改成transformer&#xff0c;并且希望能利用transformer实现并行训练。改成transformer的话有个地方要改掉&#xff0c;dreamer用ht…

OpenCV Mat实现图像四则运算及常用四则运算的API函数

装载有图像数据的OpenCV Mat对象&#xff0c;可以说是一个图像矩阵&#xff0c;可以进行加、减、乘、除运算。特别是加运算特别有用。 一 与常数的四则运算 与常数的加运算 示例&#xff1a; #include <iostream> #include <opencv2/opencv.hpp>using namespace …

JVM 垃圾回收分配及算法

一、判断对象是否可以回收 垃圾收集器在做垃圾回收的时候&#xff0c;首先需要判定的就是哪些内存是需要被回收 的&#xff0c;哪些对象是「存活」的&#xff0c;是不可以被回收的&#xff1b;哪些对象已经「死掉」了&#xff0c;需 要被回收。 一般有两种方法来判断&#xff…

mysql数据库切换成kingbase(人大金仓)数据库时遇到的字段不存在问题

一、问题描述 mysql数据库切换成国产数据库人大金仓(kingbase)数据库的遇到的字段不存在的问题,根本原因其实是没有找到相对应的表,报错示例如下图所示: 二、问题解决 1、如果所有的表都发生上述的错误,kingbase的表在xml里面写sql的时候需要带上空间名的前缀,比如pu…

《Linux运维总结:prometheus+altermanager+webhook-dingtalk配置文件详解》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、prometheus配置文件 Prometheus的配置文件是prometheus.yml&#xff0c;在启动时指定相关的…

09-axios在Vue中的导入与配置

09-axios 前言首先简单了解什么是Axios&#xff1f;以上完成后就可以使用了 前言 我们接着上一篇文章 08-路由地址的数据获取 来讲。 下一篇文章 10-vuex在Vue中的导入与配置 首先简单了解什么是Axios&#xff1f; Axios是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端…

D触发器(D Flip-Flop)与D锁存器(D Latch)

1 基础概念 我们先来简单回顾一下D触发器&#xff08;D flip-flop&#xff09;和D锁存器&#xff08;D latch&#xff09;的概念&#xff0c;以及它们在数字电路中的作用。 1.1 D触发器&#xff08;D Flip-Flop&#xff09; D触发器是一种数字存储器件&#xff0c;它在时钟信号…

VBA学习(16):工作表事件示例:输入数据后锁定单元格

在工作表单元格中输入数据后&#xff0c;该单元格就被锁定&#xff0c;不能再编辑。 打开VBE&#xff0c;在工程资源管理器中双击该工作表名称打开其代码模块&#xff0c;在其中输入下面的代码&#xff1a; 假设整个工作表的LockedFalse Private Sub Worksheet_Change(ByVal …

超大cvs文件导入MySQL

1 XXX.cvs 太大 使用cvs拆分HugeCSVSplitter_jb51工具进行拆分&#xff0c;Line Count 设置为1,000,000 注意&#xff1a;1 拆分后除第一个子cvs文件含有标题外&#xff0c;其他的子文档都不含有标题行&#xff1b; 2 后一个文档的第一行为前一个文档的…

【尚庭公寓SpringBoot + Vue 项目实战】用户管理(十五)

【尚庭公寓SpringBoot Vue 项目实战】用户管理&#xff08;十五&#xff09; 文章目录 【尚庭公寓SpringBoot Vue 项目实战】用户管理&#xff08;十五&#xff09;1、业务介绍2、接口实现2.1、根据条件分页查询用户列表2.2、根据ID更新用户状态 1、业务介绍 用户管理共包含两…

数据结构与算法-B(B-)树的简单实现

B(B-)树定义 B树&#xff08;或B-tree&#xff09;是一个在计算机科学中广泛使用的数据结构&#xff0c;它是一种自平衡的树&#xff0c;能够保持数据有序。 以下是B树的特性 每个节点最多右m个孩子&#xff0c;二叉树是B-树的特例&#xff0c;其有2个孩子。除了叶节点和根节点…

【Gradio】从 BigQuery 数据创建实时仪表板

Google BigQuery 是一个基于云的服务&#xff0c;用于处理非常大的数据集。它是一个无服务器且高度可扩展的数据仓库解决方案&#xff0c;使用户能够使用类 SQL 查询分析数据。 在本教程中&#xff0c;我们将向您展示如何在 Python 中查询 BigQuery 数据集&#xff0c;并使用 g…