前面我们实现了TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用协议,那就是HTTP超文本传输协议

1HTTP协议简介

  超文本传输协议(Hyper Text Transfer Protocol),简称HTTP,是一种基于TCP的应用层协议,也是目前为止最为流行的应用层协议之一,可以说HTTP协议是万维网的基石。

  HTTP是一种客户端请求、服务器应答式的应用层传输协议,也就是说服务器端是不可能主动向客户端发送数据的。在网络正常的情况下请求和响应都是一一对应的。而这个请求和响应也就是后端开发人员经常看到的Request和Response。

  首先,我们来看客户器端的请求,HTTP请求报文由请求行、请求头、空白行以及请求体组成。其报文格式如下:

  我们来说一说请求行,它由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。需要理解的是请求方法,HTTP协议的请求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT几种。先对常用的几种说明如下:

  • GET方法,意思是获取URL指定的资源,这个请求方式是最简单的也是最常用的。使用GET 方法时,可以将请求参数和对应的值附加在 URI 后面,利用一个问号(“?”)将资源的URI和请求参数隔开,参数之间使用与符号(“&”)隔开,因此传递参数长度也受到了限制,而且与隐私相关的信息也直接暴露在URI中。比如/index.jsp?username=holmofy&password=123123
  • HEAD方法,与GET用法相同,但没有响应体,使用场合没有GET多。比如下载前使用HEAD发送请求,通过ContentLength响应字段,来了解网络资源的大小;或者通过LastModified响应字段来判断本地缓存资源是否要更新。
  • POST方法,一般用提交信息或数据,请求服务器进行处理(例如提交表单或者上传文件)。表单使用POST相对GET来说还是比较隐秘的,而且GET的URL有长度限制,而上传大文件就必须要使用POST了。
  • OPTIONS方法,该方法用于请求服务器告知其支持哪些其他的功能和方法。通过OPTIONS 方法,可以询问服务器具体支持哪些方法,或者服务器会使用什么样的方法来处理一些特殊资源。可以说这是一个探测性的方法,客户端通过该方法可以在不访问服务器上实际资源的情况下就知道处理该资源的最优方式。这个选项在跨域HTTP请求的情况出现的比较多,这里有一片关于跨域请求的文章,其中有一张图很好的解释了什么是跨域HTTP请求。

  客户端发出HTTP请求,服务端接收后,会向客户端发送响应信息。所以接下来,我们来看看服务器端的响应报文。HTTP响应报文由响应行、响应头、空白行以及响应体组成。其报文格式如下:

  在响应报文中,非常重要的就是响应行,其中响应行中最重要的就是HTTP的状态码。HTTP协议中状态码有三位数字组成,第一位数字定义了响应的类别,有以下五种:

  • 1XX:信息提示。表示请求已被服务器接受,但需要继续处理,范围为100~101。
  • 2XX:请求成功。服务器成功处理了请求。范围为200~206。
  • 3XX:客户端重定向。重定向状态码用于告诉客户端浏览器,它们访问的资源已被移动,并告诉客户端新的资源位置。客户端收到重定向会重新对新资源发起请求。范围为300~305。
  • 4XX:客户端信息错误。客户端可能发送了服务器无法处理的东西,比如请求的格式错误,或者请求了一个不存在的资源。范围为400~415。
  • 5XX:服务器出错。客户端发送了有效的请求,但是服务器自身出现错误,比如Web程序运行出错。范围是500~505。

  我们开发过程有一些状态码比较常见,我们对其简单说明如下:

2HTTP客户端设计

  我们已经说过了,HTTP协议是基于TCP运行的,那么佷显然我们要实现一个HTTP客户端其本质上首先是要实现一个TCP客户端。

  在实现TCP客户端的基础上,我们要让这个客户端能够实现HTTP协议的基本操作。所以我们需要为客户端构造请求报文。关于请求报文的格式前面已经介绍过了,我们根据这个格式来构造,因为我们只是简单的一个HTTP客户端测试,所以我们采用GET方法。我们构造报文如下:

  "GET https://www.cnblogs.com/foxclever/ HTTP/1.1\r\n"

  "Host:www.cnblogs.com:80\r\n\r\n";

  对于HTTP协议具有专门的端口号,所以我们采用这个制定的端口号来实现。而实现的流程与一般TCP客户端是一样的。

3HTTP客户端实现

  经过上述的分析以及我们前面实现TCP客户端的经验,实现HTTP客户端已经没有问题。与TCP客户端一般,我们将HTTP客户端分成4个函数来实现。首先依然是实现HTTP客户端的初始化:

 /* HTTP客户端初始化配置*/
