php 自定义二进制协议

协议设计

  • 数据格式选择 :二进制格式适用于高效传输大量数据的场景,相比于 JSON 等文本格式,二进制格式更紧凑,解析效率更高。

  • 协议结构定义 :一般包括头部和体部。头部可包含版本信息、消息类型、时间戳、数据长度等元数据;体部则包含具体的业务数据。例如,头部长度固定为 12 字节,其中前 2 字节表示版本号,接下来 2 字节表示消息类型,再接下来 4 字节表示时间戳,最后 4 字节表示数据长度,体部紧跟在头部之后,长度由头部指定的数据长度确定。

编码实现

  • PHP Socket 扩展 :PHP 的 Socket 扩展允许应用程序通过网络套接字与其他应用程序进行通信,是实现自定义协议的基础工具之一。使用 socket_create()、socket_bind()、socket_listen()、socket_accept()、socket_read()、socket_write() 等函数可以创建和管理套接字,实现网络通信。

  • 数据打包与解包 :在 PHP 中,可以使用 pack() unpack() 函数来实现数据的打包和解包。pack() 函数将变量按照指定的格式打包成二进制字符串,而 unpack() 函数则将二进制字符串按照指定的格式解包成数组。

示例

以下是一个简单的基于 PHP Socket 的自定义二进制协议示例:

  • 服务端代码

<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '127.0.0.1', 8080);
socket_listen($socket, 10);

while (true) {
    $client_socket = socket_accept($socket);
    $data = socket_read($client_socket, 1024);
    // 解包
    $unpacked = unpack('vversion/vtype/Vtimestamp/Vlength', $data);
    $body = substr($data, 12, $unpacked['length']);
    echo "Received message: " . $body . "\n";
    // 回显消息
    socket_write($client_socket, $data);
    socket_close($client_socket);
}
socket_close($socket);
?>
  • 客户端代码

<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, '127.0.0.1', 8080);

$message = 'Hello, Server!';
// 打包
$version = 1;
$type = 1;
$timestamp = time();
$length = strlen($message);
$packed = pack('vV/VV', $version, $type, $timestamp, $length) . $message;
socket_write($socket, $packed, strlen($packed));

// 接收响应
$data = socket_read($socket, 1024);
echo "Received response: " . $data . "\n";
socket_close($socket);
?>

注意事项

  • 粘拆包问题 :由于 TCP 协议是面向流的,可能会出现粘包或拆包的情况。可以通过在协议中定义消息的长度字段来解决,接收方根据长度字段来判断消息的边界。

  • 字节序问题 :不同的系统可能采用不同的字节序,如 x86 系列CPU采用小端字节序,而网络协议中一般使用大端字节序。在打包和解包数据时,需要注意字节序的转换,以确保数据的正确性。

  • 协议的可扩展性 :在设计协议时,要考虑到未来的扩展需求,可以在头部预留一些字段,或者采用可变长度的字段等方式,以便在需要时可以方便地添加新的功能和信息。
     

服务端函数详解

  1. socket_create

    • 作用 :创建一个套接字。

    • 参数 :第一个参数 AF_INET 表示 IPv4 地址簇,SOCK_STREAM 表示面向连接的可靠的数据传输流,SOL_TCP 表示底层协议为 TCP。这三个参数组合后,告诉 PHP 我们要创建一个基于 TCP 的流式套接字。

    • 返回值 :成功时返回一个套接字的资源标识符,失败时返回 FALSE

    • 示例 :在服务端代码中,socket_create(AF_INET, SOCK_STREAM, SOL_TCP) 创建了一个可以用于监听客户端连接的套接字。

  2. socket_bind

    • 作用 :将套接字绑定到指定的地址和端口。

    • 参数 :第一个参数是之前通过 socket_create 创建的套接字资源标识符,第二个参数是绑定的 IP 地址,第三个参数是绑定的端口号

    • 返回值 :成功时返回 TRUE,失败时返回 FALSE

    • 示例socket_bind($socket, '127.0.0.1', 8080) 套接字绑定到本机的 8080 端口,这样客户端就可以通过这个地址端口来连接到服务端

  3. socket_listen

    • 作用 :将套接字置于监听状态,等待客户端的连接请求。

    • 参数 :第一个参数是套接字资源标识符,第二个参数是监听队列的长度,用于指定同时允许等待连接的最大客户端数量

    • 返回值 :成功时返回 TRUE,失败时返回 FALSE

    • 示例socket_listen($socket, 10) 将套接字设置为监听状态,并且指定同时最多允许 10 个客户端连接请求队列中等待

  4. socket_accept

    • 作用 :当客户端发起连接请求后,该函数接受连接请求并建立连接。

    • 参数 :唯一的参数是处于监听状态套接字资源标识符

    • 返回值 :成功时返回一个新的套接字资源标识符用于与客户端进行通信;失败时返回 FALSE

    • 示例$client_socket = socket_accept($socket) 接受客户端的连接请求,并返回一个新的套接字,通过这个套接字可以与客户端进行数据交互

  5. socket_read

    • 作用从套接字中读取数据

    • 参数 :第一个参数是套接字资源标识符,第二个参数是读取的最大字节数

    • 返回值 :成功时返回读取到的数据,失败时返回 FALSE。

    • 示例$data = socket_read($client_socket, 1024) 客户端套接字读取最多 1024 字节的数据。

  6. socket_write

    • 作用向套接字中写入数据

    • 参数 :第一个参数是套接字资源标识符,第二个参数是要写入的数据

    • 返回值 :成功时返回实际写入的字节数,失败时返回 FALSE

    • 示例socket_write($client_socket, $data) 将数据写入客户端套接字,用于向客户端发送数据

  7. socket_close

    • 作用关闭套接字资源

    • 参数 :唯一的参数是需要关闭的套接字资源标识符

    • 返回值没有返回值

    • 示例socket_close($client_socket) 关闭与客户端的连接套接字释放资源socket_close($socket) 关闭监听套接字,停止监听客户端连接

客户端函数详解

  1. socket_create

    • 作用 :与服务端相同,创建一个套接字。

    • 说明 :在客户端,同样是用于创建一个可以进行网络通信的套接字。

  2. socket_connect

    • 作用 :连接到服务器端的套接字。

    • 参数 :第一个参数是套接字资源标识符,第二个参数是服务器端的 IP 地址,第三个参数是服务器端的端口号

    • 返回值 :成功时返回 TRUE,失败时返回 FALSE。

    • 示例socket_connect($socket, '127.0.0.1', 8080) 连接到服务端的 8080 端口。

数据打包拆包函数详解

  1. pack

    • 作用 :将变量按照指定的格式打包成二进制字符串

    • 参数 :第一个参数是格式字符串,用于指定数据的打包方式,例如 v 表示无符号短整数(16 位)且字节顺序为机器字节序(通常是小端序),其他格式字符如 V 表示无符号长整数(32 位)同样是机器字节序。后面的参数是要打包的数据,按照格式字符串中指定的顺序提供。

    • 返回值 :返回打包后的二进制字符串。

    • 示例pack('vV/VV', $version, $type, $timestamp, $length) 将版本号、类型、时间戳和长度打包成二进制字符串。其中 v 表示将 version 按照 16 位无符号短整数进行打包,V 表示将 type 按照 32 位无符号长整数打包,后面的 VV 同理分别打包 timestamp 和 length。

  2. unpack

    • 作用 :将二进制字符串按照指定的格式解包成数组

    • 参数 :第一个参数是格式字符串,与 pack 函数的格式字符串类似,用于指定二进制字符串的解包方式。后面的参数是需要解包的二进制字符串

    • 返回值 :返回一个数组,数组的键由格式字符串中的键名(如果指定了)或者默认的类型名称加上数字后缀组成,值是解包后的变量值。

    • 示例unpack('vversion/vtype/Vtimestamp/Vlength', $data) 将接收到的二进制字符串按照规定的格式解包,其中 version 对应第一个 16 位无符号短整数,type 对应第二个 16 位无符号短整数,timestamp 对应第一个 32 位无符号长整数,length 对应第二个 32 位无符号长整数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值