文章851
标签121
分类10

PHP 数据序列化 msgpack 扩展安装及使用

PHP 数据序列化 msgpack 扩展安装及使用

要安装和使用PHP的msgpack扩展,你需要按照以下步骤进行操作:

  1. 检查系统要求:确保你的系统满足安装msgpack扩展的要求。msgpack扩展需要PHP版本为7.0.0或更高版本,并且需要安装msgpack C扩展库。
  2. 安装msgpack C扩展库:首先,你需要安装msgpack C扩展库。具体的安装方式取决于你的操作系统。例如,在Ubuntu上,可以使用以下命令安装:

    sudo apt-get install php-msgpack

    在其他操作系统上,你需要查找适合你系统的安装方法。

  3. 安装PHP的msgpack扩展:安装C扩展库后,你可以通过PECL(PHP Extension Community Library)安装msgpack扩展。运行以下命令:

    sudo pecl install msgpack

    如果提示选择msgpack版本,选择最新版本即可。

  4. 配置PHP加载msgpack扩展:编辑PHP的配置文件(通常为php.ini),添加以下行:

    extension=msgpack.so

    保存并关闭配置文件。

  5. 重启Web服务器:为了使配置生效,你需要重新启动你的Web服务器(例如Apache或Nginx)。
  6. 测试msgpack扩展:创建一个PHP文件(例如test.php),输入以下代码进行测试:

    <?php
    $data = array('name' => 'John', 'age' => 30);
    $serialized = msgpack_pack($data);
    $unserialized = msgpack_unpack($serialized);
    
    var_dump($unserialized);
    ?>

    运行该PHP文件,如果没有任何错误并且输出正确地反序列化了$data数组,说明msgpack扩展已成功安装和使用。

以上是安装和使用PHP的msgpack扩展的基本步骤。请注意,具体的安装和配置过程可能因操作系统和PHP版本的不同而有所差异。如果遇到任何问题,建议查阅相关文档或寻求开发社区的帮助。

深入了解PHP8 JIT(即时编译)功能

深入了解PHP8 JIT(即时编译)功能

引言

PHP 8引入了一个令人兴奋的新特性,即时编译(JIT)功能。JIT编译器可以在运行时将PHP代码转换为本地机器码,从而提高代码的执行速度。本文将深入介绍PHP 8 JIT功能的工作原理、优势和使用方法。

什么是即时编译(JIT)?

即时编译(Just-In-Time Compilation)是一种动态编译技术,它在程序运行时将解释性语言的代码转换为本地机器码,以提高执行速度。传统的解释性语言(如PHP)在每次执行代码时都需要解析和执行,而JIT编译器可以将频繁执行的代码片段编译成本地机器码,减少了解析和执行的开销,提高了代码的执行效率。

PHP 8 JIT功能的工作原理

PHP 8的JIT功能是基于编译器实现的,它使用了LLVM(低级虚拟机)作为后端编译器。当启用JIT功能时,PHP引擎会在执行过程中将一部分热点代码(Hot Code)编译成机器码,并缓存起来,以便下次执行时直接使用。这样,在后续的执行中,不需要再进行解析和解释,而是直接执行编译后的机器码,从而提高了代码的执行速度。

如何启用PHP 8 JIT功能

要启用PHP 8 JIT功能,需要在PHP的配置文件中进行相应的设置。以下是启用PHP 8 JIT功能的步骤:

  1. 打开PHP的配置文件(php.ini)。
  2. 查找[OPcache]部分,并添加以下配置:

    opcache.enable=1
    opcache.jit_buffer_size=100M
    opcache.jit=tracing

    这些配置项分别用于启用OPcache和JIT功能,设置JIT缓冲区的大小,以及选择JIT模式。在这个例子中,我们选择了tracing模式,该模式通过跟踪代码的执行来确定热点代码,进而进行编译。

  3. 保存并关闭配置文件。
  4. 重启你的Web服务器或PHP-FPM进程,使配置生效。

PHP 8 JIT功能的优势和注意事项

启用PHP 8 JIT功能可以带来以下优势:

  • 更快的执行速度:JIT编译器将热点代码编译成机器码,减少了解析和解释的开销,从而提高了代码的执行速度。
  • 更低的内存消耗:编译后的机器码占用更少的内存空间,相比解释性执行,可以减少内存的使用。
  • 可选的启用和配置:你可以根据应用程序的需求选择是否启用JIT功能,并根据实际情况调整配置。

