Unity使用webSocket与服务器通信(三)——C#服务端(Fleck)与Unity客户端( NativeWebSocket)传输多种数据数据

news2024/11/26 19:40:53

一、通信时会传输哪些内容

  • 1、字符串数据
    简单的字符串:比如登录请求信息,登录结果返回的信息。
    用json系列化的字符串:比如上传一个表到服务器,让它写入到数据库中。
    读取文件的时候,读取的是string内容。

  • 2、二进制数据
    比如传输的是文件:例如myword.doc,myexcel.xls或者是assetboundle文件。
    比如上传实验报告,生成实验报告并下载等。
    读取文件的时候,直接读取字节码。

二、Unity(NativeWebSocket)和服务端(Fleck)怎么约定要传输的数据格式

在这里插入图片描述

1、Unity(NativeWebSocket)发送和接收信息的API有哪些?

  • 发送字符串
    SendText(string message)
  • 发送二进制
    Send(byte[] bytes)
  • 接收字符串【无】
    OnMessage(string : msg)
  • 接收二进制
    OnMessage(bytes : byte[])

2、服务器发送string和byte[]给Unity时,Unity如何处理?

  • Fleck发送string,使用Send(message:string)
  • Fleck发送二进制,使用Send(bytes:byte[])
  • Unity客户端统一使用OnMessage(bytes : byte[])进行数据的接收
    Unity需要识别这个包是什么内容:字符串指令,系列化的json字符串,不同种类的二进制文件…等等!

3、传输的数据格式

在这里插入图片描述

4、服务器和客户端信息来往说明

在这里插入图片描述

5、接收和发送的关键部分

(1)、客服端发送信息

  • 发送json文件到服务器
var jsonString = JsonUtility.ToJson(myScores); //myScores 预先定义的得分信息
websocket.SendText($"JSONSCOR{jsonString}");
  • 发送字符串指令到服务器
/// <summary>
/// 命令字符串
/// </summary>
/// <param name="text">命令字符串</param>
/// <returns></returns>
public async UniTask SendCommand(string cmdText)
{
    //传输时自动在前面添加[COMD]
    var msg = $"COMD{cmdText}";
    await websocket.SendText(msg);
}
  • 发送二进制文件到服务器
/// <summary>
/// 发送文件
/// </summary>
/// <param name="file">file.extend</param>
/// <param name="data">byte[]数据</param>
/// <returns>成功与否</returns>
public async UniTask<bool> SendBinary(string fileName,byte[] data)
{
    Debug.Log($"即将发送数据:{fileName} {data.Length}");
    var rtn = false;
    if (fileName.Length > 30)
    {
        Debug.Log("文件名不能超过30个字符");
        rtn = false;
    }
    else
    {
        var head = Encoding.UTF8.GetBytes("BINA");                         
        var fileNameBytes = Encoding.UTF8.GetBytes(fileName.PadRight(30)); 
        var allData = new List<byte>();                                         
        allData.AddRange(head);             //头
        allData.AddRange(fileNameBytes);    //文件名称
        allData.AddRange(data);             //文件数据
        await websocket.Send(allData.ToArray());
        rtn = true;
    }
    return true;
}

(2)、客户端向服务器发送下载请求,并等待数据到达

  • 发送【COMDdown#shiYanBaoGao.docx】
  • 等待二进制数据下载
  • 保存文件到本地
//下载文件
downLoadBtn.onClick.AddListener(async () =>
{
    //【1】文件名
    var file = "shiYanBaoGao.docx";

    Debug.Log("从服务器下载文件");
    //COMD + down + # + shiYanBaoGao.docx

    //【2】请求下载实验报告
    var cmdText = $"COMDdown#{file}";    //[COMD][down][#][shiYanBaoGao.docx]
    await Connection.instance.websocket.SendText(cmdText);
    
    //【3】等待接收实验报告
    var data = await Connection.instance.ReceiveBinaryAsync(file);

    //【4】保存文件
    Debug.Log($"收到文件{file},大小为{data.Length}");
    FileSave.instance.SaveFileBytes(file,data);
});

(3)、客户端从服务器下载时的异步等待实现

客户端申请下载某个文件的时候逻辑:

  • 客户端创建下载任务
  • 客户端收到下载数据时判断是否是某个任务的下载需求,是则把收到的数据写入该任务缓冲里面
  • 下载完成后读取收到的数据,并从列表中删除该任务

任务列表的定义:

/// <summary>
/// 下载任务列表: 客户端申请下载某个文件的时候使用。
///  private List<DownFileTask> DownFileTasks = new List<DownFileTask>();
/// </summary>
[Serializable]
public class DownFileTask
{
    /// <summary>
    /// 任务ID
    /// </summary>
    public int taskID;

