本文已参与「新人创作礼」活动,一起开启掘金创作之路。
HTTP是典型的应用层协议,是目前最流行的通信协议,没有之一。
其衍生出了封装SSL协议的“安全版”:HTTPS协议,相对于传统的HTTP协议更为安全,但也是“相对的安全”。
只要保证通信传输一端发送时构造的数据, 在另一端能够正确的进行解析, 就是可以的,这种约定就是 应用层协议。是应用程序之间的沟通,这其中也涉及序列化与反序列化。
序列化:将数据按照持久化存储或网络数据传输的格式进行排布。 反序列化:对数据以指定的协议进行解析。
虽然应用层协议是程序猿自定制的的。但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
urlencode编码 与 urldecode解码
像 /、?、: 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
例如在百度上搜索C++,其URL为:
www.baidu.com/s?wd=C%2B%2…...
其中的%2B%2B就是转义字符,"%2B"就是"+" 字符的转义字符。
网上编码解码的工具很多,这里简单介绍一个以便读者使用:【 tool.chinaz.com/Tools/urlen… 】
HTTP协议格式
通过分析数据包数据就可以明晰网络通信原理,这里我们使用fiddler工具进行抓包,从中就可以获得HTTP请求数据、响应数据等。
HTTP请求
请求格式为:请求首行、请求头部、空行、正文
//利用随意抓取的一个数据包举例:
GET http://neirong.funshion.com/airport/lua/fire.php?name=Fireman&cid=1001799 HTTP/1.1
Cache-Control: max-age=43200
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko Funshion/1.0.0.1
Host: neirong.funshion.com
Connection: Keep-Alive
Pragma: no-cache
A SSLv3-compatible ClientHello handshake was found. Fiddler extracted the parameters below.
Version: 3.3 (TLS/1.2)
Random: 18 75 2D C6 A0
请求首行
格式为:[方法] + [url] + [版本]
[GET]
[http://neirong.funshion.com/airport/lua/fire.php?name=Fireman&cid=1001799]
[HTTP/1.1]
请求头部(Header)
请求的属性, 冒号:分割的==键值对==。每组属性之间使用\n分隔;
Cache-Control: max-age=43200
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko Funshion/1.0.0.1
Host: neirong.funshion.com
...
空行
请求头部信息遇到空行表示Header部分结束。(连续遇到2个\r\n就认为是头部信息结束了)
正文(Body)
空行后面的内容都是Body. 它允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;
HTTP响应
响应首行
格式为: [版本号] + [状态码] + [状态码解释]
[HTTP/1.1] [200] [OK]
其他都与请求的元素相同,包含Header、空行、Body。
HTTP请求方法
| 方法 | 说明 | 支持版本 |
|---|---|---|
GET | 获取资源 | 1.0/1.1 |
POST | 传输实体主体 | 1.0/1.1 |
PUT | 传输文件 | 1.0/1.1 |
HEAD | 获得报文头部 | 1.0/1.1 |
DELETE | 删除文件 | 1.0/1.1 |
| OPTIONS | 询问支持的方法 | 1.1 |
| TRACE | 追踪路径 | 1.1 |
| CONNECT | 要求用隧道协议连接代理 | 1.1 |
| LINK | 建立和资源之间的联系 | 1.0 |
| UNLINK | 断开联系 | 1.0 |
其中最常用和常见的还是GET和POST方法。
HTTP状态码
| / | 类别 | 原因 |
|---|---|---|
| 1xx | 信息性状态码 | 接收的请求正在处理 |
| 2xx | Success(成功) | 请求的数据处理完毕 |
| 3xx | Redirection(重定向) | 需要进行附加操作以完成请求 |
| 4xx | Client Error(客户端错误) | 服务器无法处理请求 |
| 5xx | Server Error(服务器错误) | 服务器处理请求出错 |
| 最常见的状态码, 比如 : | ||
200(OK) | ||
404(Not Found) | ||
403(Forbidden,没有权限) | ||
404(Not Found) | ||
302(Redirect, 重定向) | ||
504(Bad Gateway,坏的网关) |
HTTP常见Header
Content-Type: 数据类型(text/html等)Content-Length:Body的长度Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;User-Agent: 声明用户的操作系统和浏览器版本信息;referer: 当前页面是从哪个页面跳转过来的;location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Usage() {
printf("usage: ./server [ip] [port]\n");
}
int main(int argc, char* argv[]) {
if (argc != 3) {
Usage();
return 1;
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return 1;
}
ret = listen(fd, 10);
if (ret < 0) {
perror("listen");
return 1;
}
for (;;) {
struct sockaddr_in client_addr;
socklen_t len;
int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
if (client_fd < 0) {
perror("accept");
continue;
}
char input_buf[1024 * 10] = {0}; // 用一个足够大的缓冲区直接把数据读完.
ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);
if (read_size < 0) {
return 1;
}
printf("[Request] %s", input_buf);
char buf[1024] = {0};
const char* hello = "<h1>hello world</h1>";
sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", strlen(hello), hello);
write(client_fd, buf, strlen(buf));
}
return 0;
}
编译, 启动服务. 在浏览器中输入 http://[ip]:[port], 就能看到显示的结果 "Hello World"
此处我们使用 9090 端口号启动了HTTP服务器. 虽然HTTP服务器一般使用80端口,但这只是一个通用的习惯. 并不是说HTTP服务器就不能使用其他的端口号.