需要注意的是,PHP 8 JIT功能并不是适用于所有场景的解决方案。在某些情况下,JIT编译可能会增加代码执行的开销,尤其是对于一些简单的代码。因此,在启用JIT功能之前,建议进行性能测试,并根据测试结果评估是否适合启用。

结论

PHP 8的即时编译(JIT)功能是一个令人激动的新特性,它可以提高PHP代码的执行速度。通过启用JIT功能,将热点代码编译成机器码,可以减少解析和解释的开销,从而提高代码的性能。然而,需要注意的是,JIT功能并非适用于所有场景,因此在启用之前建议进行性能测试,并根据实际情况评估是否适合启用。希望本文能够帮助你深入了解PHP 8 JIT功能,并为你的应用程序的性能提升提供一些指导。

PHP 事务中锁等待超时 读数据异常问题 for update

在PHP中,默认情况下,当一个事务因为锁等待超时而失败时,MySQL扩展不会抛出异常或错误,而是简单地返回一个空的结果集。这对于应用程序来说可能并不方便,因为无法区分是数据本身为空,还是因为锁等待超时导致读不到数据。

网上给的结局方案 都不是很靠谱 可能是场景不同吧.
延长锁等待时间,治标不治本 innodb_lock_wait_timeout.

我的遇到的场景是允许抛错异常中断操作的. 并发场景下 我们需要锁定一条记录 for update 排他锁. 如果锁的久了其他请求会等待锁解开.
默认锁等待50秒, 超时后PHP 并不会抛出异常. 找了很多方法 , 根本捕获不了 , 修改 innodb_rollback_on_timeout=ON也没有什么卵用.

最后自己的解决方案是: 通过记录响应时长 超过50秒则在代码中抛错.

PHP7转PHP8代码需要做哪些修改?

在将 ·PHP 7· 代码转换为 PHP 8 时,可能需要做出一些修改,因为 PHP 8 引入了一些新特性并删除了一些过时的特性。以下是可能需要修改的一些方面:

声明严格模式:PHP 8 强制实施了类型声明,因此需要在 PHP 7 代码中进行相应修改,添加类型声明。可以使用 declare(strict_types=1); 声明严格模式,强制要求在函数和方法中进行类型声明。

面向对象编程:PHP 8 引入了一些新的面向对象编程特性,如构造函数属性推断、命名参数和属性提取等。如果使用了这些新特性,需要进行相应修改。

函数调用:PHP 8 修改了一些函数的参数,可能需要根据新的参数列表进行修改。例如,array_key_first()array_key_last() 函数需要一个数组参数,而在 PHP 7 中不需要。

错误处理:PHP 8 改变了一些错误处理方式,如对于未声明变量的引用,现在会抛出一个 Error 异常而不是一个 Notice 错误。因此,需要相应地处理这些错误。

移除过时的特性:PHP 8 移除了一些过时的特性,如 create_function() 函数和 magic_quotes_gpc 配置选项等。需要检查代码中是否使用了这些过时的特性,并进行相应的修改。

以上是可能需要修改的一些方面,具体需要根据代码的实际情况进行修改。可以使用 PHP 官方提供的迁移工具来辅助进行代码迁移,例如 php-src/UPGRADING 文件和 php-compat-info 工具。

php-src/UPGRADINGphp-compat-info

以下是使用 php-src/UPGRADING 文件和 php-compat-info 工具进行 PHP 7 代码转换为 PHP 8 的步骤:

下载 php-src/UPGRADING 文件:可以从 PHP 官方网站下载 php-src/UPGRADING 文件,该文件包含了从 PHP 7PHP 8 的所有重大变化和不兼容之处。

使用 php-compat-info 工具:php-compat-info 工具是一个命令行工具,可以分析代码库中使用的 PHP 版本和扩展,并生成有关 PHP 7PHP 8 转换的报告。

检查报告:根据 php-compat-info 工具生成的报告,检查代码库中使用的 PHP 版本和扩展,并查找需要修改的部分。报告中将显示需要修改的代码行以及建议的修改方法。

进行修改:根据报告中的建议,对代码进行相应的修改。修改后,可以再次使用 php-compat-info 工具进行检查,确保代码与 PHP 8 兼容。

尽管 php-compat-info 工具可以帮助分析代码并提供有用的建议,但它并不能处理所有情况。因此,在进行代码转换时,建议仔细阅读 php-src/UPGRADING 文件和 PHP 官方文档,以确保代码的兼容性和正确性。此外,建议进行逐步转换,先进行部分功能的测试和修改,而不是一次性修改整个代码库。这样可以更好地控制风险,避免潜在的问题。

PHP8对函数参数列表的修改

PHP 8 对一些函数的参数列表进行了修改,以下是一些常见的函数和它们在 PHP 8 中的参数列表变化:

  • implode() 函数的参数顺序变化,将 glue 参数放在第一位,将 pieces 参数放在第二位。PHP 7 参数列表:implode($glue, $pieces) PHP 8 参数列表:implode($pieces, $glue = '')
  • array_key_first()array_key_last() 函数需要传入一个数组参数。PHP 7 参数列表:array_key_first($array)、array_key_last($array) PHP 8参数列表:array_key_first(array $array)、array_key_last(array $array)
  • str_starts_with()str_ends_with() 函数需要传入一个字符串参数。PHP 7 参数列表:str_starts_with($haystack, $needle)、str_ends_with($haystack, $needle) PHP 8 参数列表:str_starts_with(string $haystack, string $needle)、str_ends_with(string $haystack, string $needle)
  • (mb_strimwidth() 函数将 start 参数改名为 startpos。PHP 7 参数列表:mb_strimwidth($str, $start, $width, $trimmarker = '', $encoding = mb_internal_encoding()). PHP 8 参数列表:mb_strimwidth(string $str, int $startpos, int $width, string $trimmarker = '', string $encoding = mb_internal_encoding())

以上仅列举了一些常见的函数,还有其他函数的参数列表也可能发生变化。在进行 PHP 7 到 PHP 8 的代码升级时,需要仔细查阅官方文档和 php-src/UPGRADING 文件,以确保代码的正确性和兼容性。

严格模式

PHP 8 引入了声明严格模式(strict mode),它是一种更加严格的类型检查模式,可以让开发者在开发过程中更加安全、可靠地编写 PHP 代码。

在声明严格模式下,PHP 将会对类型和变量使用更加严格的检查,包括以下几个方面:

类型声明在声明严格模式下,函数和方法的参数类型必须与声明的类型相匹配。如果类型不匹配,PHP 会抛出一个致命错误。

示例:

declare(strict_types=1);
 
function sum(int $a, int $b) {
    return $a + $b;
}
 
echo sum(1, 2); // 输出 3
echo sum(1.5, 2); // 报错:Uncaught TypeError

返回类型声明在声明严格模式下,函数和方法的返回值类型必须与声明的类型相匹配。如果类型不匹配,PHP 会抛出一个致命错误。

示例:

declare(strict_types=1);
 
function divide(int $a, int $b): float {
    return $a / $b;
}
 
echo divide(6, 2); // 输出 3.0
echo divide(6, 0); // 报错:Uncaught TypeError

类型推导在声明严格模式下,PHP 会自动推导一些变量的类型。如果推导出来的类型与声明的类型不匹配,PHP 会抛出一个致命错误。

示例:

declare(strict_types=1);
 
function multiply(int $a, int $b) {
    $result = $a * $b;
    return $result;
}
 
echo multiply(2, 3); // 输出 6
echo multiply("2", 3); // 报错:Uncaught TypeError

需要注意的是,声明严格模式需要在文件的开头通过 declare(strict_types=1) 声明启用。如果未启用声明严格模式,则 PHP 会采用弱类型检查模式,允许一些类型的自动转换。

命名参数

PHP 8 引入了命名参数(named parameters)功能,它可以让我们通过参数名来传递函数和方法的参数,而不必按照原始函数定义的参数顺序传递参数。

在 PHP 8 中,我们可以通过在函数或方法的调用时使用 参数名 => 值 的方式来传递参数,而不必按照原始函数定义的参数顺序传递参数。这种传递参数的方式称为命名参数。

命名参数的优点包括:

更好的可读性:命名参数可以提高函数或方法调用的可读性,因为参数名称可以清晰地表明参数的作用。

更高的灵活性:通过命名参数,我们可以只传递需要的参数,而不必传递所有参数,这样可以让函数或方法更加灵活。

下面是一个简单的例子,展示了如何使用命名参数:

function createPerson(string $name, int $age, string $gender) {
    // 创建一个人员对象
}
 
// 使用命名参数调用函数
createPerson(name: "Tom", age: 18, gender: "male");

在上面的例子中,我们通过 参数名 => 值 的方式来传递函数的参数。这样可以让代码更加清晰易懂,特别是在函数参数较多时。命名参数必须在普通参数之后传递,且不能重复声明同一个参数。此外,在使用命名参数时,我们不必按照原始函数定义的参数顺序传递参数,但是必须使用参数名称来传递参数。

函数调用

PHP 8 引入了一些新的函数调用语法,包括可选链式调用、null 合并运算符的链式调用、match 表达式等。

可选链式调用:

可选链式调用是一种在调用对象方法或访问对象属性时,如果对象为 null 则不抛出异常而是返回 null 的语法糖。在 PHP 8 中,我们可以使用 ?-> 运算符来进行可选链式调用,示例如下:

// 在对象方法调用中使用可选链式调用
$result = $object?->getValue();
 
// 在对象属性访问中使用可选链式调用
$value = $object?->property;

null 合并运算符的链式调用:

null 合并运算符(??)的链式调用可以让我们在多个变量中查找非空值并返回第一个非空值。在 PHP 8 中,我们可以使用 ??= 运算符来进行 null 合并运算符的链式调用,示例如下:

// 在 null 合并运算符中使用链式调用
$result = $object->getValue() ?? $object->getValue2() ?? $default;

match 表达式:

match 表达式是一种新的语法糖,用于替代 switch 语句。在 PHP 8 中,match 表达式可以更加简洁地实现类似的功能,示例如下:

// 使用 match 表达式代替 switch 语句
$value = 5;
$result = match ($value) {
    1 => 'one',
    2 => 'two',
    3, 4 => 'three or four',
    default => 'other',
};

在上面的示例中,我们使用 match 表达式根据变量 $value 的值来匹配不同的条件,并返回对应的结果。在 match 表达式中,我们可以使用逗号将多个条件合并在一起,用于匹配相同的结果。此外,match 表达式中必须有一个 default 分支来处理无法匹配的情况。

PHP8移除的过时特性

PHP 在不同的版本中移除了很多过时的特性,以下是一些常见的被移除的特性:

  • 不再支持 ASP 风格的标记,即将 <??> 用于标记 PHP 代码的方式。这种标记在 PHP 7.4 中被移除。
  • 不再支持未赋值的函数调用,即对一个未定义的函数进行调用,这在 PHP 7.2 中被移除。
  • 不再支持传递可变数量的参数给引用参数,这在 PHP 7.3 中被移除。
  • 不再支持传递未声明的类给 instanceof 运算符,这在 PHP 8.0 中被移除。
  • 不再支持使用被废弃的错误控制运算符 @,这在 PHP 8.0 中被移除。
  • 不再支持使用被废弃的 each() 函数,这在 PHP 7.2 中被移除。
  • 不再支持使用被废弃的 create_function() 函数,这在 PHP 7.2 中被移除。
  • 不再支持使用被废弃的 mbstringiconv 扩展中的一些函数,例如 mb_ereg_replace()iconv_set_encoding() 等。

在进行 PHP 版本升级时,需要仔细查阅官方文档和 php-src/UPGRADING 文件,以确保代码的正确性和兼容性。

str_contains()str_starts_with() 函数

PHP 8 新增了两个字符串函数 str_contains()str_starts_with(),用于判断一个字符串是否包含另一个字符串或者以另一个字符串开头。如果要适配这两个函数,可以按照以下方式使用:

str_contains() 函数该函数用于判断一个字符串是否包含另一个字符串,如果包含则返回 true,否则返回 false。它的参数列表如下:

bool str_contains(string $haystack, string $needle)

其中,$haystack 表示要搜索的字符串,$needle 表示要查找的子字符串。

示例:

$string = "hello world";
if (str_contains($string, "world")) {
    echo "包含";
} else {
    echo "不包含";
}

输出:

包含
str_starts_with() 函数该函数用于判断一个字符串是否以另一个字符串开头,如果是则返回 true,否则返回 false。它的参数列表如下:

bool str_starts_with(string $haystack, string $needle)

其中,$haystack 表示要搜索的字符串,$needle 表示要查找的前缀字符串。

示例:

$string = "hello world";
if (str_starts_with($string, "hello")) {
    echo "以 hello 开头";
} else {
    echo "不以 hello 开头";
}

输出:

hello 开头
这两个函数仅在 PHP 8 中才可用,如果在 PHP 7 或更早的版本中使用,会导致语法错误。如果要在旧版本的 PHP 中使用类似的功能,可以使用其他函数或者手动实现。

WebSocket 协议部分详解

WebSocket 是一种支持双向通信的网络协议。

双向通信:客户端(比如浏览器)可以向服务端发送消息,服务端也可以主动向客户端发送消息。
这样就实现了客户端和服务端的双向通信,那么上面所说的消息推送就比较容易实现了。

原先的 HTTP1.0/1.1 只能是客户端向服务端发送消息。

协议特点:

  • 建立在 TCP 协议之上。
  • WebSocket 协议是从 HTTP 协议升级而来。
  • 与 HTTP 协议良好兼容新。默认端口是 80 和 443,握手阶段采用 HTTP 协议。
  • 数据格式比较轻量,通信效率高,性能开销小。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务端通信。
  • 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。
  • 可以支持扩展,定了扩展协议。
  • 保持连接状态,websocket 是一种有状态的协议,通信就可以省略部分状态信息。
  • 实时性更强,因为是双向通信协议,所以服务端可以随时向客户端发送数据。

WebSocket 数据交换

数据帧格式

WebSocket 协议中,客户端与服务端数据交换的最小信息单位叫做帧(frame),由 1 个或多个帧按照次序组成一条完整的消息(message)。

数据传输的格式是由 ABNF 来描述的。

WebSocket 数据帧的统一格式如下图:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

https://www.rfc-editor.org/rfc/rfc6455.html#section-5.2 Base Framing Protocol)

上面图中名词解释:

|名词| 说明| 大小|
|-|-|-|
|FIN| 如果是 1,表示这是消息(message)的最后一个分片(fragment);如果是 0,表示不是是消息(message)的最后一个分片(fragment)| 1 个比特|
|RSV1, RSV2, RSV3| 一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非 0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错 |各占 1 个比特|
|opcode| 操作代码,Opcode 的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection)| 4 个比特|
|mask| 表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。
如果 Mask 是 1,那么在 Masking-key 中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask 都是 1。| 1 个比特|
|Payload length| 数据载荷的长度,单位是字节。假设数 Payload length === x,如果:
x 为 0~126:数据的长度为 x 字节。
x 为 126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度。
x 为 127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。
此外,如果 payload length 占用了多个字节的话,payload length 的二进制表达采用网络序(big endian,重要的位在前)。| 为 7 位,或 7+16 位,或 1+64 位。|
|Masking-key| 所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask 为 1,且携带了 4 字节的 Masking-key。如果 Mask 为 0,则没有 Masking-key。
备注:载荷数据的长度,不包括 mask key 的长度。 |0 或 4 字节(32 位|
|Payload data| 载荷数据:包括了扩展数据、应用数据。其中,扩展数据 x 字节,应用数据 y 字节。The "Payload data" is defined as "Extension data" concatenated with "Application data".
扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。 |(x+y) 字节|

表中 opcode 操作码:

  • %x0:表示一个延续帧(continuation frame)。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
  • %x1:表示这是一个文本帧(frame),text frame
  • %x2:表示这是一个二进制帧(frame),binary frame
  • %x3-7:保留的操作代码,用于后续定义的非控制帧。
  • %x8:表示连接断开。connection close
  • %x9:表示这是一个 ping 操作。a ping
  • %xA:表示这是一个 pong 操作。a pong
  • %xB-F:保留的操作代码,用于后续定义的控制帧。

数据帧另外一种表达方式

 ws-frame                = frame-fin           ; 1 bit in length
                              frame-rsv1          ; 1 bit in length
                              frame-rsv2          ; 1 bit in length
                              frame-rsv3          ; 1 bit in length
                              frame-opcode        ; 4 bits in length
                              frame-masked        ; 1 bit in length
                              frame-payload-length   ; either 7, 7+16,
                                                     ; or 7+64 bits in
                                                     ; length
                              [ frame-masking-key ]  ; 32 bits in length
                              frame-payload-data     ; n*8 bits in
                                                     ; length, where
                                                     ; n >= 0

    frame-fin               = %x0 ; more frames of this message follow
                            / %x1 ; final frame of this message
                                  ; 1 bit in length

    frame-rsv1              = %x0 / %x1
                              ; 1 bit in length, MUST be 0 unless
                              ; negotiated otherwise

    frame-rsv2              = %x0 / %x1
                              ; 1 bit in length, MUST be 0 unless
                              ; negotiated otherwise

    frame-rsv3              = %x0 / %x1
                              ; 1 bit in length, MUST be 0 unless
                              ; negotiated otherwise

    frame-opcode            = frame-opcode-non-control /
                              frame-opcode-control /
                              frame-opcode-cont

    frame-opcode-cont       = %x0 ; frame continuation

    frame-opcode-non-control= %x1 ; text frame
                            / %x2 ; binary frame
                            / %x3-7
                            ; 4 bits in length,
                            ; reserved for further non-control frames

    frame-opcode-control    = %x8 ; connection close
                            / %x9 ; ping
                            / %xA ; pong
                            / %xB-F ; reserved for further control
                                    ; frames
                                    ; 4 bits in length
                                    
    frame-masked            = %x0
                            ; frame is not masked, no frame-masking-key
                            / %x1
                            ; frame is masked, frame-masking-key present
                            ; 1 bit in length

    frame-payload-length    = ( %x00-7D )
                            / ( %x7E frame-payload-length-16 )
                            / ( %x7F frame-payload-length-63 )
                            ; 7, 7+16, or 7+64 bits in length,
                            ; respectively

    frame-payload-length-16 = %x0000-FFFF ; 16 bits in length

    frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
                            ; 64 bits in length

    frame-masking-key       = 4( %x00-FF )
                              ; present only if frame-masked is 1
                              ; 32 bits in length

    frame-payload-data      = (frame-masked-extension-data
                               frame-masked-application-data)
                            ; when frame-masked is 1
                              / (frame-unmasked-extension-data
                                frame-unmasked-application-data)
                            ; when frame-masked is 0

    frame-masked-extension-data     = *( %x00-FF )
                            ; reserved for future extensibility
                            ; n*8 bits in length, where n >= 0

    frame-masked-application-data   = *( %x00-FF )
                            ; n*8 bits in length, where n >= 0

    frame-unmasked-extension-data   = *( %x00-FF )
                            ; reserved for future extensibility
                            ; n*8 bits in length, where n >= 0

    frame-unmasked-application-data = *( %x00-FF )
                            ; n*8 bits in length, where n >= 0

客户端到服务端的掩码算法

https://www.rfc-editor.org/rfc/rfc6455.html#section-5.3 Client-to-Server Masking

掩码键(Masking-key)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

举例说明:

Octet i of the transformed data ("transformed-octet-i") is the XOR of
octet i of the original data ("original-octet-i") with octet at index
i modulo 4 of the masking key ("masking-key-octet-j"):

     j                   = i MOD 4
     transformed-octet-i = original-octet-i XOR masking-key-octet-j
original-octet-i:为原始数据的第 i 字节。
transformed-octet-i:为转换后的数据的第 i 字节。
j:为i mod 4的结果。
masking-key-octet-j:为 mask key 第 j 字节。

算法描述为: original-octet-imasking-key-octet-j 异或后,得到 transformed-octet-i

j  = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j

数据分片

分片的目的:

  • 有了消息分片,发送一个消息的时候,就可以发送未知大小的信息。如果消息不能被分片,那么就不得不缓冲整个消息,以便计算长度。而有了分片就可以选择合适大小缓冲区来缓冲分片。
  • 第二个目的是可以使用多路复用。

WebSocket 的每条消息(message)可能被切分为多个数据帧。

当 WebSocket 的接收方接收到一个数据帧时,会根据 FIN 值来判断是否收到消息的最后一个数据帧。

从上图可以看出,FIN = 1 时,表示为消息的最后一个数据帧;FIN = 0 时,则不是消息的最后一个数据帧,接收方还要继续监听接收剩余数据帧。

opcode 表示数据传输的类型,0x01 表示文本类型的数据;0x02 表示二进制类型的数据;0x00 比较特殊,表示延续帧(continuation frame),意思就是完整数据对应的数据帧还没有接收完。

更多分片内容请看这里:https://www.rfc-editor.org/rfc/rfc6455.html#section-5.4

消息分片example:

Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!
(具体例子见:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers)

五:怎么保持连接

在第二小结中我们介绍了 websocket 的特点,其中有一个是保持连接状态。

websocket 是建立在 tcp 之上,那也就是客户端与服务端的 tcp 通道要保持连接不断开。

怎么保持呢?可以用心跳来实现。

其实 websocket 协议早就想到了,它的帧数据格式中有一个字段 opcode,定义了 2 种类型操作, pingpongopcode 分别是 0x90xA

说明:对于长时间没有数据往来的连接,如果依旧长时间保持连接的状态,那么就会浪费连接资源。

[完]

六、参考

">