void Http_Client_Initialization(void)
{
struct tcp_pcb *tcp_client_pcb;
ip_addr_t ipaddr; /* 将目标服务器的IP写入一个结构体,为pc机本地连接IP地址 */
IP4_ADDR(&ipaddr,httpServerIP[],httpServerIP[],httpServerIP[],httpServerIP[]); /* 为tcp客户端分配一个tcp_pcb结构体 */
tcp_client_pcb = tcp_new(); /* 绑定本地端号和IP地址 */
tcp_bind(tcp_client_pcb, IP_ADDR_ANY, TCP_HTTP_CLIENT_PORT); if (tcp_client_pcb != NULL)
{
/* 与目标服务器进行连接,参数包括了目标端口和目标IP */
tcp_connect(tcp_client_pcb, &ipaddr, TCP_HTTP_SERVER_PORT, HTTPClientConnected); tcp_err(tcp_client_pcb, HTTPClientConnectError);
}
}

  我们很容易发现,上述初始化的代码其实就是TCP客户端的初始化代码,除了所使用的端口不一样外,其它都一样。也是在初始化代码中实现了两个函数的注册:一是使用tcp_connect注册连接完成的处理回调函数;二是使用tcp_err注册了连接错误处理回调函数。很明显接下来我们需要实现这两个函数。

  连接到服务器成功后的回调函数是tcp_connected_fn类型。在客户端建立一个连接后,内核会调用这个函数。在这个函数中,客户端回想服务器发送最初的操作请求,并且会在这个函数中注册数据接收处理回调函数。

 /* HTTP客户端连接到服务器回调函数 */
static err_t HTTPClientConnected(void *arg, struct tcp_pcb *pcb, err_t err)
{
char clientString[]="GET https://www.cnblogs.com/foxclever/ HTTP/1.1\r\n" "Host:www.cnblogs.com:80\r\n\r\n"; /* 配置接收回调函数 */
tcp_recv(pcb, HTTPClientCallback); /* 发送一个建立连接的问候字符串*/
tcp_write(pcb,clientString, strlen(clientString),); return ERR_OK;
}

  这个代码也是与普通TCP客户端一样,只是为了应用于HTTP协议,我们发送的请求字符串需要按照HTTP的格式来设定。对HTTP客户端连接服务器错误回调函数,它是tcp_err_fn类型,在这个程序中主要完成连接异常结束时的一些处理,可以释放一些必要的资源。在这个函数被内核调用时,连接实际上已经断开,相关控制块也已经被删除。所以在这个函数中我们可以重新初始化连接及其资源。在这里我们就是使用它来重新初始化TCP客户端。

 /* HTTP客户端连接服务器错误回调函数 */
static void HTTPClientConnectError(void *arg, err_t err)
{
/* 重新启动连接 */
Http_Client_Initialization();
}

  最后我们需要实现的是HTTP客户端接收到数据后的数据处理回调函数。这个函数其实就是我们前面连接成功时,注册过的HTTP客户端数据接收处理函数。这个函数是tcp_recv_fn类型。这是使用RAW API实现HTTP客户端功能最重要的一个函数,因为它决定HTTP客户端的具体功能。

 /* HTTP客户端接收到数据后的数据处理回调函数 */
static err_t HTTPClientCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err)
{
struct pbuf *tcp_send_pbuf;
char echoString[]="GET https://www.cnblogs.com/foxclever/ HTTP/1.1\r\n" "Host:www.cnblogs.com:80\r\n\r\n"; if (tcp_recv_pbuf != NULL)
{
/* 更新接收窗口 */
tcp_recved(pcb, tcp_recv_pbuf->tot_len); /* 将接收到的服务器内容回显*/
tcp_write(pcb,echoString, strlen(echoString), );
tcp_send_pbuf = tcp_recv_pbuf;
tcp_write(pcb, tcp_send_pbuf->payload, tcp_send_pbuf->len, ); pbuf_free(tcp_recv_pbuf);
}
else if (err == ERR_OK)
{
tcp_close(pcb);
Http_Client_Initialization(); return ERR_OK;
} return ERR_OK;
}

  同样,这段代码也是除了要按HTTP协议构造响应信息外,其他部分与普通TCP客户端类似。

4、结论

与前一篇实现HTTP服务器是基于TCP服务器实现的一样,这里我们实现HTTP客户端是基于TCP客户端来实现的。在我们前面已经实现TCP客户端的情况下,开发HTTP客户端应用就显得简单了。在这一篇我们基于LwIP实现了一个简单的HTTP客户端应用,我们并对其进行了简单的测试。再历程中我们只是实现了GET方法,但经测试设计是正确的。如果需要设计其他方法的HTTP应用只需在此基础上添加即可。

欢迎关注:

