unity 简易异步socket

1.unity 同步socket 改异步

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;
using System.Threading;
using System;

public class Echo : MonoBehaviour
{
    //定义套接字
    Socket socket;
    //UGUI
    public InputField InputField;
    public Text text;

    //接收缓冲区
    byte[] readBuff = new byte[1024];
    string recvStr = "";

    //点击连接按钮
    public void Connection()
    {
        Debug.Log("点击了按钮");
        //Socket
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 
        //Connect 这个是同步方法
        //socket.Connect("127.0.0.1", 8888);
        socket.BeginConnect("127.0.0.1", 8888, ConnectionCallback, socket);
    }

    //Connect 回调
    public void ConnectionCallback(IAsyncResult ar) {
       try{
          Socket socket = (Socket) ar.AsyncState;
          socket.EndConnect(ar);
          Debug.Log("Socket Connect Succ");

          socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallback, socket);
       } catch (SocketException ex) {
         Debug.Log("Socket Connect fail " + ex.ToString());
       }
    }

    //Receive回调
    public void ReceiveCallback(IAsyncResult ar) {
        try {
            Socket socket = (Socket) ar.AsyncState;
            int count = socket.EndReceive(ar);
            recvStr = System.Text.Encoding.Default.GetString(readBuff, 0, count);

            socket.BeginReceive(readBuff, 0, 1024, 0, ReceiveCallback, socket);
        } catch(SocketException ex) {
            Debug.Log("Socket Receive fail " + ex.ToString());
        }
    }

    //点击发送按钮
    public void Send()
    {
        //Send
        string sendStr = InputField.text;
        byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
        
        //测试卡住的问题 同步发送
        // for(int i=0; i<10000; i++) {
        //      socket.Send(sendBytes);
        // }

        //改用异步发送
        socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);

        //Recv 这是原来同步的处理 现在改成异步处理
        // byte[] readBuff = new byte[1024];
        // int count = socket.Receive(readBuff);
        // string recvStr = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
        // text.text = recvStr;
        //Close
        //socket.Close();
        // 
    }

    //Send回调
    public void SendCallback(IAsyncResult ar) {
        try {
            Debug.Log("异步发送测试");
            Socket socket = (Socket) ar.AsyncState;
            int count = socket.EndSend(ar);
            Debug.Log("Socket Send succ" + count);
             
            // 一般情况下EndSend的返回值count与要发送数据的长度相同,代表数据全部发出
            // 但也不绝对,如果EndSend的返回值指示未全部发完,需要再次调用BeginSend方法,以便发送未发送的数据 
        } catch (SocketException ex) {
            Debug.Log("Socket Send fail " + ex.ToString());
        }
    }

    public void Update() {
        text.text = recvStr;
    }
}

每个同步API(Connect)对应着两个异步API,分别是在原名称前面加上Begin和End(如BeginConnect 和 EndConnect)。客户端发起连接时,如果网络不好或服务的没有回应,客户端会被卡住一段时间。而这卡住的十几秒,用户不能做任何操作,游戏体验很差。

由BeginConnect最后一个参数传入的socket,可由ar.AsyncState获取到

下面 对值 得 注 意的 地 方进 行 进 一 步 解 释。
(1)BeginReceive 的参数
上述程序 中,BeginReceive 的 参数为(readBuff, 0, 1024, 0, ReceiveCallback, socket )。 第一个参数readBuff 表示接收缓冲区; 第二个参数。表示从readBuff第。位开始接收数据, 这 个 参 数 和 T C P 粘 包 问 题 有 关 , 后 续 章 节 再 详 细 介 绍; 第 三 个 参 数 1 0 2 4 代 表 每 次 最 多 接 收 1024 个字节的数据,假如服务端回应一串长长的数据,那一次也只会收到1024 个字节。
(2 ) BeginReceive 的调用位置
程序在两个地方调用了BeginReceive: 一 个是ConnectCallback,在连接成功后,就开 始 接 收 数 据 , 接 收 到数 据 后 , 回 调 两 数 R e c e i v e C a l l b a c k 被 调 用 。 另 一个 是 B e g i n R e c e i v e 内 部,接收完一串数据后, 等待下一串数据的到来,如图2- 4所示。
在这里插入图片描述
( 3 ) Update 和 recvStr
在Unity中,只有主线程可以操作UI 组件。由于异步回调是在其他线程执行的,如 果在BeginReceive给text.text赋值,Unity会 弹出“get isActiveAndEnabled can only be called from the main thread”的异常信息,所以程序只给变量recvStr赋值 , 在主线程执行的Update 中再给text.text 赋值(如图2-5所示)。
在这里插入图片描述

  1. 上面new Socket 参数说明
    socketType的含义
