一些例子

例子一

协议定义

  • 首部固定10个字节长度用来保存整个数据包长度,位数不够补0
  • 数据格式为xml

数据包样本

  1. 0000000121<?xml version="1.0" encoding="ISO-8859-1"?>
  2. <request>
  3. <module>user</module>
  4. <action>getInfo</action>
  5. </request>

其中0000000121代表整个数据包长度,后面紧跟xml数据格式的包体内容

协议实现

  1. namespace Protocols;
  2. class XmlProtocol
  3. {
  4. public static function input($recv_buffer)
  5. {
  6. if(strlen($recv_buffer) < 10)
  7. {
  8. // 不够10字节,返回0继续等待数据
  9. return 0;
  10. }
  11. // 返回包长,包长包含 头部数据长度+包体长度
  12. $total_len = base_convert(substr($recv_buffer, 0, 10), 10, 10);
  13. return $total_len;
  14. }
  15. public static function decode($recv_buffer)
  16. {
  17. // 请求包体
  18. $body = substr($recv_buffer, 10);
  19. return simplexml_load_string($body);
  20. }
  21. public static function encode($xml_string)
  22. {
  23. // 包体+包头的长度
  24. $total_length = strlen($xml_string)+10;
  25. // 长度部分凑足10字节,位数不够补0
  26. $total_length_str = str_pad($total_length, 10, '0', STR_PAD_LEFT);
  27. // 返回数据
  28. return $total_length_str . $xml_string;
  29. }
  30. }

例子二

协议定义

  • 首部4字节网络字节序unsigned int,标记整个包的长度
  • 数据部分为Json字符串

数据包样本

  1. ****{"type":"message","content":"hello all"}

其中首部四字节*号代表一个网络字节序的unsigned int数据,为不可见字符,紧接着是Json的数据格式的包体数据

协议实现

  1. namespace Protocols;
  2. class JsonInt
  3. {
  4. public static function input($recv_buffer)
  5. {
  6. // 接收到的数据还不够4字节,无法得知包的长度,返回0继续等待数据
  7. if(strlen($recv_buffer)<4)
  8. {
  9. return 0;
  10. }
  11. // 利用unpack函数将首部4字节转换成数字,首部4字节即为整个数据包长度
  12. $unpack_data = unpack('Ntotal_length', $recv_buffer);
  13. return $unpack_data['total_length'];
  14. }
  15. public static function decode($recv_buffer)
  16. {
  17. // 去掉首部4字节,得到包体Json数据
  18. $body_json_str = substr($recv_buffer, 4);
  19. // json解码
  20. return json_decode($body_json_str, true);
  21. }
  22. public static function encode($data)
  23. {
  24. // Json编码得到包体
  25. $body_json_str = json_encode($data);
  26. // 计算整个包的长度,首部4字节+包体字节数
  27. $total_length = 4 + strlen($body_json_str);
  28. // 返回打包的数据
  29. return pack('N',$total_length) . $body_json_str;
  30. }
  31. }

例子三(使用二进制协议上传文件)

协议定义

  1. struct
  2. {
  3. unsigned int total_len; // 整个包的长度,大端网络字节序
  4. char name_len; // 文件名的长度
  5. char name[name_len]; // 文件名
  6. char file[total_len - BinaryTransfer::PACKAGE_HEAD_LEN - name_len]; // 文件数据
  7. }

协议样本

  1. *****logo.png******************

其中首部四字节*号代表一个网络字节序的unsigned int数据,为不可见字符,第5个*是用一个字节存储文件名长度,紧接着是文件名,接着是原始的二进制文件数据

协议实现

  1. namespace Protocols;
  2. class BinaryTransfer
  3. {
  4. // 协议头长度
  5. const PACKAGE_HEAD_LEN = 5;
  6. public static function input($recv_buffer)
  7. {
  8. // 如果不够一个协议头的长度,则继续等待
  9. if(strlen($recv_buffer) < self::PACKAGE_HEAD_LEN)
  10. {
  11. return 0;
  12. }
  13. // 解包
  14. $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
  15. // 返回包长
  16. return $package_data['total_len'];
  17. }
  18. public static function decode($recv_buffer)
  19. {
  20. // 解包
  21. $package_data = unpack('Ntotal_len/Cname_len', $recv_buffer);
  22. // 文件名长度
  23. $name_len = $package_data['name_len'];
  24. // 从数据流中截取出文件名
  25. $file_name = substr($recv_buffer, self::PACKAGE_HEAD_LEN, $name_len);
  26. // 从数据流中截取出文件二进制数据
  27. $file_data = substr($recv_buffer, self::PACKAGE_HEAD_LEN + $name_len);
  28. return array(
  29. 'file_name' => $file_name,
  30. 'file_data' => $file_data,
  31. );
  32. }
  33. public static function encode($data)
  34. {
  35. // 可以根据自己的需要编码发送给客户端的数据,这里只是当做文本原样返回
  36. return $data;
  37. }
  38. }

服务端协议使用示例

  1. use Workerman\Worker;
  2. require_once '/your/path/Workerman/Autoloader.php';
  3. $worker = new Worker('BinaryTransfer://0.0.0.0:8333');
  4. // 保存文件到tmp下
  5. $worker->onMessage = function($connection, $data)
  6. {
  7. $save_path = '/tmp/'.$data['file_name'];
  8. file_put_contents($save_path, $data['file_data']);
  9. $connection->send("upload success. save path $save_path");
  10. };
  11. Worker::runAll();

