java大小端字节流解析_玩转socket之字节流操作--拼包、拆包

玩转socket之字节流操作--拼包、拆包

我们开发中用得最多的HTTP协议及超文本传输协议,是一种基于TCP/IP的文本传输协议。基本很少碰到字节流操作。

但是我过我们要用socket,实现一套基本TCP/IP协议的自定义协议,那么,对于字节流的操作,数据包的拼接、拆解,是绕不开的。

本文的所有示例代码在这里

字节流的表示方式

NSData、Data

在iOS,对于字节流,大多数情况下我们要打交道的是NSData类型数据。在swift中它叫Data

字节数组

在OC中它可以表示为Byte类型的数组

Byte bytes[256];

复制代码

Byte等同于UInt8及unsigned char

typedef UInt8 Byte;

typedef unsigned char UInt8;

复制代码

与NSData相互转换:

// bytes转Data

Byte bytes[256] = {0xff,0xaa,0x33,0xe4};

NSData *data = [[NSData alloc] initWithBytes:bytes length:256];

// Data转Bytes

const Byte *nBytes = [data bytes];

// 或者

Byte byte[4] = {0};

[cdata getBytes:byte length:4];

复制代码

swift中,没有Byte类型,他叫[UInt8]。转化为Data时

var bytes : [UInt8] = [0x22,0xef,0xee,0xb3]

let data = Data(bytes)

复制代码

Data转UInt8时,没有像OC一样的bytes方法

我们也可以跟OC中类似的方法

var nBytes = [UInt8]()

data.copyBytes(to:&nBytes, count:4)

复制代码

当然,最简单的方式是这样

let bytes = [UInt8](data)

复制代码

如果你喜欢,也可以这样

let bytes : [UInt8] = data0.withUnsafeBytes({$0.map({$0})})

复制代码十六进制字符串

我们都知道,计算机存储和传输的数据都是二进制的。而二进制不易与阅读。当我们要查看字节流进行调试时,往往会将其转化为16进制的字符串。

OC的NSData对象打印默认得到的是带**及空格的十六进制字符串。如果想让其根容易阅读些,可以在NSData的category中增添:

- (NSString *)hexString

{

NSMutableString *mstr = [NSMutableString new];

const Byte *bytes = [self bytes];

for (int i = 0; i < self.length; i++) {

[mstr appendFormat:@"0x%02x ",bytes[i]];

}

return mstr;

}

复制代码

swift中的Data只能打印出有多少个字节。需要一个生成十六进制串的方法:

extension UInt8 {

var hexString : String {

let str = String(format:"0x%02x",self)

return str

}

}

extension Data {

var bytes : [UInt8] {

return [UInt8](self)

}

var hexString : String {

var str = ""

for byte in self.bytes {

str += byte.hexString

str += " "

}

return str

}

}

复制代码

字节序

在进行字节流的拼接和解析之前,我们必须先了解网络传输中,一个关键的概念字节序

什么叫字节序

当一个整数的值大于255时,必须用多个字节表示。那么就产生一个问题,这些字节是从左到右还是从右到左称为字节顺序。

大端字节序Vs小端字节序

如果我们有一个16进制值0x0025,那么它包含两个字节0x00、0x25。在传输时,我们期望的是,在字节流中,先看到0x00而0x25紧随其后。

如果是大端字节序,一切将会是我们预期的。

但是如果是小端字节序,我们看到的是将是0x25、0x00。

在网络传输时,TCP/IP中规定必须采用网络字节顺,也就是大端字节序。

而不同的CPU和操作系统下的主机字节序是不同的。

我们用的是什么字节序

我们用简单代码测试一下。

int16_t i = 0x0025;

NSData *data = [[NSData alloc] initWithBytes:&i length:2];

NSLog(@"%@",[data hexString]);

// 输出:0x25 0x00

复制代码

swift中

var value : UInt16 = 0x0025

let data = Data(bytes:&value, count:2)

print([UInt8](data))

// 输出:0x25 0x00

复制代码