SocketType的值含义
Dgram支 持 数 据 报 , 即 最 大 长 度 固 定 ( 通 常 很 小 ) 的 无 连 接 、 不 可 靠 消 息 。, 消 息 可 能 会 天 失 或重复并可能在到达时不按顺序排列。Dgram 类型的Socket 在发送和接收 数据之前不 需要任何连接,并且可以与多个对方主机进行通信。Dgram使用数据报协议(UDP) 和 InterNetwork AddressFamily
Raw支持对基础传输协议的访问。通过使用Socket TypeRaw,可以使用Inter et控制消息协议 ( ICMP) 和Internet 组管理协议(Igmp)这样的协议来进行通信。在发送时,您的应 用程序必 须 提 供 完整 的 1 P 标 头。 所接 收 的 数 据 报 在 返 回 时会 保 持 其 1 P 标 头和 选 项 不 变
RDM支持无连接、面向消息、以可靠方式发送的消息,并保留数据中的消息边界。RDM( 以 可靠方式发送的消息)消息会依次到达,不会重复。此外,如果消息丢失, 将会通知发送 方。 如 果 使 用 R D M 初 始 化 S o c k e t , 则 在 发 送 和 接 收 数 据 之 前 无 须 建 立 远 程 主 机 连 接 。 利 用 RDM,可以 与多个对方主机 进行通信
Seqpacket在网络上提供排序字节流的面 向连接且可靠的双向传输。Seqpacket 不重复数据, 它在数 据流中保留边界。Seqpacket 类型的Socket与单个对方主机通信,并且在通信开始之前需要 建立 远程主机连接
Stream支持可靠、双向、基于连接的字节流,而不重复数据,也不保留边界。此类型的Socket 与 单个对方主机通信,并且在通信开始 之前需要建 立远程主机连接。Strca m 使用传输控制协议 ( TCP)和InterNetworkAddressFamily
Unknown指定未知的Socket 类型

常用的协议

常用的协议含义
GGP网 关 到 网 关 协 议
ICMP网际 消息控制协议
ICMPv6用于IPv6 的Internet 控制消息协议
IDPI n t e r n e t 数 据报 协 议
IGMP网 际组 管理 协议
IP网际 协议
Internet数 据 包 交 换 协议
PARC通用 数 据 包 协 议
RAW原始IP 数据包 协议
Т С Р传输控制协议
U D P用 户 数 据 包 协 议
U n k n o w n未知 协 议
Unspecified未指定 的协议
  1. 异步Connect函数说明
    首先同步方法这样 socket.Connect(“127.0.0.1”, 8888);
public IAsyncResult BeginConnect ( string host,
	int port,
	AsyncCallback requestCallback, 
	object state
)
参数说明
host远 程 主机 的名 称 ( I P ) , 如 “ 1 2 7 . 0 . 0 . 1 ”
port远程主机的端又号,如“8888”
requestCallback一个AsyncCallback 委托,即回调两数,回调两数的参数必须是这样的形式:void requestCallbackConnectCallback(IAsyncResultar)
state一个用户 定义对象,可包含连接操作的相关信 息。此对象会被传递给回调函数
public void EndConnect (
    IAsyncResult asyncResult
)
  1. 异步Receive函数方法
    Receive 是个阻塞方法,会让客户端一直卡着,直至收到服务端的数据为止。如果服务 端不回应(试试注释掉Echo服务端的Send 方法!),客户端就算等到海枯石烂,也只能继 续等着。异步Receive 方法BeginReceive 和EndReceive 正是解决这个问题的关键。 与BeginConnect 相似,BeginReceive 用于实现异步数据的接收,它的原型如下所示。