客户端文件 client.php (这里用php模拟客户端上传)

  1. <?php
  2. /** 上传文件客户端 **/
  3. // 上传地址
  4. $address = "127.0.0.1:8333";
  5. // 检查上传文件路径参数
  6. if(!isset($argv[1]))
  7. {
  8. exit("use php client.php \$file_path\n");
  9. }
  10. // 上传文件路径
  11. $file_to_transfer = trim($argv[1]);
  12. // 上传的文件本地不存在
  13. if(!is_file($file_to_transfer))
  14. {
  15. exit("$file_to_transfer not exist\n");
  16. }
  17. // 建立socket连接
  18. $client = stream_socket_client($address, $errno, $errmsg);
  19. if(!$client)
  20. {
  21. exit("$errmsg\n");
  22. }
  23. // 设置成阻塞
  24. stream_set_blocking($client, 1);
  25. // 文件名
  26. $file_name = basename($file_to_transfer);
  27. // 文件名长度
  28. $name_len = strlen($file_name);
  29. // 文件二进制数据
  30. $file_data = file_get_contents($file_to_transfer);
  31. // 协议头长度 4字节包长+1字节文件名长度
  32. $PACKAGE_HEAD_LEN = 5;
  33. // 协议包
  34. $package = pack('NC', $PACKAGE_HEAD_LEN + strlen($file_name) + strlen($file_data), $name_len) . $file_name . $file_data;
  35. // 执行上传
  36. fwrite($client, $package);
  37. // 打印结果
  38. echo fread($client, 8192),"\n";

客户端使用示例

命令行中运行 php client.php <文件路径>

例如 php client.php abc.jpg

例子四(使用文本协议上传文件)

协议定义

json+换行,json中包含了文件名以及base64_encode编码(会增大1/3的体积)的文件数据

协议样本

{“file_name”:”logo.png”,”file_data”:”PD9waHAKLyo……”}\n

注意末尾为一个换行符,在PHP中用双引号字符"\n"标识

协议实现

  1. namespace Protocols;
  2. class TextTransfer
  3. {
  4. public static function input($recv_buffer)
  5. {
  6. $recv_len = strlen($recv_buffer);
  7. if($recv_buffer[$recv_len-1] !== "\n")
  8. {
  9. return 0;
  10. }
  11. return strlen($recv_buffer);
  12. }
  13. public static function decode($recv_buffer)
  14. {
  15. // 解包
  16. $package_data = json_decode(trim($recv_buffer), true);
  17. // 取出文件名
  18. $file_name = $package_data['file_name'];
  19. // 取出base64_encode后的文件数据
  20. $file_data = $package_data['file_data'];
  21. // base64_decode还原回原来的二进制文件数据
  22. $file_data = base64_decode($file_data);
  23. // 返回数据
  24. return array(
  25. 'file_name' => $file_name,
  26. 'file_data' => $file_data,
  27. );
  28. }
  29. public static function encode($data)
  30. {
  31. // 可以根据自己的需要编码发送给客户端的数据,这里只是当做文本原样返回
  32. return $data;
  33. }
  34. }

服务端协议使用示例

说明:写法与二进制上传写法一样,即能做到几乎不用改动任何业务代码便可以切换协议

  1. use Workerman\Worker;
  2. require_once '/your/path/Workerman/Autoloader.php';
  3. $worker = new Worker('TextTransfer://0.0.0.0:8333');
  4. // 保存文件到tmp下
  5. $worker->onMessage = function($connection, $data)
  6. {
  7. $save_path = '/tmp/'.$data['file_name'];
  8. file_put_contents($save_path, $data['file_data']);
  9. $connection->send("upload success. save path $save_path");
  10. };
  11. Worker::runAll();

客户端文件 textclient.php (这里用php模拟客户端上传)

  1. <?php
  2. /** 上传文件客户端 **/
  3. // 上传地址
  4. $address = "127.0.0.1:8333";
  5. // 检查上传文件路径参数
  6. if(!isset($argv[1]))
  7. {
  8. exit("use php client.php \$file_path\n");
  9. }
  10. // 上传文件路径
  11. $file_to_transfer = trim($argv[1]);
  12. // 上传的文件本地不存在
  13. if(!is_file($file_to_transfer))
  14. {
  15. exit("$file_to_transfer not exist\n");
  16. }
  17. // 建立socket连接
  18. $client = stream_socket_client($address, $errno, $errmsg);
  19. if(!$client)
  20. {
  21. exit("$errmsg\n");
  22. }
  23. stream_set_blocking($client, 1);
  24. // 文件名
  25. $file_name = basename($file_to_transfer);
  26. // 文件二进制数据
  27. $file_data = file_get_contents($file_to_transfer);
  28. // base64编码
  29. $file_data = base64_encode($file_data);
  30. // 数据包
  31. $package_data = array(
  32. 'file_name' => $file_name,
  33. 'file_data' => $file_data,
  34. );
  35. // 协议包 json+回车
  36. $package = json_encode($package_data)."\n";
  37. // 执行上传
  38. fwrite($client, $package);
  39. // 打印结果
  40. echo fread($client, 8192),"\n";

客户端使用示例

命令行中运行 php textclient.php <文件路径>

例如 php textclient.php abc.jpg