LwIP应用开发笔记之八:LwIP无操作系统HTTP客户端的更多相关文章

  1. LwIP应用开发笔记之一:LwIP无操作系统基本移植

    现在,TCP/IP协议的应用无处不在.随着物联网的火爆,嵌入式领域使用TCP/IP协议进行通讯也越来越广泛.在我们的相关产品中,也都有应用,所以我们结合应用实际对相关应用作相应的总结. 1.技术准备 ...

  2. LwIP应用开发笔记之四:LwIP无操作系统TFTP服务器

    前面我们已经实现了UDP的回环客户端和回环服务器的简单应用,接下来我们实现一个基于UDP的简单文件传输协议TFTP. 1.TFTP协议简介 TFTP是TCP/IP协议族中的一个用来在客户机与服务器之间 ...

  3. LwIP应用开发笔记之六:LwIP无操作系统TCP客户端

    上一篇我们基于LwIP协议栈的RAW API实现了一个TCP服务器的简单应用,接下来一节我们来实现一个TCP客户端的简单应用. 1.TCP简述 TCP(Transmission Control Pro ...

  4. LwIP应用开发笔记之七:LwIP无操作系统HTTP服务器

    前面我们实现了TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用协议,那就是HTTP超文本传输协议 1.  HTTP协议简介   超文本传输协议(Hyper Text Transf ...

  5. LwIP应用开发笔记之五:LwIP无操作系统TCP服务器

    前面我们实现了UDP服务器及客户端以及基于其上的TFTP应用服务器.接下来我们将实现同样广泛应用的TCP协议各类应用. 1.TCP简述 TCP(Transmission Control Protoco ...

  6. LwIP应用开发笔记之二:LwIP无操作系统UDP服务器

     前面我们已经完成了LwIP协议栈基于逻辑的基本移植,在这一节我们将以RAW API来实现UDP服务器. 1.UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包, ...

  7. LwIP应用开发笔记之三:LwIP无操作系统UDP客户端

    前一节我们实现了基于RAW API的UDP服务器,在接下来,我们进一步利用RAW API实现UDP客户端. 1.UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包 ...

  8. Modbus库开发笔记之八:CRC循环冗余校验的研究与实现

    谈到Modbus通讯自然免不了循环冗余校验(CRC),特别是在标准的串行RTU链路上是必不可少的.不仅如此在其他开发中,也经常要用到CRC 算法对各种数据进行校验.这样一来,我们就需要研究一下这个循环 ...

  9. STM32F412应用开发笔记之八:迪文串口屏显示驱动

    迪文的显示屏使用起来比较方便,其使用串口通讯,即可支持RS232,又可以支持TTL电平.在NUCLEO-F412ZG实验板上,USART2已经引到了CN9上,我们就利用USART2来实现与迪文串口屏的 ...

随机推荐

  1. 1.1 关于LVM的创建、删除、扩容和缩减

    一.新建LVM的过程 1.使用fdisk 新建分区 修改ID为8e 3.使用 pvcreate 创建 PV  4.使用 vgcreate 创建 VG  5.使用 lvcreate 创建 LV  6.格 ...

  2. 【LeetCode】缺失的第一个正数【原地HashMap】

    给定一个未排序的整数数组,找出其中没有出现的最小的正整数. 示例 1: 输入: [1,2,0] 输出: 3 示例 2: 输入: [3,4,-1,1] 输出: 2 示例 3: 输入: [7,8,9,11 ...

  3. [转帖]OLAP引擎这么多,为什么苏宁选择用Druid?

    OLAP引擎这么多,为什么苏宁选择用Druid? 原创 51CTO 2018-12-21 11:24:12 [51CTO.com原创稿件]随着公司业务增长迅速,数据量越来越大,数据的种类也越来越丰富, ...

  4. Java开发笔记(一百四十五)FXML布局的伸展适配

    前面介绍了FXML的基本格式及其控制器的用法,算是打通了FXML方式的编码流程.程序界面通常保持固定尺寸,不过有时也允许用户拖曳窗口大小,不拖不打紧,一拖就可能坏事.像之前的登录窗口,没拖的时候界面如 ...

  5. 删除字符串中的字符(C语言)

    题目: 编程序将给定字符串中指定字符删除.要求删除指定字符后原字符串不能留下空位置,字符串和指定字符均由键盘输入 基本思路 将字符串与要删除的字符进行比较,若为相同字符,则将字符串中的该字符替换为原字 ...

  6. 干货|Dubbo社区开发者日经验分享

    Hello,各位小伙伴大家好,我是小栈君,昨天也就是2019年10月26日,有幸在成都参加了由阿里举办的"Dubbo社区开发者日". 本次活动汇聚了各方面的大神欢聚一堂,主要是对现 ...

  7. TensorFlow学习笔记——cmd调用方法

    由于tensorflow支持最高的python的版本和anaconda自动配置的python最新版本并不兼容,故直接用常规的在终端键入“python”会出现问题.经过尝试对激活环境,调用的过程暂总结如 ...

  8. linux 1-常用命令

    文件处理命令: 命令格式:命令 [-选项] [参数] 例如:ls -la /etc   多个选项可以写在一起,不区分前后关系,例如 -l 和 -a 一起写成 -la 目录处理命令:ls (就是list ...

  9. Java.util.Math类--数学相关的工具类

    Math类--数学相关的工具类 java.util.Math类是数学相关的工具类,里面提供了大量的静态方法,完成与数学运算相关的操作. public static double abs(double ...

  10. 常用Java API之Ramdom--用代码模拟猜数小游戏

    常用Java API之Ramdom Ramdom类用来生成随机数字.使用起来也是三个步骤: 1.导包 import java.util.Random; 2.创建 Random r = new Rand ...