首先介绍一些必要的知识点。

TCP协议为操作系统底层协议,能够保证应用层获取到完整的、顺序一直的包序列。但TCP不提供具体的分包,需要上层协议自己解决。TCP发送给上层协议的数据是一个没有意义的字符串序列。如何解释这段序列,需要应用层定义,也就是应用层协议规范的内容。

应用层协议按格式一般可以分为文本协议和二进制协议。文本协议最常见的就是HTTP,二进制协议如websocket。无论是哪种协议,都需要对格式严格定义,以方便程序对字符串序列进行分包、拆包。

HTTP协议通过两种方式定义协议帧(一个HTTP请求或一个HTTP响应)结束标志。第一种是在http header中使用Content-Length头给出body长度,header与body使用\r\n\r\n分隔,这样我们就能够确定一个HTTP帧的开始与结束。另外一种是chunked编码的http帧,通过在header中使用Transfer-Encoding:chunked标志声明该编码方式,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。在一些实现中,块大小和CRLF之间填充有白空格(0x20)。最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。消息最后以CRLF结尾。

格式如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 25 This is the data in the first chunk 1C and this is the second one 3 con 8 sequence 0

为简单起见,这里对HTTP服务端协议的解析,只考以Content-Length方式分帧的http帧,不考虑chunked编码的http协议解析。

流程图如下:

http协议解析流程图

如上图所示,TCP提供的是字节流,所以会出现如下三种种情况:

  1. 缺包:当前接收到的字符不够一个完整的HTTP帧
  2. 粘包:当前接收到的字符串包含一个以上的HTTP帧
  3. 当前接收到的字符串正好是一个HTTP帧

针对以上三种情况,分别要做处理,出现粘包和缺包时,我们需要缓存尚未处理的部分留待下次接收到数据时一并处理。

根据如上流程图实现的PHP程序如下:

代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

<?php /** * Created by PhpStorm. * User: Jenner * Date: 2015/8/10 * Time: 14:00 * * 简单http server端协议解析,实现粘包、缺包,拆包 */ $server = new Server(); //注册响应回调函数 $server->registerHandler(function($connection, $header, $body){ echo "get request: " . time() . PHP_EOL; echo "header: " . PHP_EOL . $header . PHP_EOL; echo "body: " . PHP_EOL . $body . PHP_EOL; $response = "HTTP/1.1 200 OK\r\n"; $response .= "Date: Mon, 10 Aug 2015 06:22:08 GMT\r\n"; $response .= "Content-Type: text/html;charset=utf-8\r\n\r\n"; socket_write($connection, $response, strlen($response)); }); //启动server $server->start(); /** * 简易http server 支持http协议解析 * Class Server */ class Server { /** * @var string 字符流缓存 */ protected $cache = ""; /** * @var http请求处理器 */ protected $handler; /** * 启动server */ public function start() { $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_bind($socket, '0.0.0.0', 1212); socket_listen($socket); while ($connection = socket_accept($socket)) { echo "connected" . PHP_EOL; while(true){ $bytes = socket_read($connection, 1); echo "read: " . $bytes . PHP_EOL; if(empty($bytes)) { sleep(1); continue; } if($this->parse($connection, $bytes)){ break; } echo "parse" . PHP_EOL; usleep(100); } } } /** * 注册处理器 * @param $handler */ public function registerHandler($handler) { $this->handler = $handler; } /** * http协议解析 * @param $connection * @param $data * @return bool */ protected function parse($connection, $data) { $data = $this->cache . $data; var_dump($data); $header = $body = ""; if (strstr($data, "\r\n\r\n") === false) { $this->cache = $data; echo "cached" . PHP_EOL; return false; } $http_info = explode("\r\n\r\n", $data, 2); $header = $http_info[0]; $body = count($http_info) > 1 ? $http_info[1] : 0; $content_length = $this->getContentLength($header); if ($content_length == 0) { // 正好是一个http帧 call_user_func($this->handler, $connection, $header, null); socket_close($connection); return true; } elseif($content_length > strlen($body)){ // 缺少body部分 $this->cache = $data; return false; } else { // 发生粘包,摘出当前包,缓存剩余部分 $body = substr($body, 0, $content_length); $this->cache = substr($body, $content_length); call_user_func($this->handler, $connection, $header, $body); return false; } } /** * 获取content-length * @param $headers * @return int */ protected function getContentLength($headers) { $headers = explode("\r\n", $headers); foreach ($headers as $header) { if (stristr("content-length", $headers) === false) continue; $content_length = intval(explode(":", $header, 2)[1]); return $content_length; } return 0; } }

客户端代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