    /// <summary>
    /// 要下载的文件的名字:
    /// </summary>
    public string fileName;

    /// <summary>
    /// 下载状态:false-未下载,true-下载成功
    /// </summary>
    public bool state;

    /// <summary>
    /// 文件的数据:二进制信息
    /// </summary>
    public List<byte> data;
}

异步等待下载文件:

/// <summary>
/// 等待接收文件
/// </summary>
/// <param name="fileName">文件名</param>
/// <returns></returns>
public async UniTask<byte[]> ReceiveBinaryAsync(string fileName)
{
    //【1】创建下载任务
    var taskId = CreatTask(fileName, ref DownFileTasks);

    //【2】OnMessage(bytes[])+ ()=>{}中更新,收到文件,写入数据
    //在ParseBytes()里
    
    Debug.Log(DownFileTasks.Where(x => x.taskID == taskId).All(x => x.state));
    Debug.Log("开始等待下载......");

    //【3】等待下载
    await UniTask.WaitUntil
    (
        () => DownFileTasks.Where(x => x.taskID == taskId).All(x => x.state) == true
    );

    //【4】提取数据
    Debug.Log("提取数据......");
    var data = DownFileTasks.First(x => x.taskID == taskId).data;

    //【5】删除下载任务
    Debug.Log("删除下载任务......");
    DeleteTask(taskId, ref DownFileTasks);

    //【6】返回数据
    return data.ToArray();
}

6、客户端socket的主要事件绑定

  • 连接打开时…
  • 连接出错时…
  • 连接关闭时…
  • 收到二进制数据时…
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="ip">服务器ip</param>
/// <param name="port">服务器端口号</param>
/// <returns></returns>
async UniTask<bool> ConnectServer(string ip, int port)
{
    bool rtn = false;
    try
    {
        //websocket = new WebSocket("ws://192.168.0.137:8081");
        Debug.Log($"ws://{ip}:{port}");
        websocket = new WebSocket($"ws://{ip}:{port}");

        //连接打开时...
        websocket.OnOpen += () =>
        {
            hasConnected = true;

            userID = iptfdUsername.text;
            Debug.Log("Connection open!");
            textLog.text = $"Connection open! {Time.realtimeSinceStartup} {Environment.NewLine} {textLog.text}";
            websocket.SendText($"COMDname#{userID}"); //发送用户id
        };

        //连接出错时...
        websocket.OnError += (e) =>
        {
            Debug.Log("Error! " + e);
            textLog.text = $"Error:{e} {Time.realtimeSinceStartup} {Environment.NewLine} {textLog.text}";
        };

        //连接关闭时...
        websocket.OnClose += (e) =>
        {
            hasConnected = false;
            Debug.Log("连接被关闭了!");
            textLog.text = $"连接被关闭了! {Time.realtimeSinceStartup} {Environment.NewLine} {textLog.text}";
        };

        //收到二进制数据时...
        websocket.OnMessage += (bytes) =>
        {
            //解析数据
            ParseBytes(bytes);
        };

        //开始连接
        await websocket.Connect();
        rtn = true;
    }
    catch (Exception e)
    {
        Debug.Log($"连接服务器出错:{e.Message}");
        textLog.text = $"连接服务器出错:{e.Message}";
        rtn = false;
    }
    return rtn;
}

7、服务器端socket的主要事件绑定

  • 连上时…
  • 断开时…
  • 收到字符串数据时…
  • 收到二进制数据时…
