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;//UGUIpublic InputField InputField;public Text text;//接收缓冲区byte[] readBuff = new byte[1024];string recvStr = "";//点击连接按钮public void Connection(){Debug.Log("点击了按钮");//Socketsocket = 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(){//Sendstring 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 ( ) {// Sendstring 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{// 监听Socketstatic Socket listenfd;// 客户端Socket及状态信息static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();public static void Main (string[] args){Console.WriteLine ("Hello World!");//Socketlistenfd = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);// BindIPAddress ipAdr = IPAddress.Parse("127.0.0.1");IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);listenfd.Bind(ipEp);//Listenlistenfd.Listen(0);Console.WriteLine("[服务器]启动成功");// Acceptlistenfd.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);// 接收数据BeginReceiveclientfd.BeginReceive(state.readBuff, 0, 1024, 0, ReceiveCallback, state);//继续Acceptlistenfd.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());}}}
}

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

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

相关文章

【C#】使用JavaScriptSerializer序列化对象

在C#开发语言编程中&#xff0c;通常使用系统内置的JavaScriptSerializer类来序列化对象&#xff0c;以便将其转换为JSON格式的文本存储与后台服务通信, 在这里将为大家详细介绍一下这个过程。 文章目录 反序列化序列化忽略属性 假设处理的数据中有一个对象类, 如下 public cl…

Linux系统脚本开机自启动,开机自启动jar包vue前台等

脚本内容jiaobenname.sh #!/bin/bash # 设置环境变量 export JAVA_HOME/usr/local/java/jdk-17.0.10 export CLASSPATH.:$JAVA_HOME/lib/ export PATH.:$JAVA_HOME/bin:$PATHwhile true; doif ps aux | grep -v grep | grep "tomcat" > /dev/null; thenecho &quo…

ppt添加圆角矩形,并调整圆角弧度方法

一、背景 我们看的论文&#xff0c;许多好看的图都是用PPT做的&#xff0c;下面介绍用ppt添加圆角矩形&#xff0c;并调整圆角弧度方法。 二、ppt添加圆角矩形&#xff0c;并调整圆角弧度 添加矩形&#xff1a; 在顶部工具栏中&#xff0c;点击“插入”选项卡。 在“插图”…

索引-定义、创建(CREATE INDEX)、删除(DROP INDEX)

一、概述 1、索引是SQL语言定义的一种数据对象&#xff0c;是大多数DBMS为数据库中基本表创建的一种辅助存取结构&#xff0c;用于响应特定查询条件进行查询时的查询速度&#xff0c;DBMS根据查询条件从数据库文件中&#xff0c;选择出一条或者多条数据记录以供检索&#xff0…

springboot优雅shutdown时异步线程安全优化

前面针对graceful shutdown写了两篇文章 第一篇&#xff1a; https://blog.csdn.net/chenshm/article/details/139640775 只考虑了阻塞线程&#xff0c;没有考虑异步线程 第二篇&#xff1a; https://blog.csdn.net/chenshm/article/details/139702105 第二篇考虑了多线程的安全…

基于C#开发web网页管理系统模板流程-参数传递

点击返回目录-> 基于C#开发web网页管理系统模板流程-总集篇-CSDN博客 前言 当用户长时间未在管理系统界面进行操作&#xff0c;或者用户密码进行了更改&#xff0c;显然用户必须重新登录以验证身份&#xff0c;如何实现这个功能呢&#xff1f; HTTP Cookie&#xff08;也叫 …

【Linux】 进程信号的发生

送给大家一句话&#xff1a; 何必向不值得的人证明什么&#xff0c;生活得更好&#xff0c;乃是为你自己。 -- 亦舒 进程信号的发生 1 何为信号2 信号概念的基础储备3 信号产生kill系统调用alarm系统调用异常core term Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢…

【教程】设置GPU与CPU的核绑(亲和力Affinity)

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 简单来说&#xff0c;核绑&#xff0c;或者叫亲和力&#xff0c;就是将某个GPU与指定CPU核心进行绑定&#xff0c;从而尽可能提高效率。 推荐与进程优先…

1055 集体照(测试点3, 4, 5)

solution 从后排开始输出&#xff0c;可以先把所有的学生进行排序&#xff08;身高降序&#xff0c;名字升序&#xff09;&#xff0c;再按照每排的人数找到中间位置依次左右各一个进行排列测试点3&#xff0c; 4&#xff0c; 5&#xff1a;k是小于10的正整数&#xff0c;则每…

线程池ThreadPoolExecutor源码分析

一、线程池基本概念和线程池前置知识 1.1 Java中创建线程的方式有哪些 传统答案&#xff1a; 继承Thread类 通过继承Thread类并重写其run方法来创建线程。具体步骤包括定义Thread类的子类&#xff0c;在子类中重写run方法以实现线程的具体逻辑&#xff0c;然后创建子类的实例…

Unity的三种Update方法

1、FixedUpdate 物理作用——处理物理引擎相关的计算和刚体的移动 (1) 调用时机&#xff1a;在固定的时间间隔内&#xff0c;而不是每一帧被调用 (2) 作用&#xff1a;用于处理物理引擎的计算&#xff0c;例如刚体的移动和碰撞检测 (3) 特点&#xff1a;能更准确地处理物理…

植物大战僵尸杂交版全新版v2.1解决全屏问题

文章目录 &#x1f68b;一、植物大战僵尸杂交版❤️1. 游戏介绍&#x1f4a5;2. 如何下载《植物大战僵尸杂交版》 &#x1f680;二、解决最新2.1版的全屏问题&#x1f308;三、画质增强以及减少闪退 &#x1f68b;一、植物大战僵尸杂交版 《植物大战僵尸杂交版》是一款在原版《…

Es 索引查询排序分析

文章目录 概要一、Es数据存储1.1、_source1.2、stored fields 二、Doc values2.1、FieldCache2.2、DocValues 三、Fielddata四、Index sorting五、小结六、参考 概要 倒排索引 优势在于快速的查找到包含特定关键词的所有文档&#xff0c;但是排序&#xff0c;过滤、聚合等操作…

内存分配器性能优化

背景 在之前我们提到采用自定义的内存分配器来解决防止频繁 make 导致的 gc 问题。gc 问题本质上是 CPU 消耗&#xff0c;而内存分配器本身如果产生了大量的 CPU 消耗那就得不偿失。经过测试初代内存分配器实现过于简单&#xff0c;产生了很多 CPU 消耗&#xff0c;因此必须优…

跨语言翻译的突破:使用强化学习与人类反馈提升机器翻译质量

在人工智能领域&#xff0c;知识问答系统的性能优化一直是研究者们关注的焦点。现有的系统通常面临知识更新频繁、检索成本高、以及用户提问多样性等挑战。尽管采用了如RAG&#xff08;Retrieval-Augmented Generation&#xff09;和微调等技术&#xff0c;但它们各有利弊&…

C++ 45 之 赋值运算符的重载

#include <iostream> #include <string> #include <cstring> using namespace std;class Students05{ public:int m_age;char* m_name;Students05(){}Students05(const char* name,int age){// 申请堆空间保存m_name;this->m_name new char[strlen(name)…

案例 采用Springboot默认的缓存方案Simple在三层架构中完成一个手机验证码生成校验的程序

案例 Cacheable 是 Spring Framework 提供的一个注解&#xff0c;用于在方法执行前先检查缓存&#xff0c;如果缓存中已存在对应的值&#xff0c;则直接返回缓存中的值&#xff0c;而不执行该方法体。如果缓存中不存在对应的值&#xff0c;则执行方法体&#xff0c;并将方法的…

免费下载全球逐日气象站点数据

环境气象数据服务平台近期升级了NOAA GSOD 全球逐日气象站点数据&#xff08;NOAA Global Surface Summary of the Day &#xff09;的检索方式&#xff0c;升级后&#xff0c;用户无需注册&#xff0c;即可以在平台上下载全球逐日气象站点数据。 检索方式&#xff1a; 1. 访…

Python学习打卡:day07

day7 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day753、列表的常用操作课后练习题54、列表的循环遍历列表的遍历—— while 循环列表的遍历—— for 循环while 循环和 for 循环的对比练习 55、元组…

3 高频小信号放大器

分类与质量指标 分类 质量指标 增益 电压与功率的放大倍数。 通频带 放大效果比较好的频率范围。 选择性 放大目标信号以滤除其他信号的综合能力。 稳定性 噪声系数 晶体管高频等效电路 混合Π等效电路 共射三极管的等效电路。 Y参数等效电路 混合Π与Y参数等效电路的转换 单…