根据简单的测试。很显然,我们用的是小端字节序

如何转换

我们的主机字节序与网络字节序是不一致。那么,在字节流的编码和解码过程中,就需要进行字节序的转化。

swift中所有整形,都有bigEndian属性,可以很容易进行大小端字节序之间的转化

let v : UInt32 = 78

let bv = v.bigEndian

复制代码

OC中,将转化方法分为两种,主机序转大字节序、大字节序转主机序。其实只用其中之一就可以了。因为两种方法实现都是一样,都是字节序的反转。但为了代码可读性,可以在编码和解析时候区分一下,使用不同方法。

// 大端字节序转主机字节序(小端字节序)

uint16_t CFSwapInt16BigToHost(uint16_t arg)

uint32_t CFSwapInt32BigToHost(uint32_t arg)

// 大端字节序转主机字节序(小端字节序)

uint16_t CFSwapInt16HostToBig(uint16_t arg)

uint32_t CFSwapInt32HostToBig(uint32_t arg)

复制代码double需不需要处理

swift中只对所用整形类型提供转化方法,对于浮点型却没有。那么如果碰到浮点数,我们需要如果处理呢。

一般而言,编译器是按照IEEE标准对浮点型解释的。只要编译器是支持IEEE浮点标准的,就不需要考虑字节顺序。而目前主流编译器都是支持IEEE的。

所以浮点型不用考虑字节序问题

编码方式(拼包)

编码方式即,我们根据事先约定好的格式,从低位到高位,依次拼接相应数据类型及字节长度的数据。最终形成数据包。

下面我们来看一下,针对不同的数据类型的处理方式

整型

OC中拼接方式很简单,只要注意大端字节序的转化就行了。

// 以整形初始化

int a = -25;

int biga = CFSwapInt32HostToBig(a);

NSMutableData *data = [[NSMutableData alloc] initWithBytes:&biga length:sizeof(biga)];

// 整形的拼接

uint16_t b = 8;

uint16_t bigb = CFSwapInt16HostToBig(b);

[data appendBytes:&bigb length:sizeof(bigb)];

复制代码

需要补充一点:OC中int固定占4个字节32位;NSInteger与swift中Int类型一样,根据不同的平台会差生差异。目前iOS都是64位系统,他们都占8个字节64位相当于int64_t或Int64。所以上述代码中int类型的a用CFSwapInt32HostToBig()转化

如果你喜欢byte数组,也可以。

uint32_t value = 0x1234;

Byte byteData[4] = {0};

byteData[0] =(Byte)((value & 0xFF000000)>>24);

byteData[1] =(Byte)((value & 0x00FF0000)>>16);

byteData[2] =(Byte)((value & 0x0000FF00)>>8);

byteData[3] =(Byte)((value & 0x000000FF));

// 输出 byteData:0x00 0x00 0x12 0x34

复制代码

swift当中

var a = 3.bigEndian

var b = UInt16(23).bigEndian

var data = Data(bytes:&a, count:a.bitWidth/8)

let bpoint = UnsafeBufferPointer(start:&b, count:1)

data.append(bpoint)

复制代码

我们也可以转化为UIn8字节数组

extension FixedWidthInteger {

var bytes : [UInt8] {

let size = MemoryLayout.size

if size == 1 {

return [UInt8(self)]

}

var bytes = [UInt8]()

for i in 0..

let distance = (size - 1 - i) * 8;

let sub = self >> distance

let value = UInt8(sub & 0xff)

bytes.append(value)

}

return bytes

}

}

复制代码

如此得到的字节数组本身就是大端字节序,我们可以直接这样用

let c : Int8 = 6

let d : UInt16 = 0x1234

var abytes = c.bytes

abytes += d.bytes

let data0 = Data(abytes)

复制代码浮点型

浮点型的编码方式与整型相似,只是少了大字节序的转化过程。但是上述转成字节数组的方式只适用于整型,对于浮点型并不奏效。

而在swift中,转化字节数组,对于任意类型都有效的方式:

