我是靠谱客的博主 飘逸黑猫,这篇文章主要介绍HTTP协议详解 - 通过C++实现HTTP服务剖析HTTP协议,现在分享给大家,希望可以做个参考。

前言

  • C/C++程序员一般很少会接触到HTTP服务端的东西,所以对HTTP的理解一般停留在理论。 本文章实现通过C++实现了一个http服务,可以通过代码对HTTP协议有更深的理解,并且通过抓包工具对HTTP协议进行更为详细的分析。

HTTP协议简介

  • HTTP(hypertext transport protocol 超文本传输协议):一种无状态的,以请求/应答方式运行的协议,它使用可扩展的语义和自描述消息格式,与基于网络的超文本信息系统灵活的互动。

HTTP报文格式

  • 请求报文:由请求行,头部字段集合,消息正文三大部分组成。
    请添加图片描述

    • 请求行:描述请求的基本信息

      • 请求方法
        • 请求方法说明
          GET请求服务器发送某个资源
          POST用来传输实体的主体
          PUT用来传输文件
          HEAD获取报文首部,用于确认URI的有效性及资源更新的日期时间等
          DELETE删除文件
          OPTIONS查询针对请求URI指定的资源支持的方法
          TRACE用于追踪路径
          CONNECT要求与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信
      • URI:统一资源标识符(Uniform Resource Identifier)
      • HTTP版本
        • HTTP版本说明
          HTTP/0.91991年制定,只支持GET方法
          HTTP/1.01996年诞生,增加了POST,HEAD方法
          HTTP/1.11999年发布并成为标准,增加了PUT方法,并允许持久连接
    • 头部字段集合:使用key-value形式更详细地说明报文。主要分为四类:通用首部,请求首部,响应首部,实体首部

      • 通用首部:提供与报文相关的基本信息。既可以出现在请求报文中,也可以出现在响应报文中。
        • 首部字段名说明
          CacheControl控制缓存的行为
          Connection允许客户端和服务端指定与请求/响应连接有关的选项
          Date创建报文的日期时间
          Pragma另一种随报文传送指示的方式,但并不专用于缓存
          Transfer-Encoding告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
          Trailer报文末端的首部一览
          Update给出了发送端可能想要"升级"使用的新版本或协议
          Via显示了报文经过的中间节点(代理,网关)
      • 请求首部:只在请求报文中有意义的首部。用于说明是谁或什么在发送请求,请求源自何处,或者客户端的喜好和及能力
        • 首部字段名说明
          Accept告诉服务器能够发送哪些媒体类型
          Accept-Charset告诉服务器能够发送哪些字符集
          Accept-Encoding告诉服务器能够发送哪些编码方式
          Accept-Language告诉服务器能够发送哪些语言
          Authorization包含了客户端提供给服务器,以便对其自身进行认证的数据
          From提供了客户端用户的E-mail地址
          Host给出了接收请求的服务器的主机名和端口号
          If-Match如果实体标记与文档当前的实体标记相匹配,就获取这份文档
          If-Modified-Since除非在某个指定的日期之后资源被修改过,否则就限制这个请求
          If-None-Match如果提供的实体标记与当前文档的实体标记不相符,就获取文档
          If-Range允许对文档的某个范围进行条件请求
          If-Unmodified-Since除非在某个指定日期之后资源没有被修改过,否则就限制这个请求
          Max-Forward将请求转发给其他代理或网关的最大次数
          Proxy-Authorization与Authorization首部相同,但这个首部实在与代理进行认证时使用
          Range如果服务器支持范围请求,就请求资源的指定范围
          Referer提供包含当前请求URI的文档的URL
          TE告诉服务器可以使用哪些扩展传输编码
          User-Agent将发起请求的应用程序名称告知服务器
      • 实体首部:提供有关实体及其内容得到大量信息
        • 首部字段名说明
          Allow资源可支持的HTTP方法
          Content-Encoding实体主体适用的编码方式
          Content-Language实体主体的自然语言
          Content-Length实体主体的大小(单位:字节)
          Content-Location替代对应资源的URI
          Content-MD5实体主体的报文摘要
          Content-Range实体主体的位置范围
          Content-Type实体主体的媒体类型
          Expires实体主体过期的日期时间
          Last-Modified资源的最后修改日期
    • 消息正文:实际传输的数据,可以是纯文本,也可以是图片、视频等二进制数据

  • 响应报文:由响应行,头部字段集合,消息正文三大部分组成。
    请添加图片描述

    • 响应行:描述响应的基本信息
      • HTTP版本:上面已经介绍过了
      • 状态码:状态码的职责是当客户端向服务端发送请求时,描述返回的请求结果
        • 状态码类别原因短语
          1XX信息性状态码接收的请求正在处理
          2XX成功状态码请求正常处理完毕
          3XX重定向状态码需要进行附加操作以完成请求
          4XX客户端错误状态码服务端无法处理请求
          5XX服务端错误状态码服务器处理请求出错
        • 常用的错误码主要有14种
        • 错误码错误码描述详细描述
          200OK表示从客户端发来的请求在服务端被正常处理了
          204No Content无内容。服务器成功处理,但未返回内容
          206Partial Content部分内容。服务器成功处理了部分GET请求
          301Moved Permanently永久重定向,意思是本地请求的资源以及不存在,使用新的URI再次访问
          302Found临时重定向,临时则所请求的资源暂时还在,但是目前需要用另一个URI访问
          303See Other与301类似,使用GET和POST请求查看
          304Not Modified运用于缓存控制。它用于 If-Modified-Since 等条件请求,表示资源未修改,可以理解成"重定向已到缓存的文件"
          307Temporary Redirect临时重定向,与302类似,使用GET请求重定向
          400Bad Request客户端请求的语法错误,服务器无法理解
          401Unauthorized表示发送的请求需要有通过HTTP认证的认证信息
          403Forbidden这一个是表示服务器禁止访问资源。原因比如涉及到敏感词汇、法律禁止等
          404Not Found服务器无法根据客户端的请求找到资源
          500Internal Server Error服务器内部错误,无法完成请求
          503Service Unavailable表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的"网络服务正忙,请稍后重试"的提示信息就是状态码 503
      • 状态码描述:作为状态码补充,是更详细的解释文字,帮助理解原因
    • 头部字段合集:上面已经介绍过,这里只介绍下响应首部字段
      • 响应首部
        • 首部字段名说明
          Accept-Ranges对此资源来说,服务器可接受的范围类型
          Age响应持续时间
          ETag资源的匹配信息
          Location令客户端重定向至指定URI
          Proxy-Authenticate代码服务器对客户端的认证信息
          Retry-After如果资源不可用,在此日期或时间重试
          Server服务器应用程序软件的名称和版本
          Vary代理服务器缓存的管理信息
          WWW-Authenticate服务器对客户端的认证信息
    • 消息正文

C++实现http服务

  • 我参考TinyHttpd项目,使用C++实现了一个http服务,功能比较简单,目前只支持GET和POST请求。并且也只是对http请求报文进行了解析,然后进行简单回应,未实现其他功能。使用第三方json解析库json11对json报文体进行解析处理。
  • 项目源代码可以从这里下载:项目地址
  • 主要代码
    • 复制代码
      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
      140
      #include "httpd.h" void threadFunc(void* arg, int conn){ Httpd* httpd = (Httpd*)arg; // 接收http请求 char bodyBuf[1024] = {0}; int recvSize = recv(conn, bodyBuf, sizeof(bodyBuf), 0); printf("%sn", bodyBuf); std::string strMethod; std::string strUri; std::string strVersion; std::map<std::string, std::string> requestHead; std::string requestBody; // 解析http请求,包括请求方式(目前只支持GET和POST请求),URI,http版本 httpd->parseHttpRequestInfo(bodyBuf, strMethod, strUri, strVersion); // 解析http请求头 httpd->parseHttpRequestHead(bodyBuf, requestHead); //解析http请求体 httpd->parseHttpRequestBody(bodyBuf, requestBody); //根据不同请求方式进行响应 if(strMethod.compare("GET") == 0){ httpd->httpResponseHtml(conn); }else if(strMethod.compare("POST") == 0){ std::string data1; std::string data2; if(httpd->parseBodyJson(requestBody, data1, data2)){ httpd->httpResponseJson(conn, data1, data2); } } //关闭套接字 close(conn); } bool Httpd::start(){ //定义sockfd int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(4000); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //bind,成功返回0,出错返回-1 if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){ perror("bind"); return false; } //listen,成功返回0,出错返回-1 if(listen(server_sockfd, 5) == -1){ perror("listen"); return false; } //客户端套接字 char buffer[1024] = {0}; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); int conn = 0; while(1){ //成功返回非负描述字,出错返回-1 conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length); if(conn < 0){ perror("connect"); return false; } //开启线程处理请求 std::thread th; th = std::thread(threadFunc, this, conn); th.join(); } close(server_sockfd); return true; } bool Httpd::parseHttpRequestInfo(std::string httpRequest, std::string& method, std::string& uri, std::string& version){ int recvSize = httpRequest.size(); //查找请求头 std::string strRequestHead; int pos = httpRequest.find("rn"); strRequestHead = httpRequest.substr(0, pos); //解析请求类型 method = strRequestHead.substr(0, strRequestHead.find(" ")); //解析uri uri = strRequestHead.substr(strRequestHead.find(" ") + 1, strRequestHead.find(" ", strRequestHead.find(" ") + 1) - strRequestHead.find(" ")); //解析http版本 version = strRequestHead.substr(strRequestHead.rfind(" "), strRequestHead.size() - strRequestHead.rfind(" ")); return true; } bool Httpd::parseHttpRequestHead(std::string httpRequest, std::map<std::string, std::string>& requestHead){ int recvSize = httpRequest.size(); int headPos = httpRequest.find("rn"); int bodySize = parseBodySize(httpRequest); std::string strRequestH; do{ int iPos = httpRequest.find("rn", headPos + strlen("rn")); strRequestH = httpRequest.substr(headPos, iPos - headPos); if(strRequestH.find(":") != std::string::npos){ std::string strKey = strRequestH.substr(0, strRequestH.find(":")); std::string strValue = strRequestH.substr(strRequestH.find(":") + 1, strRequestH.size() - strRequestH.find(":")); requestHead.insert(std::pair<std::string, std::string>(strKey, strValue)); } headPos = iPos; } while(headPos < recvSize - bodySize && headPos > 0); return true; } bool Httpd::parseHttpRequestBody(std::string httpRequest, std::string& requestBody){ int recvSize = httpRequest.size(); int bodySize = parseBodySize(httpRequest); if(bodySize == 0){ return false; } requestBody = httpRequest.substr(recvSize - bodySize, bodySize); return true; } int Httpd::parseBodySize(std::string httpRequest){ std::string strContentLength; int posLengthStart = httpRequest.find("Content-Length: ") + strlen("Content-Length: "); int posLengthEnd = httpRequest.find("rn", httpRequest.find("Content-Length: ") + strlen("Content-Length: ")); strContentLength = httpRequest.substr(posLengthStart, posLengthEnd - posLengthStart); return atoi(strContentLength.c_str()); }
  • 通过代码我们可以看到,其实底层还是TCP编程,只不过TCP通信时,我们是直接拿数据,不用遵守什么规则。但如果要进行HTTP通信,就要遵守人家的规则,按照请求报文的格式去进行解析,才能拿到服务端想要的信息,然后再根据响应报文去组装数据,返回给客户端。

演示

  • POST请求演示,我通过postman演示下post请求,目前实现的功能是将请求数据拼接后返回。
    • postman界面演示
      在这里插入图片描述
    • 服务端打印
      请添加图片描述
  • GET请求演示,直接在浏览器中访问,返回一个html格式的页面
    • 浏览器页面
      在这里插入图片描述
    • 服务端打印
      在这里插入图片描述

抓包分析

  • 下面我们通过wireShark工具抓包分析下http协议的通信过程,发送一个post请求。
    在这里插入图片描述
  • 通过抓包可以看到,在http通信前,先要通过TCP三次握手建立连接,并且一次请求结束后,进行TCP四次挥手断开连接(http协议目前是支持长连接的,也就是建立连接后,可以发送多个http请求,我这里为了分析方便,在发送一次http请求后就关闭了套接字)。
  • 先看下前三行,是TCP建立连接的过程,我在使用wireShark抓包分析TCP协议进行了详细介绍,这里就不再过多阐述了。
  • 第四行开始是http通信,可以看到http请求的所有信息
    在这里插入图片描述
  • 再看第七行,是http服务的响应
    在这里插入图片描述
  • 后面是TCP四次挥手过程,这里也不过多阐述了。

最后

以上就是飘逸黑猫最近收集整理的关于HTTP协议详解 - 通过C++实现HTTP服务剖析HTTP协议的全部内容,更多相关HTTP协议详解内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(98)

评论列表共有 0 条评论

立即
投稿
返回
顶部