socket收发数据的处理

2024-06-16 01:36
文章标签 数据 处理 收发 socket

本文主要是介绍socket收发数据的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. TCP 协议是一种基于数据流的协议

  • Socket的Receive方法只是把接收缓冲区的数据提取出来,当系统的接收缓冲区为空,Receive方法会被阻塞,直到里面有数据。

  • Socket的Send方法只是把数据写入到发送缓冲区里,具体的发送过程由操作系统负责。当操作系统的发送缓冲区满了,Send方法会阻塞

2.解决粘包问题的方法

一般有三种方法 可以解决粘包和半包问题,分别是

  • 长度信息法: 指在每个数据包前面加上长度信
  • 固定长度法: 每次都以相同的长度发送数据
  • 结束符号法

一般 的游戏开发会在每个数据包前面加上长度字节, 以方便解析

2.1 发送数据代码

//点击发送按钮
public void Send(string sendStr)
{//组装协议byte [] bodyBytes = System.Text.Encoding.Default.GetBytes(sendStr);Int16 len = (Int16)bodyBytes.Length;byte[] lenBytes = BitConverter.GetBytes (len);byte[] sendBytes = lenBytes.Concat(bodyBytes).ToArray( ); // 为了精简代码:使用同步Send// 不考虑抛出异常socket.Send(sendBytes);
}

2.2 定义一个缓冲区(readBuff)和一个指示缓冲区有效数据长度变量(buffCount)。

// 接收缓冲区
byte[] readBuff= new byte[1024];
// 接收缓 冲区的数据 长度
int buffcount = 0;public void ReceiveCallback (IAsyncResult ar) { Socket socket = (Socket) ar.AsyncState;// 获取接收数据长度int count = socket.EndReceive(ar);buffCount += count;.....
}

3. 大小端

3.1 使用Reverse()兼容大小端编码
如果使用BitConverter.GetBytes将数字转换成二进制数据,转换出来的数据有可能基于大端模式, 也有可能基于小端模式。

因为我们规定必须使用小端编码,一个简单的办法 是,判断系统是否是 小端编码的系统,如果不是,就使用Reverse() 方法将大端编码转换为 小端编码。以Send 为例,代码如下:

//点击发送按钮 
public void Send () 
{string sendStr = InputFeld.text; // 组装协议byte[| bodyBytes = System.Text.Encoding.Default.GetBytes (sendStr); Int16 len = (Int16)bodyBytes.Length;byte[] lenBytes = BitConverter.GetBytes (len); //大 小 端编 码if(!BitConverter.IsLittleEndian) {Debug. Log (" [Send] Reverse lenBytes");lenBytes.Reverse();}    // 拼接字节byte[] sendBytes = lenBytes.Concat(bodyBytes) .ToArray ( );socket.Send(sendBytes);
}

