添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • 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.9 1991年制定,只支持GET方法
    HTTP/1.0 1996年诞生,增加了POST,HEAD方法
    HTTP/1.1 1999年发布并成为标准,增加了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种
      • 错误码 错误码描述 详细描述
        200 OK 表示从客户端发来的请求在服务端被正常处理了
        204 No Content 无内容。服务器成功处理,但未返回内容
        206 Partial Content 部分内容。服务器成功处理了部分GET请求
        301 Moved Permanently 永久重定向,意思是本地请求的资源以及不存在,使用新的URI再次访问
        302 Found 临时重定向,临时则所请求的资源暂时还在,但是目前需要用另一个URI访问
        303 See Other 与301类似,使用GET和POST请求查看
        304 Not Modified 运用于缓存控制。它用于 If-Modified-Since 等条件请求,表示资源未修改,可以理解成"重定向已到缓存的文件"
        307 Temporary Redirect 临时重定向,与302类似,使用GET请求重定向
        400 Bad Request 客户端请求的语法错误,服务器无法理解
        401 Unauthorized 表示发送的请求需要有通过HTTP认证的认证信息
        403 Forbidden 这一个是表示服务器禁止访问资源。原因比如涉及到敏感词汇、法律禁止等
        404 Not Found 服务器无法根据客户端的请求找到资源
        500 Internal Server Error 服务器内部错误,无法完成请求
        503 Service 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报文体进行解析处理。
    • 项目源代码可以从这里下载: 项目地址
    • 主要代码
      •   #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("%s\n", 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("\r\n");
          	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("\r\n");
          	int bodySize = parseBodySize(httpRequest);
          	std::string strRequestH;
          		int iPos = httpRequest.find("\r\n", headPos + strlen("\r\n"));
          		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("\r\n", 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四次挥手过程,这里也不过多阐述了。
    Workflow是一个高性能的 C++ 异步 网络 框架,专为大型复杂业务设计。本文档将重点介绍如何使用Workflow框架构建 HTTP 服务 器,包括处理请求、设置响应以及 实现 常见Web功能。在开始使用Workflow框架前,确保系统已安装以下依赖: 安装Workflow 安装wfrest (Workflow的 HTTP 封装库) 基本 服务 器结构 一个基本的 HTTP 服务 器结构如下: HTTP 请求处理 Workflow框架支持所有标准 HTTP 方法: 请求参数获取 HTTP 响应设置 设置响应内容 设置响应头
    HTTP (超文本传输 协议 )是一种客户端与 服务 端的传输 协议 ,最早用于浏览器和 服务 器之间的通信,后来因为其使用灵活、方便等特点,广泛用于客户端与 服务 端的通信。文章将简单介绍 HTTP 协议 ,同时以 C++ 方式分别 实现 HTTP GET、POST 请求 HTTP 请求报文 HTTP 请求报文的一般格式由4部分组成:请求行、请求头部、空行、请求数据。如...