public IAsyncResult BeginReceive ( byte[] buffer,
	int offset, 
	int size,
	SocketFlags socketFlags, 
	AsyncCallback callback, 
	object state
)

表 2- 2 对Be gi n Re c e i ve 的 参数进行 了说明。

参 数说明
bufferByte 类型的数组,它存储接收到的数据
offsetbuffer中存储数据的位置,该位置从0开始计数
size最 多接收的 字节数
SocketFlagsSocketFlags 值的按位组合,这里设置为0
callback回调函数,一 个AsyncCallback 委托
state一 个用户定义对象,其中包含接收操作的相关信息。 当操作完成时,此对象会被传递 state给EndReceive 委托

虽然参数比较多,但我们先重点关注buffer、callback 和state 三个即可。对应的End- R e c e i v e 的原 型 如 下, 它 的返 回 值 代 表 了 接 收 到 的 字 节 数 。

public int EndReceive ( 
   IAsyncResult    asyncResult
)
  1. 异步send
    尽管不容易察觉,Send也是个阻塞方法, 可能导致客户端在发送数据的一瞬间卡住。 T C P 是 可 靠 连 接 , 当 接 收 方 没 有 收 到 数 据 时 , 发 送 方 会 重 新 发 送 数 据 , 直 至 确 认 接收 方 收 到数据为止。

在操作系统内部,每个Socket 都会有一个发送缓冲区,用于保存那些接收方还没有确 认的数据。图2-6指示了一个Socket涉及的属性,它分为“用户层面” 和“操作系统层面” 两大部分。Socket 使用的协议、IP、端又属于用户层面的属性,可以直接修改;操作系统 层面拥有“发送” 和“接收” 两个缓冲区,当调用Send 方法时,程序将要发送的字节流写 人到发送缓冲区中,再由操作系统完成数据的发送和确认。由于这些步骤是操作系统 自动 处理的,不对用户开放,因此称为“操作系统层面” 上的属性。

发 送 缓 冲 区 的 长 度 是 有 限 的 ( 默 认 值 约 为 8 K B ), 如 果 缓 冲 区 满 , 那 么 S e n d 就 会 阻 塞 ,直到 缓冲区 的数据被确认 腾出 空间。
在这里插入图片描述

可以 做 一个 这 样 的 实 验 : 删 去 服 务 端 Receive 相 关 的 内 容 , 使 客 户 端 的 S o c k e t 缓 冲 区 不 能 释 放 , 然 后 发 送 很 多 数 据 ( 如 下 代 码 所 示 ), 这 时 就 能 够 把 客 户 端 卡 住 。

// 点击发送按钮

public void Send ( ) {
   // Send
   string sendStr = InputFeld.text;
   byte [ ] sendBytes = System. Text. Encoding. Default.GetBytes (sendStr); 
   for (int i=0;i<10000;1++) {
        socket.Send( sendBytes ) ;
    }
}

值得注意的是,send 过程只是把数据写人到发送缓冲区,然后由操作系统负责重传、确 认等步骤。Send 方法返 回只代表成功将数据放到发送缓存区中,对方可能还没有收到数据。 异步Send 不会卡住程序, 当数据成功写人输人缓冲区(或发生错误)时会调用回调两 数。异步Send 方法BeginSend的原型如下。

public IAsyncResult BeginSend(
	byte[] buffer,
	int offset,
	int size,
	SocketFlags socketFlags, 
	AsyncCallback callback, 
	object state
)

