这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天
网络基础之HTTP协议
一、什么是HTTP协议
超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议,是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。HTTP是一种请求/应答协议。客户的浏览器一般通过TCP的80号端口向web服务器发送对某一页面的请求信息,web服务器接收该请求,并给客户返回其指定的页面作应答。
二、认识URL
URL全称Uniform Resource Locator,即统一资源定位符
URL的完整格式由以下几部分组成
protocol://hostname[:port]/path[?query][#fragment]
- protocol:网络传输协议,一般有http、https、file、ftp
- hostname:主机域名,即IP地址
- port:端口号,取值范围在0~65535
- path:路由地址,一般用来表示主机上的一个目录或文件地址
- query:查询字符串,从?到#,参数与参数之间用&分隔
- fragment:片段标识符,是#后的name
urlencode和urldecode
URL编码,将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
如上图,“+”被转义成了“%2B”
urldecode就是urlencode的逆过程
三、HTTP工作原理
- 客户端连接到Web服务器 一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接
- 发送HTTP请求 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成
- 服务器接受请求并返回HTTP响应 Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成
- 释放连接TCP连接 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求
- 客户端浏览器解析HTML内容 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示
四、HTTP格式
HTTP请求格式
- 首行: [方法] + [url] + [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度
-
Host :请求的资源在哪个主机的端口上
-
Connection:该请求支持长连接
-
Content-Length:正文内容长度,及Body的长度
-
Content-Type:数据类型
-
User-Agent:声明用户的操作系统和浏览器版本信息,就是告诉服务端用什么发送的请求
-
Accept:发起了请求
-
Referer:当前页面是从哪个页面跳转过来的
-
Accept-Encoding:接受的编码
-
Accept-Language:接受的语言类型
-
Cookie:用于在客户端存储少量信息,通常用于实现会话(session)功能
HTTP响应格式
五、HTTP方法
其中,最常用的就是post和get方法
GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
六、HTTP状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
上图就是post请求成功的例子
七、最简单的HTTP服务器
#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;
}
编译后,在运行端输入
./test 0 8080 //一定要输入3个参数,最后一个为指定的端口号
然后在网页中输入http://[ip]:[port] (自己的云服务器的公网IP及自己设置的端口号),就可以看到结果