func toByteArray(value: T) -> [UInt8] {

var value = value

let bytes = withUnsafeBytes(of: &value,{$0.map({$0})})

return bytes

}

复制代码

虽然上述方法是范型的,理论上任意类型都可以调用。但准确来说上述方法,只适用于整型与浮点型。

字符串

通常我们使用和传输的字符串都是utf-8编码的。

字符串转NSData很简单。

NSString *str = @"temp";

NSData *datas = [str dataUsingEncoding:NSUTF8StringEncoding];

复制代码

swift中类似的

var data = "buf".data(using:.utf8)

复制代码字符串转字节数组

如果我们要使用字节数组,我们往往会将字符串转化为c字符串。因为c字符串本身就是字符数组,而每个字符正好是一个字节。

NSString *string = @"一个字符串";

Byte *cbytes = (Byte *)[string cStringUsingEncoding:NSUTF8StringEncoding];

复制代码

需要注意的是,这样转化之后我们并不知道字节数组的长度,它与NSString的length截然不同。需要根据字符串末尾的\0标识符来确定

int ci = 0;

while (*(cbytes+ci) != '\0') {

ci++;

}

NSLog(@"string.length=%lu cstring.lenth=%d",(unsigned long)string.length,ci);

// 输出:string.length=5 cstring.lenth=15

复制代码

如果在swift中这要做:

var sarr = "一个字符串".cString(using:.utf8)

复制代码

可以直接得到[CChar]即[Int8],并且可以直接得到数组长度。但是要注意的是,swift是强类型,离我们的[UInt8]还差一步。注意如果CChar是负数及首位上是1,直接转化成UInt8直接抛出异常。

但我们可以先去掉首位,等转化完成再加上

extension String {

var bytes : [UInt8] {

var bytes = self.cString(using:.utf8)?.map({ char -> UInt8 in

if char < 0 {

let b = char & 0b01111111

let c = UInt8(b) | 0b10000000

return c

}else{

return UInt8(char)

}

})

bytes = bytes?.dropLast()

return bytes ?? [UInt8]()

}

}

复制代码

需要注意的是,转成[CChar]之后,其末尾的\0也会带上。这里我们同意把它去掉

字节数组如何拼接

貌似漏了字符数组的拼接方式。下面我们来看看

先说swift,基于之前定义的扩展方法,它拼接起来很简单

var mbytes = [UInt8]()

mbytes += 5.bytes

mbytes += toByteArray(value:3.14)

mbytes += "a string".bytes

let mdata = Data(mbytes)

复制代码数据包中添加字符串

需要注意的是,如果我们在二进制数据包中加入字符串,那么必须指定字符串的长度。要么在字符串之前添加指定长度的字段,要么指定字符串的固定最大长度。不然将会对数据的解析造成困扰。

解码的方式(拆包)

用OC实现

int16_t ri;

UInt8 rj;

double rk;

[data0 getBytes:&ri range:NSMakeRange(0,2)];

int16_t rri = CFSwapInt16BigToHost(ri);

[data0 getBytes:&rj range:NSMakeRange(2,1)];

[data0 getBytes:&rk range:NSMakeRange(3,8)];

NSData *rsData = [data0 subdataWithRange:NSMakeRange(8,8)];

NSString *rs = [[NSString alloc] initWithData:rsData encoding:NSUTF8StringEncoding];

复制代码用swift实现

基于字节数组如何拼接一节中的swift代码片段中的mdata。在swift中可以这样拆包、解析:

let mi : Int = mdata[0..<8].withUnsafeBytes {$0.pointee}

let rmi : Int = mi.bigEndian

let md : Double = mdata[8..<16].withUnsafeBytes {$0.pointee}