表2- 3对BeginSend的参数进行了说明。

参数说明
bufferByte类型的数组, 包含要发送的数据
offset从buffer中的offset位置开始发送
socketFlagsSocketFlags值的按位组合,这里设置为0
callback回调函数,一个AsyncCallback委托
state一个用户定义对象,其中包含发送操作的相关信息. 当操作完成时,此对象会被传递给EndSend委托

EndSend 函数原 型 如 下。 它 的 返 回值 代 表 发 送 的 字 节 数 , 如 果 发 送失 败 会 抛 出 异 常

 public int EndSend ( 
      IAsyncResult   asyncResult
)

============================================================
c#建议服务端

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;

namespace EchoServer
{
	class ClientState
	{
		public Socket socket;
		public byte[] readBuff = new byte[1024];
	}

	class MainClass
	{
		// 监听Socket
		static Socket listenfd;

        // 客户端Socket及状态信息
		static Dictionary<Socket, ClientState> clients = 
		    new Dictionary<Socket, ClientState>();


		public static void Main (string[] args)
		{
			Console.WriteLine ("Hello World!");
			//Socket
			listenfd = new Socket(AddressFamily.InterNetwork,
				SocketType.Stream, ProtocolType.Tcp);

			// Bind
			IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
			IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
			listenfd.Bind(ipEp);

			//Listen
			listenfd.Listen(0);
			Console.WriteLine("[服务器]启动成功");

			// Accept
			listenfd.BeginAccept(AcceptCallBack, listenfd);
            // 等待
			Console.ReadLine();

			// accept 这部分改异步
			// while (true) {
			// 	//Accept
			// 	Socket connfd = listenfd.Accept ();
			// 	Console.WriteLine ("[服务器]Accept");
			// 	//Receive
			// 	byte[] readBuff = new byte[1024];
			// 	int count = connfd.Receive (readBuff);
			// 	string readStr = System.Text.Encoding.UTF8.GetString (readBuff, 0, count);
			// 	Console.WriteLine ("[服务器接收]" + readStr);
			// 	//Send
			// 	string sendStr = System.DateTime.Now.ToString();
			// 	byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
			// 	connfd.Send(sendBytes);
			// }
		}

		//Accept 回调
		// 1. 给新的连接分配clientState,并把它添加到clients列表中 2. 异步接收客户端数据 3.再次调用BeginAccept实现循环
		public static void AcceptCallBack(IAsyncResult ar) {
			try {
				Console.WriteLine("[服务器]Accept");
				Socket listenfd = (Socket)ar.AsyncState;
				Socket clientfd = listenfd.EndAccept(ar);
				//clients列表
				ClientState state = new ClientState();
				state.socket = clientfd;
				clients.Add(clientfd, state);
				// 接收数据BeginReceive
				clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);

				//继续Accept
				listenfd.BeginAccept(AcceptCallBack, listenfd);

			} catch (SocketException ex) {
				Console.WriteLine("Socket Accept fail" + ex.ToString());
			}
		}

        //Receive 回调
		public static void ReceiveCallback(IAsyncResult ar) {
			try {
               ClientState state = (ClientState) ar.AsyncState;
			   Socket clientfd = state.socket;
			   int count = clientfd.EndReceive(ar);
			   // 客户端关闭
			   if (count == 0) {
				 clientfd.Close();
				 clients.Remove(clientfd);
				 Console.WriteLine("Socket Close");
				 return;
			   }

			   string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 0, count);
			   byte[] sendBytes = System.Text.Encoding.Default.GetBytes("echo" + recvStr);

			   clientfd.Send(sendBytes);//减少代码量, 不用异步
			   clientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);

			} catch (SocketException ex) {
                Console.WriteLine("Socket Receive fail" + ex.ToString());
			}
		}
	}
}

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

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

相关文章

Pikachu靶场--暴力破解

