前面我们实现了TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用协议,那就是HTTP超文本传输协议
1 、 HTTP****协议简介
超文本传输协议(Hyper Text Transfer Protocol),简称HTTP,是一种基于TCP的应用层协议,也是目前为止最为流行的应用层协议之一,可以说HTTP协议是万维网的基石。
HTTP是一种客户端请求、服务器应答式的应用层传输协议,也就是说服务器端是不可能主动向客户端发送数据的。在网络正常的情况下请求和响应都是一一对应的。而这个请求和响应也就是后端开发人员经常看到的Request和Response。
首先,我们来看客户器端的请求,HTTP请求报文由请求行、请求头、空白行以及请求体组成。其报文格式如下:
我们来说一说请求行,它由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。需要理解的是请求方法,HTTP协议的请求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT几种。先对常用的几种说明如下:
客户端发出HTTP请求,服务端接收后,会向客户端发送响应信息。所以接下来,我们来看看服务器端的响应报文。HTTP响应报文由响应行、响应头、空白行以及响应体组成。其报文格式如下:
在响应报文中,非常重要的就是响应行,其中响应行中最重要的就是HTTP的状态码。HTTP协议中状态码有三位数字组成,第一位数字定义了响应的类别,有以下五种:
我们开发过程有一些状态码比较常见,我们对其简单说明如下:
2 、 HTTP****客户端设计
我们已经说过了,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客户端是一样的。
3 、 HTTP****客户端实现
经过上述的分析以及我们前面实现TCP客户端的经验,实现HTTP客户端已经没有问题。与TCP客户端一般,我们将HTTP客户端分成4个函数来实现。首先依然是实现HTTP客户端的初始化:
1 /* HTTP客户端初始化配置*/
2 void Http_Client_Initialization(void)
3 {
4 struct tcp_pcb *tcp_client_pcb;
5 ip_addr_t ipaddr;
6
7 /* 将目标服务器的IP写入一个结构体,为pc机本地连接IP地址 */
8 IP4_ADDR(&ipaddr,httpServerIP[0],httpServerIP[1],httpServerIP[2],httpServerIP[3]);
9
10 /* 为tcp客户端分配一个tcp_pcb结构体 */
11 tcp_client_pcb = tcp_new();
12
13 /* 绑定本地端号和IP地址 */
14 tcp_bind(tcp_client_pcb, IP_ADDR_ANY, TCP_HTTP_CLIENT_PORT);
15
16 if (tcp_client_pcb != NULL)
17 {
18 /* 与目标服务器进行连接,参数包括了目标端口和目标IP */
19 tcp_connect(tcp_client_pcb, &ipaddr, TCP_HTTP_SERVER_PORT, HTTPClientConnected);
20
21 tcp_err(tcp_client_pcb, HTTPClientConnectError);
22 }
23 }
我们很容易发现,上述初始化的代码其实就是TCP客户端的初始化代码,除了所使用的端口不一样外,其它都一样。也是在初始化代码中实现了两个函数的注册:一是使用tcp_connect注册连接完成的处理回调函数;二是使用tcp_err注册了连接错误处理回调函数。很明显接下来我们需要实现这两个函数。
连接到服务器成功后的回调函数是tcp_connected_fn类型。在客户端建立一个连接后,内核会调用这个函数。在这个函数中,客户端回想服务器发送最初的操作请求,并且会在这个函数中注册数据接收处理回调函数。
1 /* HTTP客户端连接到服务器回调函数 */
2 static err_t HTTPClientConnected(void *arg, struct tcp_pcb *pcb, err_t err)
3 {
4 char clientString[]="GET https://www.cnblogs.com/foxclever/ HTTP/1.1\\r\\n"
5
6 "Host:www.cnblogs.com:80\\r\\n\\r\\n";
7
8 /* 配置接收回调函数 */
9 tcp_recv(pcb, HTTPClientCallback);
10
11 /* 发送一个建立连接的问候字符串*/
12 tcp_write(pcb,clientString, strlen(clientString),0);
13
14 return ERR_OK;
15 }
这个代码也是与普通TCP客户端一样,只是为了应用于HTTP协议,我们发送的请求字符串需要按照HTTP的格式来设定。对HTTP客户端连接服务器错误回调函数,它是tcp_err_fn类型,在这个程序中主要完成连接异常结束时的一些处理,可以释放一些必要的资源。在这个函数被内核调用时,连接实际上已经断开,相关控制块也已经被删除。所以在这个函数中我们可以重新初始化连接及其资源。在这里我们就是使用它来重新初始化TCP客户端。
1 /* HTTP客户端连接服务器错误回调函数 */
2 static void HTTPClientConnectError(void *arg, err_t err)
3 {
4 /* 重新启动连接 */
5 Http_Client_Initialization();
6 }
最后我们需要实现的是HTTP客户端接收到数据后的数据处理回调函数。这个函数其实就是我们前面连接成功时,注册过的HTTP客户端数据接收处理函数。这个函数是tcp_recv_fn类型。这是使用RAW API实现HTTP客户端功能最重要的一个函数,因为它决定HTTP客户端的具体功能。
1 /* HTTP客户端接收到数据后的数据处理回调函数 */
2 static err_t HTTPClientCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *tcp_recv_pbuf, err_t err)
3 {
4 struct pbuf *tcp_send_pbuf;
5 char echoString[]="GET https://www.cnblogs.com/foxclever/ HTTP/1.1\\r\\n"
6
7 "Host:www.cnblogs.com:80\\r\\n\\r\\n";
8
9 if (tcp_recv_pbuf != NULL)
10 {
11 /* 更新接收窗口 */
12 tcp_recved(pcb, tcp_recv_pbuf->tot_len);
13
14 /* 将接收到的服务器内容回显*/
15 tcp_write(pcb,echoString, strlen(echoString), 1);
16 tcp_send_pbuf = tcp_recv_pbuf;
17 tcp_write(pcb, tcp_send_pbuf->payload, tcp_send_pbuf->len, 1);
18
19 pbuf_free(tcp_recv_pbuf);
20 }
21 else if (err == ERR_OK)
22 {
23 tcp_close(pcb);
24 Http_Client_Initialization();
25
26 return ERR_OK;
27 }
28
29 return ERR_OK;
30 }
同样,这段代码也是除了要按HTTP协议构造响应信息外,其他部分与普通TCP客户端类似。
4 、结论
与前一篇实现HTTP服务器是基于TCP服务器实现的一样,这里我们实现HTTP客户端是基于TCP客户端来实现的。在我们前面已经实现TCP客户端的情况下,开发HTTP客户端应用就显得简单了。在这一篇我们基于LwIP实现了一个简单的HTTP客户端应用,我们并对其进行了简单的测试。再历程中我们只是实现了GET方法,但经测试设计是正确的。如果需要设计其他方法的HTTP应用只需在此基础上添加即可。
全部0条评论
快来发表一下你的评论吧 !