let ms = String(data:mdata[16..

复制代码碰到的坑

但是Data的下列方法在swift5中废弃了。

public func withUnsafeBytes(_ body: (UnsafePointer) throws -> ResultType) rethrows -> ResultType

复制代码

换成了同名但参数类型变化了的函数

@inlinable public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R

复制代码

直接影响是用旧方法时,无法联想出pointee属性,每次都得手敲。

那么,我们就换新方法试一下

let mi0 = mdata[0..<8].withUnsafeBytes { $0.load(as:Int.self) }

let rmi0 = mi0.bigEndian

let md0 = mdata[8..<16].withUnsafeBytes { $0.load(as: Double.self) }

let ms0 = String(data:mdata[16..

复制代码

一切看起来都很正常。但是如果我们在要处理的数据最前面加上一个UInt类型。依次方法解析,在获取第二个变量时,会抛出

Fatal error: load from misaligned raw pointer

复制代码

搜索一番后,在stack overflow。得到的结果是除非支持unaligned loads,不然还是用[UInt8]吧

By loading individual UInt8 values instead and performing bit shifting, we can avoid such alignment issues (however if/when UnsafeMutableRawPointer supports unaligned loads, this will no longer be an issue).

然后看到这个答案,知道原来它需要内存像C语言结构体那样的对界方式,如果你取UInt32需要按4的倍数取,如果你取Int需要按8的倍数取

然而,我们通过subscript得到了新的Data肯定是对齐的啊。

有可能通过subscript获取到的Data和原始数据共享的相同内存。那么我们创建新的对象试试:

let mdata1 = Data(mdata[1..<9])

let mi0 = mdata1.withUnsafeBytes {$0.load(as:UInt64.self)}

复制代码

果然跟我们想的一样,这回跑起来一起正常。

用字节数组实现

我们还是以先以swift为例

let bytes = [UInt8](mdata)

let ma : UInt8 = bytes[0]

let mb = bytes[1..<9].enumerated().reduce(0, { (result, arg) -> Int in

let (offset, item) = arg

let size = MemoryLayout.size

let biteOffset = (size - offset - 1) * 8

let temp = Int(item) << biteOffset

return result | temp

})

复制代码

对于double类型,我们没办法进行位运算。聪明的你如果想通过Int进行位运算再转化为double,但是就是在转成Dobule那一步一切将前功尽弃。原因很简单,double遵循IEEE浮点标准,跟整型的编码方式不一样,在做类型转化时,所有已经排好的字节将会按新规则重新生成。

还是像当初将double转成字节数组一样

func byteArrayToType(_ value:[UInt8]) -> T

{

return value.withUnsafeBytes({$0.load(as:T.self)})

}

复制代码

使用时

let mc : Double = byteArrayToType(bytes[9..<17].map({$0}))

复制代码用c指针实现

如果你对C指针很熟悉的话,自然会这样做:

const void *bytes = [data0 bytes];

int16_t ci = *(int16_t*)(bytes);

uint8_t cj = *(uint8_t*)(bytes+2);

double ck = *(double*)(bytes+3);

char *cstr = (char *)(bytes+11);

NSString *nstr = [NSString stringWithCString:cstr encoding:NSUTF8StringEncoding];

复制代码

C结构体的传输

当我们的服务器是C/C++时,那么我们在数据传递时,有一种更为高效的方式,直接传递结构体。

为了有效传输和解析,需要保证

结构体的大小必须是固定的。这就意味着我们在传递字符串或者数组时,必须定义其大小

接受与发送端结构体定义必须一致。就是说,其一变量的顺序一致;其二结构体的字节对齐方式一致,都是自然对界(按结构体的成员中size最大的成员对齐)或者#pragma pack (n)申明一致

如何满足上述两个条件。我们可以很容易的完成数据包的生成及拆解。

数据包的生成:

struct Message msg = {};

msg.type = 1;

msg.seq = 0x0102;

msg.timeTemp = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;

memcpy(msg.content,cstr,16);

void *sent = &msg;

int length = sizeof(struct Message);

NSData *cdata = [[NSData alloc] initWithBytes:&sent length:length];

复制代码

拆解:

const void *rec = [cdata bytes];

struct Message nmsg = *(struct Message *)rec;

复制代码

参考资料

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

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

相关文章

js替换数组中字符串实例

这个是替换数组中的一个对象字符串&#xff1b; 直接上代码&#xff1a; 1 var aaa[ 2 {"name":"张珊","sex":"man"}, 3 {"name":"李斯","sex":"woman"}, 4 …

ie浏览器升级_微软呼吁用户停用IE浏览器 2020年将不再更新升级

驱动中国2019年2月10日消息 IE浏览器作为美国微软公司推出的一款网页浏览器成为许多人接触互联网初选&#xff0c;深受不少用户喜欢但这款流畅多年的浏览器正式遭弃用&#xff01;今日微软网络安全专家在在官方Winhows IT Pro上发帖呼吁用户停止使用IE浏览器&#xff01;微软网…

有条件忽略测试的JUnit规则

我一直认为使用Ignore停用测试是一个坏主意。 例外&#xff0c;这可能是一种将间歇性失败的测试放入隔离区以供以后处理的方法&#xff08;如Martin Fowler 在此处所述 &#xff09;。 随着越来越多的测试不断被忽略和遗忘&#xff0c;这带来了测试套件衰减的危险。 因此&#…

【学亮IT手记】PL/SQL游标编程

游标提供了一种从表中检索数据并进行操作的灵活手段&#xff0c;主要用在服务器上&#xff0c;处理由客户端发送给服务器端的sql语句&#xff0c;或者是批处理、存储过程、触发器中的数据处理请求。 显式游标 是由用户声明和操作的一种游标&#xff0c;通常用于将查询出的结果集…

CentOS安装zookeeper

第一步&#xff0c;先去Apache下载安装包 http://mirror.bit.edu.cn/apache/zookeeper/ 第二步&#xff0c;上传到服务器并解压 tar -zxvf zookeeper-3.4.6 第三步&#xff0c;新建一个文件夹 #先改个名 mv zookeeper-3.4.6 zookeeper cd zookeeper mkdir data 第四步&#xff…

php根据id查找条件怎么写_thinkphp where()条件查询

今天来给大家讲下查询最常用但也是最复杂的where方法&#xff0c;where方法也属于模型类的连贯操作方法之一&#xff0c;主要用于查询和操作条件的设置。where方法的用法是ThinkPHP查询语言的精髓&#xff0c;也是ThinkPHP ORM的重要组成部分和亮点所在&#xff0c;可以完成包括…

3分钟简单了解 prototype 和 __proto__

关于prototype 1. 所有的函数都会有一个prototype属性&#xff0c;属性值是一个普通对象&#xff1b; 2. 当我们去new一个构造函数的实例时&#xff0c;构造函数的原型对象(prototype)会被赋值给它实例的[[Prototype]]属性上&#xff1b; 3. prototype可被称为“显式原型”。…

rdlc报表 矩形高固定_固定资产条码管理系统特点分析

固定资产条码管理系统的特点都有什么&#xff1f;固定资产是维持企业稳定健康发展的重要物质保障&#xff0c;若想最大化发挥固定资产的效能作用&#xff0c;满足生产经营管理的需求&#xff0c;提高应对企业内外部经营环境变化的能力&#xff0c;企业管理者就必须及时了解固定…

Spring 4中@ControllerAdvice的改进

在Spring 4的许多新功能中&#xff0c;我发现了ControllerAdvice的改进。 ControllerAdvice是Component的特殊化&#xff0c;用于定义适用于所有RequestMapping方法的 ExceptionHandler&#xff0c; InitBinder和ModelAttribute方法。 在Spring 4之前&#xff0c; ControllerAd…

bzoj 2007 [Noi2010]海拔——最小割转最短路

题目&#xff1a;https://www.lydsy.com/JudgeOnline/problem.php?id2007 一个点的高度一定不是0就是1。答案一定形如一个左上角的连通块全是0的点、一个右下角的连通块全是1的点。 注意从东到西还有从南到北的边也有用&#xff01;因为不一定是一个阶梯形的&#xff0c;还可以…

php 四维数组_PHP 多维数组

PHP - 两维数组两维数组是数组的数组(三维数组是数组的数组的数组)。首先&#xff0c;让我们看看下面的表格&#xff1a;品牌库存销量Volvo3320BMW1715Saab52Land Rover1511我们能够在两维数组中存储上表中的数据&#xff0c;就像这样&#xff1a;$cars array(array("Vol…

Vue于React特性对比(二)

一&#xff0c;关于响应式数据更新方式的实现 1&#xff09;只有在data里面定义的数据才会有响应式更新 vue依赖的defineProperty的数据劫持加上依赖数据&#xff0c;实现数据的响应式更新。可以称之为依赖式的响应。因为依赖所以注定了只会更新与data相关的数据。 2&#xff0…

《黑客攻防技术宝典Web实战篇@第2版》读书笔记1:了解Web应用程序

读书笔记第一部分对应原书的第一章&#xff0c;主要介绍了Web应用程序的发展&#xff0c;功能&#xff0c;安全状况。 Web应用程序的发展历程 早期的万维网仅由Web站点构成&#xff0c;只是包含静态文档的信息库&#xff0c;随后人们发明了Web浏览器用来检索和显示那些文档&am…

电脑入门完全自学手册_3DMAX零基础入门到精通的学习路线和教程

没有美术基础可以学建模吗&#xff1f;毋庸置疑&#xff0c;只要肯学都是可以的&#xff01;那么我们今天来说说零基础学习建模要着重注意什么。最高效最快速的入门学习方式&#xff1a;看知识兔视频课程&#xff0c;跟着知识兔老师操作&#xff0c;听知识兔老师讲解&#xff0…

使用Java RMI时要记住的两件事

这是一篇简短的博客文章&#xff0c;介绍使用Java RMI时应注意的两个常见陷阱。 设置java.rmi.server.hostname 如果您感到陌生&#xff0c;Connection拒绝托管&#xff1a; RMI客户端上的错误消息&#xff0c;并且您确定连接应该正常工作&#xff08;您仔细检查了所有标准配置…

Python-UiAutomator2实现Android自动化测试

本帖转自搜狗测试公众号 【一、前言】 基于Python-UiAutomator2实现Android自动化测试&#xff0c;小编在Android应用的自动化性能测试中进行了实践。本篇将简单介绍python中使用adb、aapt命令的方法以及Python-UiAutomator2使用&#xff0c;后续文章将对环境搭建以及自动化性能…

阅读笔记:ImageNet Classification with Deep Convolutional Neural Networks

概要&#xff1a; 本文中的Alexnet神经网络在LSVRC-2010图像分类比赛中得到了第一名和第五名&#xff0c;将120万高分辨率的图像分到1000不同的类别中&#xff0c;分类结果比以往的神经网络的分类都要好。为了训练更快&#xff0c;使用了非饱和神经元并对卷积操作进行双GPU实现…

vsCode 设置vue 保存自动格式化代码

setting {// vscode默认启用了根据文件类型自动设置tabsize的选项"editor.detectIndentation": false,// 重新设定tabsize"editor.tabSize": 2,// #每次保存的时候自动格式化"editor.formatOnSave": true,// #每次保存的时候将代码按eslint格式进…

您是否敢更改HashMap实现?

如今&#xff0c;有大胆的工程师在为Oracle工作。 昨天尝试确定Heisenbug时&#xff0c;我得出了这个结论。 不足为奇&#xff0c;当我尝试找到解决方案时&#xff0c;该错误似乎消失了。 几个小时后&#xff0c;将问题归结为JDK7更新之间的细微差别&#xff0c;该错误的“ Hei…

php 函数变量的顺序,PHP实现参数的自定义顺序调用 | 剑花烟雨江南

首先我们以添加用户为例来看两个方法&#xff1a;//方法①public function addUser(string $userName, string $userPassword, string $telephone, string $userEmail ){}//方法②public function addUser(array $data){}方法①的优点&#xff1a; 通过方法的定义&#xff0c;…