协议设计
-
数据格式选择 :二进制格式适用于高效传输大量数据的场景,相比于 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采用小端字节序,而网络协议中一般使用大端字节序。在打包和解包数据时,需要注意字节序的转换,以确保数据的正确性。
-
协议的可扩展性 :在设计协议时,要考虑到未来的扩展需求,可以在头部预留一些字段,或者采用可变长度的字段等方式,以便在需要时可以方便地添加新的功能和信息。
服务端函数详解
-
socket_create
-
作用 :创建一个套接字。
-
参数 :第一个参数 AF_INET 表示 IPv4 地址簇,SOCK_STREAM 表示面向连接的可靠的数据传输流,SOL_TCP 表示底层协议为 TCP。这三个参数组合后,告诉 PHP 我们要创建一个基于 TCP 的流式套接字。
-
返回值 :成功时返回一个套接字的资源标识符,失败时返回 FALSE。
-
示例 :在服务端代码中,
socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
创建了一个可以用于监听客户端连接的套接字。
-
-
socket_bind
-
作用 :将套接字绑定到指定的地址和端口。
-
参数 :第一个参数是之前通过 socket_create 创建的套接字资源标识符,第二个参数是绑定的 IP 地址,第三个参数是绑定的端口号。
-
返回值 :成功时返回 TRUE,失败时返回 FALSE。
-
示例 :
socket_bind($socket, '127.0.0.1', 8080)
将套接字绑定到本机的 8080 端口,这样客户端就可以通过这个地址和端口来连接到服务端。
-
-
socket_listen
-
作用 :将套接字置于监听状态,等待客户端的连接请求。
-
参数 :第一个参数是套接字资源标识符,第二个参数是监听队列的长度,用于指定同时允许等待连接的最大客户端数量。
-
返回值 :成功时返回 TRUE,失败时返回 FALSE。
-
示例 :
socket_listen($socket, 10)
将套接字设置为监听状态,并且指定同时最多允许 10 个客户端连接请求在队列中等待。
-
-
socket_accept
-
作用 :当客户端发起连接请求后,该函数接受连接请求并建立连接。
-
参数 :唯一的参数是处于监听状态的套接字资源标识符。
-
返回值 :成功时返回一个新的套接字资源标识符,用于与客户端进行通信;失败时返回 FALSE。
-
示例 :
$client_socket = socket_accept($socket)
接受客户端的连接请求,并返回一个新的套接字,通过这个套接字可以与客户端进行数据交互。
-
-
socket_read
-
作用 :从套接字中读取数据。
-
参数 :第一个参数是套接字资源标识符,第二个参数是读取的最大字节数。
-
返回值 :成功时返回读取到的数据,失败时返回 FALSE。
-
示例 :
$data = socket_read($client_socket, 1024)
从客户端套接字中读取最多 1024 字节的数据。
-
-
socket_write
-
作用 :向套接字中写入数据。
-
参数 :第一个参数是套接字资源标识符,第二个参数是要写入的数据。
-
返回值 :成功时返回实际写入的字节数,失败时返回 FALSE。
-
示例 :
socket_write($client_socket, $data)
将数据写入客户端套接字,用于向客户端发送数据。
-
-
socket_close
-
作用 :关闭套接字资源。
-
参数 :唯一的参数是需要关闭的套接字资源标识符。
-
返回值 :没有返回值。
-
示例 :
socket_close($client_socket)
关闭与客户端的连接套接字,释放资源;socket_close($socket)
关闭监听套接字,停止监听客户端连接。
-
客户端函数详解
-
socket_create
-
作用 :与服务端相同,创建一个套接字。
-
说明 :在客户端,同样是用于创建一个可以进行网络通信的套接字。
-
-
socket_connect
-
作用 :连接到服务器端的套接字。
-
参数 :第一个参数是套接字资源标识符,第二个参数是服务器端的 IP 地址,第三个参数是服务器端的端口号。
-
返回值 :成功时返回 TRUE,失败时返回 FALSE。
-
示例 :
socket_connect($socket, '127.0.0.1', 8080)
连接到服务端的 8080 端口。
-
数据打包拆包函数详解
-
pack
-
作用 :将变量按照指定的格式打包成二进制字符串。
-
参数 :第一个参数是格式字符串,用于指定数据的打包方式,例如 v 表示无符号短整数(16 位)且字节顺序为机器字节序(通常是小端序),其他格式字符如 V 表示无符号长整数(32 位)同样是机器字节序。后面的参数是要打包的数据,按照格式字符串中指定的顺序提供。
-
返回值 :返回打包后的二进制字符串。
-
示例 :
pack('vV/VV', $version, $type, $timestamp, $length)
将版本号、类型、时间戳和长度打包成二进制字符串。其中 v 表示将 version 按照 16 位无符号短整数进行打包,V 表示将 type 按照 32 位无符号长整数打包,后面的 VV 同理分别打包 timestamp 和 length。
-
-
unpack
-
作用 :将二进制字符串按照指定的格式解包成数组。
-
参数 :第一个参数是格式字符串,与 pack 函数的格式字符串类似,用于指定二进制字符串的解包方式。后面的参数是需要解包的二进制字符串。
-
返回值 :返回一个数组,数组的键由格式字符串中的键名(如果指定了)或者默认的类型名称加上数字后缀组成,值是解包后的变量值。
-
示例 :
unpack('vversion/vtype/Vtimestamp/Vlength', $data)
将接收到的二进制字符串按照规定的格式解包,其中 version 对应第一个 16 位无符号短整数,type 对应第二个 16 位无符号短整数,timestamp 对应第一个 32 位无符号长整数,length 对应第二个 32 位无符号长整数。
-