<?php /** * Created by PhpStorm. * User: Jenner * Date: 2015/8/10 * Time: 14:19 * * 简单http socket客户端,实现一次HTTP请求 */ $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if(socket_connect($socket, "127.0.0.1", 1212) === false){ echo "ERROR:" . socket_strerror(socket_last_error($socket)) . PHP_EOL; exit; } $request_headers = array( "GET / HTTP/1.1", "Host: xxx.xxx", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", ); $request = implode("\r\n", $request_headers); // if comment the follow line, you will read nothing. because the request format is error. $request .= "\r\n\r\n"; var_dump($request); if(socket_write($socket, $request, strlen($request)) === false){ echo "ERROR:" . socket_strerror(socket_last_error($socket)) . PHP_EOL; exit; } echo "the server will not response until time is out" . PHP_EOL; $response = socket_read($socket, 1024); if($response === false || empty($response)){ echo "ERROR: timeout or socket error" . PHP_EOL; exit; } echo "response:" . PHP_EOL; var_dump($response); socket_close($socket);

简易HTTP协议解析相关推荐

  1. synopsys PCIE IP协议解析

    synopsys PCIE IP协议解析 1.Overview Core支持单个Pcie内核的Loopback功能,该功能主要为了做芯片验证,以及在没有远程接收器件的情况下完成自己的回环.同时,Cor ...

  2. 详解BLE 空中包格式—兼BLE Link layer协议解析

    BLE有几种空中包格式?常见的PDU命令有哪些?PDU和MTU的区别是什么?DLE又是什么?BLE怎么实现重传的?BLE ACK机制原理是什么?希望这篇文章能帮你回答以上问题. 虽然BLE空中包(pa ...

  3. wireshark协议解析器 源码分析 封装调用

    源码分析 Wireshark启动时,所有解析器进行初始化和注册.要注册的信息包括协议名称.各个字段的信息.过滤用的关键字.要关联的下层协议与端口(handoff)等.在解析过程,每个解析器负责解析自己 ...

  4. wireshark协议解析器原理与插件编写

    工作原理 每个解析器解码自己的协议部分, 然后把封装协议的解码传递给后续协议. 因此它可能总是从一个Frame解析器开始, Frame解析器解析捕获文件自己的数据包细节(如:时间戳), 将数据交给一个 ...

  5. SGS 0.9.7 协议解析

    SGS as客户端发送数据规则: sgs 是0.9.7版 协议规则:  包头3个字节: 包大小(2个字节),命令(1个字节)  数据包  注意:包大小=命令+ 数据包 的大小 登陆命令:   命令是: ...

  6. 视音频数据处理入门:UDP-RTP协议解析

    ===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...

  7. 公网传输技术之SRT协议解析(上)

     点击上方"LiveVideoStack"关注我们 作者:张博力 编辑:Alex ▼扫描下图二维码或点击阅读原文▼ 了解音视频技术大会更多信息 " 摘  要:SRT协议( ...

  8. 修改wireshark协议解析规则

    不同的协议有不同的解码器,wireshark尝试为每个包尝试找到正确的解码器,特定的情况有可能会选择错误的解码器. 1.使用了其它协议的标准端口,被错误解码,使用udp的80端口发送数据被当作QUIC ...

  9. 计算机网络实验arp协议分析,计算机网络ARP地址协议解析实验报告

    计算机网络ARP地址协议解析实验报告 (5页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 计算机网络实验报告.实验目的:1. 掌握ARP协议 ...

最新文章

  1. python字典的键可以用列表吗_python字典多键值及重复键值的使用方法(详解)
  2. doc es 中type_Elasticsearch(024):es常见的字段映射类型之 连接类型(join type)
  3. Ubuntu下安装pip
  4. 优化案例(part1)--Efficient multi-modal geometric mean metric learning
  5. C语言试题六十五之请编写函数实现猴子吃桃问题
  6. 基于visual Studio2013解决C语言竞赛题之0705矩阵转置
  7. API的String 一些用法
  8. Spark 基础 —— RDD(创建 RDD)的两种方式
  9. 梦想cad控件 android,梦想CAD控件 2018.7.26更新
  10. 使用jquery做一个动态简历
  11. 使用arduino作为programer对新的mcu烧录bootloader
  12. Redis和Memcached区别
  13. 小型微型计算机投稿流程,小型微型计算机系统
  14. Android 商品详情页
  15. matlab目标跟踪目标检测项目系统源码合集【33套】
  16. PPT打开显示找不到 ppcore.dll,无法打开 PPT
  17. 【JY】流体力学之牛顿流体和非牛顿流体
  18. Daily Practice 5th:Educational Codeforces Round 120 (Rated for Div. 2)
  19. Ubuntu快捷键——终端
  20. Erlang词法分析器、语法分析器(lexer-leex,yac-yecc)

热门文章

  1. 残差网络ResNet笔记
  2. C语言之获取类型存储空间
  3. Opencv判断是否加载图片的两种方法
  4. caffe使用过程+digits在windows下的安装和运行
  5. .htaccess FollowSymlinks影响rewrite功能
  6. android view知识点 总结
  7. 4、Angular JS 学习笔记 – 创建自定义指令 [翻译中]
  8. 局域网(LocalAreaNetwork;LAN)
  9. Namespace declaration statement has to be the very first statement in the script
  10. 使用 soapUI 测试 REST 服务