实验前的准备 问题解决 PHPStudy&#xff08;小皮&#xff09;V8.1安装后启动Apache报错AH00526: Syntax error 【数据库连接问题】【靶场访问错误】 抓不到本地靶场包的原因及解决方法_pakachu抓不到包 设置代理 BP添加和选择代理 火狐浏览器-->设置-->拓展-->搜索…

vue input 限制输入,小数点后保留两位 以及 图片垂直居中显示 和 分享 git 小技巧

&#xff08;1&#xff09;input 限制输入&#xff0c;小数点后保留两位 <template><div><el-input v-model"number" input"checkNumber" blur"completeNumber" placeholder"请输入"></el-input></div>…

【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版10(附带项目源码)

最终效果 系列导航 文章目录 最终效果系列导航前言使用DoTween优化阳光生成和拾取效果拾取阳光优化生成阳光优化 场景加载进度条新增加载场景Loading&#xff0c;绘制开始界面绘制菜单界面滑动滚轮一直滚动 场景加载源码结束语 前言 本节主要实现使用DoTween优化阳光生成和拾取…

域策略笔记

域策略 导航 文章目录 域策略导航一、设置客户端壁纸二、重定向用户配置文件路径三、部署网络打印机四、部署共享文件夹为网络驱动器五、通过域策略推送软件安装六、通过域策略限制软件的使用通过路径进行限制通过进程限制 七、通过域策略将文件添加白名单八、通过域策略添加可…

Python机器学习决策树可视化工具库之pybaobabdt使用详解

概要 决策树是一种常用的机器学习算法,广泛应用于分类和回归任务。为了更好地理解和解释决策树模型的决策过程,pybaobabdt 库提供了一种可视化工具,帮助用户以图形化方式展示决策树的结构和决策路径。本文将详细介绍 pybaobabdt 库,包括其安装方法、主要特性、基本和高级功…

5位AI界“考生”参加高考作文写作,最高分竟然是...

随着一年一度高考的帷幕缓缓降下&#xff0c;如同往昔&#xff0c;各省高考作文命题迅速成为了社会各界热议的焦点。高考作文命题历来紧扣时代脉搏&#xff0c;而今年新课标I卷则直接聚焦于当前最为炙手可热的领域——“人工智能”。 阅读下面的材料&#xff0c;根据要求写作。…

python __init__.py 文件案例练习

通过一些案例练习来更好地理解 __init__.py 的用法。我们将创建一个简单的 Python 包,并在 __init__.py 中实现不同的功能。 案例一:基本包结构 创建包目录结构: mypackage/__init__.pymodule1.pymodule2.py实现 module1.py 和 module2.py: # mypackage/module1.py def fu…

vue-editor设置字体font-family

背景&#xff1a;Vue项目中需要用到富文本编辑器&#xff0c;所以选择了vue-editor这个富文本编辑器&#xff0c;发现字体font-family只有三种Sans Serif、Serif、MonoSpace可以选择&#xff0c;满足不了产品的需求&#xff0c;所以用想要定义成常用字体&#xff0c;主要是需要…

SwiftUI 利用 Swizz 黑魔法为系统创建的默认对象插入新协议方法(六)

功能需求 在 SwiftUI 的开发中,我们往往需要借助底层 UIKit 的“上帝之手”来进一步实现额外的定制功能。比如,在可拖放(Dragable)SwiftUI 的实现中,会缺失拖放取消的回调方法让我们这些秃头码农们“欲哭无泪” 如上图所示,我们在拖放取消时将界面中的一切改变都恢复如初…

【unity笔记】二、海洋系统Crest Ocean System基础

1. 创建海平面 首先确定项目中导入了HDRP插件。这里使用Crest Ocean System HDRP插件。 在场景下创建空对象&#xff0c;这里命名为Ocean。将 OceanRenderer 组件分配给Ocean。该组件将生成海洋几何图形并执行所有必需的初始化。其中Global Wind Speed 属性可以调节风浪大小。…