3.2 手动还原数值
BitConverter. Toint16中根据系统大小端采用不同的编码方式,如果是小端编码,返回 的 是(pbyte) | ((pbyte+1)<< 8),如果是大端编码,返回的是(pbyte<< 8) | ((pbyte+1)。以小端为例,由于采用指针,(pbyte)指向缓冲区中指定位置的第1 个字节,(pbyte + 1)指向缓冲区中指定位置的第2个字节,((pbyte+1)<<8)表示左移8位,相当于乘以 256,返回的数字便是“第1个字节+第2字节256”,与4. 4.1节中介绍的步骤相同。

public void OnReceiveData ( ) { // 消息长 度if (buffCount <= 2)return;// 消息长度Int16 bodyLength = (short) ((readBuff[1] << 8) | readBuff[0]);Debug.Log ( "[Recv] bodyLength=" + bodyLength); // 消息体、更新 缓冲区// 消 息 处 理 、 继 续读 取 消 息.....
}

readBuff[0] 代表缓冲区的第1 个字节,readBuff[1] 代 表 缓 冲 区 的 第 2 个 字 节, ( readBuff[1] < < 8 ) 代 表 将 缓 冲 区 第 2 个 字 节 的 数 据 乘 以 2 5 6 , 中 间的“|” 代表逻辑与,在这里等同于相加。

  1. 完整的ByteArray
    ByteArray作为一种通用的byte型缓冲区结构 ,它应该支持自动扩展,支持常用的读写操作 。 同时 , 为了做到极致的效率, ByteArray 的大部分成员变量都设为
    public ,以提供灵活性
    在这里插入图片描述
    在这里插入图片描述
    readldx 代表可 读位置,即缓冲区有效数据的起始位置,writeldx 代表可写位置,即缓冲区有效数据的末尾。 成员两数remain 代表缓冲区还可以容纳的字节数
using System;public class ByteArray  {//默认大小const int DEFAULT_SIZE = 1024;//初始大小int initSize = 0;//缓冲区public byte[] bytes;//读写位置public int readIdx = 0;public int writeIdx = 0;//容量private int capacity = 0;//剩余空间public int remain { get { return capacity-writeIdx; }}//数据长度public int length { get { return writeIdx-readIdx; }}//构造函数public ByteArray(int size = DEFAULT_SIZE){bytes = new byte[size];capacity = size;initSize = size;readIdx = 0;writeIdx = 0;}//构造函数public ByteArray(byte[] defaultBytes){bytes = defaultBytes;capacity = defaultBytes.Length;initSize = defaultBytes.Length;readIdx = 0;writeIdx = defaultBytes.Length;}//重设尺寸public void ReSize(int size){if(size < length) return;if(size < initSize) return;int n = 1;while(n<size) n*=2;capacity = n;byte[] newBytes = new byte[capacity];Array.Copy(bytes, readIdx, newBytes, 0, writeIdx-readIdx);bytes = newBytes;writeIdx = length;readIdx = 0;}//写入数据public int Write(byte[] bs, int offset, int count){if(remain < count){ReSize(length + count);}Array.Copy(bs, offset, bytes, writeIdx, count);writeIdx+=count;return count;}//读取数据public int Read(byte[] bs, int offset, int count){count = Math.Min(count, length);Array.Copy(bytes, 0, bs, offset, count);readIdx+=count;CheckAndMoveBytes();return count;}//检查并移动数据public void CheckAndMoveBytes(){if(length < 8){MoveBytes();}}//移动数据public void MoveBytes(){Array.Copy(bytes, readIdx, bytes, 0, length);writeIdx = length;readIdx = 0;} //读取Int16public Int16 ReadInt16(){if(length < 2) return 0;Int16 ret = BitConverter.ToInt16(bytes, readIdx);readIdx += 2;CheckAndMoveBytes();return ret;}//读取Int32public Int32 ReadInt32(){if(length < 4) return 0;Int32 ret = BitConverter.ToInt32(bytes, readIdx);readIdx += 4;CheckAndMoveBytes();return ret;}//打印缓冲区public override string ToString(){return BitConverter.ToString(bytes, readIdx, length);}//打印调试信息public string Debug(){return string.Format("readIdx({0}) writeIdx({1}) bytes({2})",readIdx,writeIdx,BitConverter.ToString(bytes, 0, capacity));}
}

4.2. 重设尺寸
在 某 此 情 况 下, 比 如 需 要 写 人 的 数 据 量 大 于 缓 冲 区 剩 余 长 度 (r e m a i n ) 时 , 就 需 要 扩 大 缓 冲 区。 例 如 要 在 图 4 - 4 2 所 示 缓 冲 区 后 面 添 加 数 据 〝0 5 h e l l o ” , 使 绥 冲 区 数 据 变 成 〝02hioshello”。此时缓冲区只剩余6 个字节,但“0shello” 是7个字节,放不下。此时的 做法是,重新申请一 个长度合适的byte 数组,然后把原byte 数组的数据复制过去,再重新 设置readldx、writeldx 等数值。
在这里插入图片描述
为了避免频繁重设尺寸,规定每次翻倍增加bytes数组长度,即长度是1、2、4、8、 1 6 、 32 、 64 、 128 、 256 、 512 、 1024 、 2048 等 值 。 如果参数 size 是1500 , 缓冲区长度会被设置成 2048 , 如果参数 size 是130 (假设size值合理), 缓冲区长度会被设 置成256。通过“ int n= 1; while(n<size) n*= 2〞 这两行代码,即可得出长度值。在图4-42 所示的缓冲区上执行Resize(6)后,缓冲区将变成图 4- 43 所 示的样式。
在这里插入图片描述
4.3 移动数据
在 某 些 情 形 下 , 例 如 有 效 数 据 长 度 很 小 ( 这 里 设 置 为 8 ), 或 者 数 据 全 部 被 读 取 时 ( readldx==writeldx),可以将数据前移,增加remain,避免bytes数组过长。由于数据很 少, 程 序 执 行 的 效 率 不 会 有 影 响 。 在 图 4 - 4 0 所 示 的 缓 冲 区 上 执 行 CheckAndMoveBytes 后 , 缓冲 区将 变成图4- 44 所示的样式。
在这里插入图片描述

  1. 把上面的ByteArray 应用到异步
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using UnityEngine.UI;
using System;
using System.Linq;public class Echo : MonoBehaviour {//定义套接字Socket socket;//UGUIpublic InputField InputFeld;public Text text;//接收缓冲区ByteArray readBuff = new ByteArray();//发送缓冲区Queue<ByteArray> writeQueue = new Queue<ByteArray>();bool isSending = false;//显示文字string recvStr = "";//点击连接按钮public void Connection(){//Socketsocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);//为了精简代码:使用同步Connect//             不考虑抛出异常socket.Connect("127.0.0.1", 8888);socket.BeginReceive( readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);}//Receive回调public void ReceiveCallback(IAsyncResult ar){try {Socket socket = (Socket) ar.AsyncState;//获取接收数据长度int count = socket.EndReceive(ar);readBuff.writeIdx+=count;//处理二进制消息OnReceiveData();//继续接收数据if(readBuff.remain < 8){readBuff.MoveBytes();readBuff.ReSize(readBuff.length*2);}socket.BeginReceive( readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0, ReceiveCallback, socket);}catch (SocketException ex){Debug.Log("Socket Receive fail" + ex.ToString());}}//public void OnReceiveData(){Debug.Log("[Recv 1] length  =" + readBuff.length);Debug.Log("[Recv 2] readbuff=" + readBuff.ToString());if(readBuff.length <= 2) return;//消息长度int readIdx = readBuff.readIdx;byte[] bytes =readBuff.bytes; Int16 bodyLength = (Int16)((bytes[readIdx+1] << 8 )| bytes[readIdx]);if(readBuff.length < bodyLength)return;readBuff.readIdx+=2; Debug.Log("[Recv 3] bodyLength=" +bodyLength);//消息体byte[] stringByte = new byte[bodyLength];readBuff.Read(stringByte, 0, bodyLength);string s = System.Text.Encoding.UTF8.GetString(stringByte);Debug.Log("[Recv 4] s=" +s);
;Debug.Log("[Recv 5] readbuff=" + readBuff.ToString());//消息处理recvStr = s + "\n" + recvStr;//继续读取消息if(readBuff.length > 2){OnReceiveData();}}//点击发送按钮public void Send(){string sendStr = InputFeld.text;//组装协议byte[] bodyBytes = System.Text.Encoding.Default.GetBytes(sendStr);Int16 len = (Int16)bodyBytes.Length;byte[] lenBytes = BitConverter.GetBytes(len);//大小端编码if(!BitConverter.IsLittleEndian){Debug.Log("[Send] Reverse lenBytes");lenBytes.Reverse();}//拼接字节byte[] sendBytes = lenBytes.Concat(bodyBytes).ToArray();ByteArray ba = new ByteArray(sendBytes);lock(writeQueue){writeQueue.Enqueue(ba);}//sendsocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);if(!isSending){socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, SendCallback, socket);isSending = true;}Debug.Log("[Send] " + BitConverter.ToString(sendBytes));}//Send回调public void SendCallback(IAsyncResult ar){//获取stateSocket socket = (Socket) ar.AsyncState;//EndSend的处理int count = 0;count = socket.EndSend(ar);Debug.Log("Socket Send succ " + count);ByteArray ba = writeQueue.First();ba.readIdx+=count;if(ba.length == 0){lock(writeQueue){writeQueue.Dequeue();ba = writeQueue.First();}}if(ba != null){socket.BeginSend(ba.bytes, ba.readIdx, ba.length, 0, SendCallback, socket);}else{isSending = false;}}public void Update(){text.text = recvStr;}
}

这篇关于socket收发数据的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1065145

相关文章

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat