文章目录
重点
- 应用层的作用:满足我们日常需求的网络程序,都是在应用层
- 能够根据自己的需求,设计对应的应用层协议。
- 了解HTTP协议。
- 理解DNS的原理和工作流程。
一、http
1.url
http是向特定服务器申请特定资源获取到本地使用或者展示的
http(s)://<host>:<port>/<path>?<query>#<frag>
- 开头为协议名:http 或 https 协议;
- :主机名。一个 URL 中,既可以使用域名也可以使用 IP 表示主机地址
- :端口。主机名和端口之间使用冒号分隔。端口是可选的,如果省略将采用默认端口,http 默认端口是 80,https 默认端口 443;
- :资源路径。资源在网络主机上的路径,路径也是可选的,缺省访问默认资源;
- :查询参数。格式为 key=value,多个参数使用 & 分隔;参数也是可选的;
- :片段。从 # 开始到最后,一般用于定位到资源内的一个片段,比如文档的一个章节;片段也是可选的。
2.编码解码
转义的规则:将需要转码的字符转为十六进制,然后从右到左,取4位(不足4位直接处理),每两位做一位,前面加上%,编码成%XY格式
编码工具:http://tool.chinaz.com/tools/urlencode.aspx
3.域名和端口
HTTP | 80 |
---|---|
HTTPS | 443 |
SSH | 22 |
二、http 协议
1.概念
http请求由以下四部分组成:
-
请求行:[请求方法]+[url表示文件路径]+[http版本]
-
请求参数:请求的属性,是以key: value的形式。
-
标识报头结束:遇到空行表示请求报头结束。
-
请求正文:请求报头中会有一个Content-Length属性来标识请求正文的长度。
http响应由以下四部分组成:
- 状态行:[http版本]+[状态码]+[状态码描述]
- 响应参数:响应的属性,这些属性都是以key: value的形式按行陈列的。
- 标识报头结束:遇到空行表示响应报头结束。
- 响应正文:响应报头中会有一个Content-Length属性来标识响应正文的长度。
telnet
用于登录远程主机
telnet 192.168.7.42
telnet 192.168.7.42 8080
当终端出现 Escape character is '^]'.
,在终端按下键盘的 Ctrl + ]
组合键即可进入 socket
交互模式
命令参数
telnet> send ?
ao Send Telnet Abort output 进入了 socket 通道
ayt Send Telnet 'Are You There'
brk Send Telnet Break
ec Send Telnet Erase Character
el Send Telnet Erase Line
escape Send current escape character
ga Send Telnet 'Go Ahead' sequence
ip Send Telnet Interrupt Process
nop Send Telnet 'No operation'
eor Send Telnet 'End of Record'
abort Send Telnet 'Abort Process'
susp Send Telnet 'Suspend Process'
eof Send Telnet End of File Character
synch Perform Telnet 'Synch operation'
getstatus Send request for STATUS
? Display send options
# 输入 quit 退出 telnet 模式
telnet> quit
2.请求测试
TCP服务器,运行服务器,在浏览器输入ip:8081(我们绑定端口是8081),即可接收到一下请求
c++
int main()
{
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0){
cerr << "socket error!" << endl;
return 1;
}
cout << "socket creat succes, sock: " << listen_sock << endl;
//绑定
struct sockaddr_in local;
memset(&local, '\0', sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8081);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
return 2;
}
cout << "bind success" << endl;
//监听
if (listen(listen_sock, 5) < 0){
cerr << "listen error!" << endl;
return 3;
}
cout << "listen success" << endl;
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
while(1){
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0){
cerr << "accept error!" << endl;
continue;
}
//业务
char buffer[1024];
while (1)
{
ssize_t size = read(sock, buffer, sizeof(buffer) - 1); // 默认是字符串
if (size > 0)
{ // 读取成功
buffer[size] = '\0';
cout << "--------------------------http request begin--------------------------" << endl;
cout << buffer << endl;
cout << "---------------------------http request end---------------------------" << endl;
}
else if (size == 0)
{ // 对端关闭连接
cout << " close!" << endl;
break;
}
else
{ // 读取失败
cerr << sock << " read error!" << endl;
break;
}
}
}
return 0;
}
-
GET:请求方式为获取数据
-
/:请求的是根路径
-
HTTP/1.1:使用的 http 协议版本
-
Connection:代表我们和服务器的链接方式,keep-alive 代表保持连接,closed 代表当前端口只支持短链接
-
User-Agent:客户端信息,可以看到是 windows 系统、Chrome 内核的浏览器
-
Accept: 支持接收的信息类型
-
Aceept-Encoding: 对信息进行压缩
-
Accept-Language:支持的语言
-
Cookie:身份信息
--------------------------http request begin--------------------------
GET / HTTP/1.1
Host: ip:8081
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9---------------------------http request end---------------------------
3.响应测试
在当前目录下建立一个web 目录,然后在web目录下建立index.html文件,存放以下代码,前端代码用于展示
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test</title>
</head>
<body>
<h1>test</h1>
</body>
</html>
TCP服务器,运行服务器,在浏览器输入ip:8081(我们绑定端口是8081),即可接收到一下请求,并且将上面前端代码在浏览器展示
c++
int main()
{
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0){
cerr << "socket error!" << endl;
return 1;
}
cout << "socket creat succes, sock: " << listen_sock << endl;
//绑定
struct sockaddr_in local;
memset(&local, '\0', sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8081);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
return 2;
}
cout << "bind success" << endl;
//监听
if (listen(listen_sock, 5) < 0){
cerr << "listen error!" << endl;
return 3;
}
cout << "listen success" << endl;
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
while(1){
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0){
cerr << "accept error!" << endl;
continue;
}
//业务
char buffer[10240];
while (1)
{
ssize_t size = read(sock, buffer, sizeof(buffer) - 1); // 默认是字符串
if (size > 0)
{ // 读取成功
buffer[size] = '\0';
cout << "--------------------------http request begin--------------------------" << endl;
cout << buffer << endl;
cout << "---------------------------http request end---------------------------" << endl;
}
else if (size == 0)
{ // 对端关闭连接
cout << " close!" << endl;
break;
}
else
{ // 读取失败
cerr << sock << " read error!" << endl;
break;
}
string path = getPath(buffer);// 文件路径
string resources = "web"; // 网址根目录地址
resources += path;// 文件路径
cerr << "文件路径:"<<resources.c_str() << endl;
string html = readFile(resources);// 打开文件
//构建HTTP响应
string status_line = "http/1.1 200 OK\r\n"; //状态行
string response_header = "Content-Length: " + to_string(html.size()) + "\r\n"; //响应参数
string blank = "\r\n"; //标识报头结束
string response_text = html; //响应正文
string response = status_line + response_header + blank + response_text; //响应
//响应HTTP请求
send(sock, response.c_str(), response.size(), 0);
}
}
return 0;
}
浏览器展示如下:
4.请求方法
get
将html代码修改为以下代码,方法用get,之后运行服务器,在浏览器访问ip:8081(我们绑定端口是8081)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test</title>
</head>
<body>
<h1>test</h1>
<form action="/a/index.html" method="get">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
在浏览器显示如下
url会带参数,输入账号和密码提交后会404,因为不支持参数请求
post
将html代码修改为以下代码,方法用post,之后运行服务器,在浏览器访问ip:8081(我们绑定端口是8081)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test</title>
</head>
<body>
<h1>test</h1>
<form action="/a/index.html" method="post">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
url不会带参数,输入账号和密码提交后会404,因为不支持参数请求
get和post区别
- GET 方法通过 url 传参,不私密
- POST 方法会将 url 参数提取出来,拼接到正文部分,私密
http方法
方法 | 说明 | 支持的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 |
5.状态码
类别 | 原因短语 | |
---|---|---|
1XX | Informational(信息性状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
404/403
403 forbidden请求权限不够,404(Not Found)
5xx
程序有 bug 提前退出
301/302 重定向
- 301 永久重定向
- 302 临时重定向
格式:location:new url
上面代码对应内容修改为下面内容,之后运行服务器,在浏览器访问ip:8081(我们绑定端口是8081),发现我们访问的页面是百度
c++
// 尝试进行302重定向
string response = "http/1.1 302 Temporarily moved\r\n";
response += "Location: https://www.baidu.com/\r\n";
response += "\r\n";
// 响应HTTP请求
send(sock, response.c_str(), response.size(), 0);
6.http的Header
-
Content-Type:数据类型(text/html等)。
-
Content-Length:正文的长度。
-
Host:IP和端口。
-
User-Agent:用户的操作系统和浏览器的版本信息。
-
Referer:记录上一个页面。
-
Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
-
Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。
Cookie
http实际上是一种无状态协议,http的每次请求/响应之间是没有任何关系的,
例如登录一次csdn时,在csdn跳转不用再次登录,如果将csdn中的cookie,则需要重新登录
浏览器帮我们存取了一定的身份信息在本地,记录用户状态会话保持,叫做cookie
- 重新登录网站需要重新输入账号和密码,说明浏览器保存的cookie信息是内存级别的
- 重新登录网站不需要重新输入账号和密码,说明浏览器保存的cookie信息是文件级别的
响应报头当中添加上一个Set-Cookie字段,之后运行服务器,在浏览器访问ip:8081(我们绑定端口是8081),发现cookie内容变成test
c++
// 构建HTTP响应
string status_line = "http/1.1 200 OK\r\n"; // 状态行
string response_header = "Content-Length: " + to_string(html.size()) + "\r\n"; // 响应参数
response_header += "Set-Cookie: test\r\n"; // 添加Set-Cookie字段
string blank = "\r\n"; // 标识报头结束
string response_text = html; // 响应正文
string response = status_line + response_header + blank + response_text; // 响应
session
- 第一次登录某个网站,服务器生成一个对应的SessionID
- 客户端下一次请求的时候,加上SessionID
- 服务器收到请求,在库中对比SessionID
三、https协议
https 在 http 的下层添加了一个 SSL/TLS 软件层,这就叫做https
下面是加密和解密的过程
对称加密
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密
客户端和服务器都有一把🔑,客户端先把信息丢进盒子里,再用🔑锁上盒子,发送给服务端。服务端用🔑打开盒子,取出数据
对称加密是不可取的,🔑可能被窃取了
非对称加密
采用公钥和私钥来进行加密和解密,用其中一个密钥进行加密就必须用另一个密钥进行解密,这种加密方法称为非对称加密
非对称 + 对称:
- 服务端具有非对称公钥 S 和私钥 S'
- 客⼾端发起请求,获取服务端公钥 S
- 客⼾端在本地生成对称密钥 C, 通过公钥 S 加密,发送给服务器.
- 由于中间人没有私钥,即使截获了数据,也无法还原出内部的原文,也就无法获取到对称密钥
- 服务器通过私钥 S' 解密,还原出客户端发送的对称密钥 C,并且使用这个对称密钥加密给客户端发送的响应数据
- 后续客户端和服务端都采用密钥 C 来进行对称加密通信
CA 证书
CA 机构颁发的权威证书,就像是身份证一样
ssl 证书加密原理:
客户端用 hash 函数对数据进行散列,散列完后的数据用CA密钥加密得到签名,最后签名和原始数据构成签名的数据
服务器得到签名数据,拆成数据和签名,签名用CA公钥解密得到散列完后的数据,在将原始数据散列,对比散列完后的数据和散列后原始数据,如果一样正确,否则错误,保证如果数据或者签名被修改则会被服务器发现
注意:先hash再加密形成摘要是缩短密文长度,提高速度
ssl 证书 + 非对称 + 对称:
- 客户端向服务器发起请求
- 服务器向客户端发送证书
- 客户端收到 ssl 证书,用CA的公钥验证合法后,生成本地生成的密钥 R,用ssl 证书的公钥加密密钥R后发送服务器
- 服务端收到密钥 R 的加密信息,使用 ssl 证书的私钥进行解密,获取到密钥 R
- 服务器使用 R 加密数据返回给客户端
- 客户端接受服务器端响应的数据并用密钥 R 解密
DNS-域名解析
将域名转化为IP地址的应用层协议,域名解析基于 UDP
- 查询浏览器本地缓存,查询到记录就可以直接得到对应的IP地址,完成解析
- 查询 PC 主机本地缓存;
- 查询主机 HOSTS 文件;
- 查询默认 DNS 本地服务器查询;
- 查询根 DNS 服务器
- 查询顶级 DNS 服务器
- 本地 DNS 服务器向客户端返回 IP 地址