//var server = new WebSocketServer("ws://192.168.0.137:8081");  
var server = new WebSocketServer($"ws://{ip}:{port}");
server.Start(socket =>
{
    //连上ss
    socket.OnOpen = () =>
    {
        this.Invoke(new Action(() =>
        {
            //textBoxLog.Text += $"有新用户连入:{socket.ConnectionInfo.ClientIpAddress} {Environment.NewLine} {textBoxLog.Text}";
            AddText($"有新用户连入:{socket.ConnectionInfo.ClientIpAddress}");
        }));
   
        //报错的写法
        //textBoxLog.Text = $"有新用户连入:{socket.ConnectionInfo.ClientIpAddress} \n {textBoxLog.Text}";
        //SetTextLog($"有新用户连入:{socket.ConnectionInfo.ClientIpAddress}");

        Debug.WriteLine($"有新用户连入:{socket.ConnectionInfo.ClientIpAddress}");

        //回复一个信息
        //SendCommand(socket);
    };

    //断开
    socket.OnClose = () =>
    {
        Debug.WriteLine($"用户断开连接:{socket.ConnectionInfo.ClientIpAddress}");
        this.Invoke(new Action(() =>
        {
            AddText($"用户断开连接:{socket.ConnectionInfo.ClientIpAddress}");
        }));
        UserSockets.First(x => x.socket.ConnectionInfo.Id == socket.ConnectionInfo.Id).connected = false;
    };

    //收到string信息
    socket.OnMessage = message =>
    {
        Debug.WriteLine($"收到一条消息,来自:{socket.ConnectionInfo.ClientIpAddress}");
        Debug.WriteLine(message);

        this.Invoke(new Action(() =>
        {
            AddText($"收到一条消息,来自:{socket.ConnectionInfo.ClientIpAddress}");
            AddText($"{message}");
        }));

        //解析客户端字符串命令
        ParseString(message,socket);
    };

    //收到二进制信息
    socket.OnBinary = bytes =>
    {
        var userName = UserSockets.First(x => x.socket.ConnectionInfo.Id == socket.ConnectionInfo.Id)
            .userID;
        Debug.WriteLine($"收到二进制数据,长度为{bytes.Length}Bytes,来自ip:{socket.ConnectionInfo.ClientIpAddress},userID ={userName}");
        
        this.Invoke(new Action(() =>
        {
            AddText($"收到二进制数据,长度为{bytes.Length}Bytes,来自ip:{socket.ConnectionInfo.ClientIpAddress},userID ={userName}");
        }));

        var head = Encoding.UTF8.GetString(bytes.Take(4).ToArray());

        switch (head)
        {
            case "BINA":
                //收到二进制文件
                ParseBinaryFile(bytes,socket);
                break;

            case "other":
                //
                break;

            default:
                //
                break;
        }
    };
});

三、todo

实时传输语音和视频

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

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

相关文章

如何使用Midjourney自己设计独特的Logo,常用的提示和使用效果展示(内附Midjourney提示词网站)

文章目录一、提示技巧二、图案标志(图形)1.最小线条标志(Minimal Line Marks)2.渐变标志(Gradient Marks)3.中式标志&#xff08;Chinese Style Marks&#xff09;三、抽象/几何标志四、徽章设计五、吉祥物Logo六、平面设计七、其它一些Logo八、好用的Midjourney提示词网站生成…

Java通过继承的方法来实现长方形的面积和体积

目录 前言 一、测试.java类 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 二、Changfangxing.java类 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 三、Jxing.java类 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 前言 1.若有选择…

【通用篇】chrome调试技巧

一、前言 Google Chrome 是一款由 Google 公司开发的免费网页浏览器&#xff0c;它的特点是速度快、安全、简单易用。它的市场份额长期位居全球第一&#xff0c;受到了广泛的用户和开发者的欢迎。 Chrome DevTools 是 Chrome 浏览器内置的一组开发者工具&#xff0c;旨在帮助…

深度学习目标检测项目实战(四)—基于Tensorflow object detection API的骨折目标检测及其界面运行

深度学习目标检测项目实战(四)—基于Tensorflow object detection API的骨折目标检测及其界面运行 使用tensorflow object detection进行训练检测 参考原始代码&#xff1a;https://github.com/tensorflow/models/tree/master/research 我用的是1.x的版本 所以环境必须有gpu版…

初级算法-二叉树

主要记录算法和数据结构学习笔记&#xff0c;新的一年更上一层楼&#xff01; 初级算法-二叉树一、递归遍历二、迭代遍历三、统一迭代法四、层序遍历五、翻转二叉树六、对称二叉树七、二叉树的最大深度八、二叉树的最小深度九、完全二叉树的节点个数十、平衡二叉树十一、二叉树…

浅说黄河三门

黄河是一首雄浑的诗。 黄河是一幅神奇的画。 她从雪域高原走来&#xff0c;一路接百川、纳细流&#xff0c;穿山越岭。 在行至内蒙古托尧托县的河口镇时&#xff0c;骤然调头南下&#xff0c;滚滚河水如一把利剑&#xff0c;将偌大的黄土高原一劈两半。 在秦晋两省的边界线上…

网络安全自学笔记+岗位介绍

我就在这个行业&#xff0c;目前湖南&#xff0c;薪资就没必要说了&#xff0c;高就对了。 这个行业优势就是工资高&#xff0c;缺点就需要一直学&#xff0c;卷得要死&#xff0c;不是跟别人卷&#xff0c;而是自己卷&#xff0c;一会后面细说 这个行业目前分为几个岗位&#…

Hadoop之HBase

文章目录一、HBase简介二、HBase结构1.1HBase逻辑结构1.2HBase物理结构1.3HBase基础架构三、HBase安装配置3.1单机模式3.2集群搭建四、HBase JAVA API一、HBase简介 《HBase官方文档》的原文地址是&#xff1a;http://hbase.apache.org/book.html W3Cschool.cn进行整理翻译 ht…