【新品上架】动捕级视觉定位精度!!首款搭载 VIOBOT 模块的无人机发布!

P450_Viobot无人机是P450系列科研无人机的最新产品&#xff0c;硬件上采用VIOBOT定位模块&#xff0c;室内定位精度极高&#xff0c;静态定位精度可达到与动捕定位相当的厘米级定位&#xff0c;飞行悬停定位误差在3.5cm内。也可室外GPS飞行&#xff08;可选装RTK&#xff09;&a…

latex导入图片报错

忘记导包了 \usepackage{graphicx}其中Photo是图片名字 \begin{figure}\centering\includegraphics[width0.5\linewidth]{Photo.png}\caption{Enter Caption}\label{fig:enter-label} \end{figure}

基于STM32移植U8g2图形库——OLED显示(HAL库)

文章目录 一、U8g2简介1、特点2、U8g2的使用步骤 二、I2C相关介绍1、I2C的基本原理2、I2C的时序协议 三、OLED屏的工作原理四、汉字点阵显示原理五、建立STM32CubeMX工程六、U8g2移植1、U8g2源码2、移植过程 七、代码编写1、参考博主实现的U82G的demo例程&#xff08;1&#xf…

Matlab进阶绘图第59期—棒棒糖图

​棒棒糖图本质上是柱状图的一种变体。 棒棒糖图通过在每根柱子顶端添加圆点&#xff0c;以表示数据之间的相对位置。 此外&#xff0c;一般还会对每根棒棒糖按数值大小进行排序&#xff0c;从而更加方便阅读。 本文利用自制的Lollipop工具进行棒棒糖图的绘制&#xff0c;先…

python数据分析--- ch1-2 python初识入门

python数据分析--- ch1-2 python初识入门 1. 安装并启动jupyter2. 打印变量print()练一练 3. 变量与赋值 input()3.1 示例--饮料交换3.2 饮料交换完整code3.3 jupyter中写入code到py文件中3.4 在终端运行.py文件 &#xff1a; python 文件名3.5练一练1&#xff1a;简易版2&…

cdh zookeeper报错 Canary 测试建立与 ZooKeeper 服务的连接或者客户端会话失败。

我一直纳闷这个是什么问题&#xff0c;搜索了半天没有结果&#xff0c;因为别人没有遇到过。后面我重新搭建了另一套cdh&#xff0c;然后看了一下默认的配置&#xff0c;然后更新上去才发现的。 这里面的clientPortAddress不要手动设置端口号。 别勾选通信验证 不要开启TLS/SS…

嵌入式学习(二)——c51单片机(1)

使用keil软件 同时安装CH340驱动 将变成好的文件存成 .hex 交替闪烁代码 #include "reg51.h"void delay(unsigned int n) { while(n) { --n; } }int main(void) { while(1) { P20x00; delay(20000); P20xff; delay(20000); } return 0; } 让指定的灯亮 #includ…

在调用接口上map与forEach的区别

在场景&#xff1a;一个表格数据需要上传&#xff0c;每行表格需要上传图片->这就需要在提交时对数据也就是数组进行处理&#xff08;先将每个元素图片上传拿到图片id 这种情况我刚开始就用的map处理&#xff0c;然后问题来了&#xff0c;提交的接口调用了&#xff0c;但是…

【Kadane】Leetcode 918. 环形子数组的最大和【中等】

环形子数组的最大和 给定一个长度为 n 的环形整数数组 nums &#xff0c;返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上&#xff0c; nums[i] 的下一个元素是 nums[(i 1) % n] &#xff0c;nums[i] 的前一个元素是 nums…

云效codeup

云效codeup 什么是云效codeup云效codeup操作代码库代码托管代码检测代码提交代码评审代码迁移 使用感受及建议 什么是云效codeup 云效代码管理&#xff08;Codeup&#xff09;是阿里云云效一站式 BizDevOps 平台提供的自研代码管理服务&#xff0c;为企业提供代码托管、代码评…