C++linux高并发服务器项目实践 day6

Clinux高并发服务器项目实践 day6exec函数族介绍execlexeclp其他进程控制进程退出孤儿进程僵尸进程进程回收wait()函数waitpid()函数exec函数族 介绍 exec函数族的作用是根据指定的文件名找到可执行文件&#xff0c;并用它来取代调用进程的内容&#xff0c;换句话说&#xff…

【路径规划】Dubins路径

简介 在无障碍物的情况下&#xff0c;路径规划中最简单的形式&#xff0c;就是将路径看作是由直线段和常曲率圆弧段组成&#xff0c;这就是Dubins路径。Dubins路径可以简单的理解为&#xff1a;在最大曲率限制下&#xff0c;平面内两个由方向的点间的最短可行路径是 CLC 路径或…

[STM32F103C8T6]基于LCD和DHT11、HC08的温湿度检测系统并上传服务器

项目实际图 本次项目需要整合LCD1602、DHT11、HC08、继电器 1.首先是LCD1602显示程序 封装管脚&#xff0c;这样的话写时序的时候不用随时都在哪儿HAL_GPIO_WritePin #define RS_GPIO_Port GPIOB #define RW_GPIO_Port GPIOB #define EN_GPIO_Port GPIOB #define RS_Pin GPI…

PP模块-生产主数据之一-物料主数据

物料主数据的配置主要在 MM 模块中进行管理&#xff0c;一般由MM顾问或MDM的顾问负责流程梳理、规则讨论、并主导完成数据收集工作。所以在SAP系统项目的实施过程中&#xff0c;根据系统对物料主数据数特有的配置对象要求&#xff0c;与业务负责人进行讨论并达成一致&#xff0…

FreeRTOS如何解决访问冲突/线程不安全(临界段、互斥锁、挂起调度、看门人任务)

在多任务&#xff08;多线程&#xff09;系统中&#xff0c;存在一个隐患&#xff0c;那就是多线程的访问&#xff08;在FreeRTOS中就是任务&#xff09;。当一个任务A开始访问一个资源&#xff08;外设、一块内存等&#xff09;&#xff0c;但是A还没有完成访问&#xff0c;B任…

精通 TensorFlow 2.x 计算机视觉:第二部分

原文&#xff1a;Mastering Computer Vision with TensorFlow 2.x 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的形象&#xff0c;…

【RabbitMQ学习日记】—— 再见RabbitMQ

一、发布确认高级篇 在生产环境中由于一些不明原因&#xff0c;导致 rabbitmq 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c;导致消息丢失&#xff0c;需要手动处理和恢复如何才能进行 RabbitMQ 的消息可靠投递呢&#xff1f; 特别是在这样比较极端的情…

MYSQL:数据类型与运算符、MySQL函数

一.部分需要学会的操作&#xff08;以举例形式列出&#xff09;&#xff1a; insert into tmp15 values(This is good,50); /*向tmp15插入note 为 “This is good”&#xff0c;price为50的元素*/ 注&#xff1a;需要严格对应字段和元素属性的位置 select * from tmp15 /*查…

【Diffusion Model】Learning notes

来自 扩散模型 Diffusion Model 1-1 概述 扩散模型是什么&#xff1f; 本质是生成模型&#xff0c;拟合目标分布&#xff0c;然后生成很多数据符合这个分布 训练测试阶段&#xff1f; 和 GAN 相比优势是什么&#xff1f; generator 和 discriminator 两者都得训练的比较均衡…

JDK8到JDK17有哪些吸引人的新特性?

作者&#xff1a;京东零售 刘一达 前言 2006年之后SUN公司决定将JDK进行开源&#xff0c;从此成立了OpenJDK组织进行JDK代码管理。任何人都可以获取该源码&#xff0c;并通过源码构建一个发行版发布到网络上。但是需要一个组织审核来确保构建的发行版是有效的, 这个组织就是J…

Excel VBA 之Interior 对象设置底色

Interior 对象 代表一个对象的内部 针对interior对象&#xff0c;我们用得最多的是它的颜色&#xff0c;下面就来讨论一下。 1.ColorIndex 索引颜色值 Sub 索引颜色值()For i 1 To 56Cells(i, 1).Interior.ColorIndex iCells(i, 2) iNext iFor i 1 To 56Cells(i, 3).Interi…

算法训练第六十天 | 84.柱状图中最大的矩形

单调栈part0384.柱状图中最大的矩形题目描述思路暴力解法双指针解法单调栈84.柱状图中最大的矩形 题目链接&#xff1a;84.柱状图中最大的矩形 参考&#xff1a;https://programmercarl.com/0084.%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E…