目录
概念
应用层:是TCP/IP的顶层,通过使用传输层提供的服务,直接向用户提供服务,是TCP/IP网络与用户之间的界面或接口。应用层是直面程序员的一层,因为应用层是程序员自己写的,因此应用层的协议都是程序员自己定的
应用层上的典型应用包括web浏览器、电子邮件、文件传输访问和远程登录等。
序列化和反序列化
序列化:将各个数据对象按照指定的协议组织成为持久化存储/数据传输的二进制数据串
反序列化:将二进制数据串按照指定协议解析得到各种数据对象
常用的序列化方式:json序列化/protobuf/二进制序列化
序列化方式的评价标准:解析数据的性能、传输数据的性能、和可读性
协议
应用层有两个重要的协议:一个是自定制协议,一个是超文本传送协议HTTP
。
自定义协议:程序员自己根据自己的应用场景的特点,定义协议(数据的格式/数据的描述信息)
如何制定自定制协议
我们以一个简单的网络版计算器来举例,计算机功能为:客户端发送两个数字以及一个运算符给服务端,服务端获取到数据后然后进行运算,将运算结果返回给客户端。
有两种约定方案可以实现:
方案一:1、客户端发送一个型如"1+1"的字符串;2、这个字符串的两个操作数都是整形;3、两个数字之间会有一个字符是运算符,运算符只能是+;4、数字和运算之间没有空格
方案二:采用结构体构造二进制数据串,将三个数据都封装到一个结构体中
struct cal_t
{
int num1;
int num2;
char op;
};
这种方案是非常常用的,将多个对象封装成一个对象,当客户端收到数据时就不用再多此一举得去解析,而是按指定格式去取数据;前4个字节数数据1,前8个字节的后4字节是数据2,最后一个字节就是运算符。用起来非常方便
socket封装的对象中添加结构体信息
typedef struct
{
int num1;
int num2;
char op;
}cal_t;
客户端:
cal_t cal;
cal.num1 = 12;
cal.num2 = 34;
cal.op = '+';
send(fd, (char*)&cal, sizeof(cal_t), 0);
服务端:
cal_t cal;
recv(fd, (char*)&cal, sizeof(cal_t), 0);
printf("%d % c %d\n", cal.num1, cal.op, cal.num2);
这样子就可以实现自定制协议了
知名协议----HTTP协议
HTTP是当今互联网应用中使用最广泛的应用层协议,也是应用程序之间远程通信所采用比较多的协议,用来在浏览器和WWW服务器之间传送超文本的协议。基于浏览器的HTMP、XML、JSON、等格式的文本都是通过HTTP进行传输的。它非常便捷,客户向服务端发送请求服务时,只需要发送路径、参数以及请求方法即可。请求方法常用的右GET、POST、UDPATE、DELETE等,它们组成RESTful架构风格的不可缺一的部分
网址:例如我们平时打开的网页都有相对应的网址https://www.baidu.com/
。网址是统一资源定位器,简称URL,定位网络中某台主机上的某个资源
URL的组成
协议方案名称://认证用户名:认证密码@服务器IP地址:服务器处理进程端口/请求的资源路径?查询字符串#片段标识符
http://username:password@www.baidu.com:80/index.html?name=WhiteShirtI&age=21#id
协议方案名称(常用):通信协议,通常使用http
认证用户名:认证密码(少用):用户的账号密码
服务器IP地址(常用):我们看到的不一定是IP地址,也可能是一个域名,也就是服务器的别名,通过域名解析服务器就能得到服务器的IP地址
服务器处理进程端口(常用):web服务器默认http服务器端口是80端口,默认不显示
/请求的资源路径(常用):资源在服务器上的路径,这里的/
是http的根目录,但是是一个服务器的相对根目录,只是一个子目录。原因是省去前面重要的目录地址信息,防止用户访问
查询字符串:客户端提交给服务端的一些数据,由key=val&key=val形式的键值对组成的;查询的字符串不能出现特殊字符:因为url中特殊字符都是有特殊含义的,一旦提交的数据中有特殊字符,就会造成数据二义,因此若要提交的数据中有特殊字符,则需要进行数据转义;urlencode
:url解码,提交的数据中不能出现特殊字符,一旦出现就要进行转义。将特殊字符每一个字节转换为16进制的数字字符,并且使用%前缀作为转义;+ 转义成 %2B ;解释:在ASSIC中+是代表数字43,转换成16进制就是2B
urldecode
:url解码,在url中遇到%,则认为其后两个字符需要转义,将第一个字符转换成数字乘以16,加上第二个字符转换的数字。%2B 转义成 + ;解释:2*16+11=43, 将43转换为ASSIC码对应的符号
片段标识符:html中的一个标签id,直接跳转到页面的某个位置
HTTP协议请求格式
HTTP协议请求包含四个内容:首行、请求头部、空行、正文
首行
GET https://blog.csdn.net/qq_44443986?spm=1011.2124.3001.5113 HTTP/1.1 以空格进行间隔包含三个要素,并且最终以\r\n作为结尾
第一要素:请求方式:GET/POST/HEAD/PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
- GET-请求指定的页面信息,并返回实体主体,没有请求正文。也可以通过url中查询字符串向服务器提交数据,但是数据不安全 且 url长度有限制(各个服务器应用商的限制)
- POST-向指定资源提交数据进行处理请求(例如提交表单或者上传文件),且提交的数据在请求正文中,相对GET比较安全 且无长度限制。
- HEAD-类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
第二要素:URL:网址信息
第三要素:协议版本:HTTP/1.1/0.9/1.0/2.0
- HTTP/0.9-这时候的http仅用于传输html数据,并且只有GET请求方法,并且没有协议标准格式
- HTTP/1.0-正式规定了http协议格式,并且增加了多种请求方法,并且支持了不同文件格式的数据流
- HTTP/1.1-在1.0的基础上增加了更多请求方法和头部描述信息,并且支持了长连接管线化传输。(http基于在传输层tcp实现通信,短连接指的是建立连接后发送一个请求,得到响应之后就关闭连接;长连接指的是发送请求得到响应后并不会关不连接,当下次再来一次请求是还会使用当前这个连接,但这个连接不是永久存在的,当两端长时间没有交往时会自动断开;管线化传输指的是将多个HTTP请求整批提交的技术,而在传送过程中不需先等待服务端的回应 <响应的顺序必须与请求的顺序保证一致:1请求到2请求,必须等到1响应了2才能响应,存在线头阻塞问题>)
- HTTP/2.0-采用二进制流传输,并且支持多路复用,并且允许服务端主动推送数据 。(服务端主动推送数据是指请求一次,服务端会一次性推送所有数据,而不是一个请求,对应一个响应的数据;<利用多路复用,头部中标识了自己属于哪个流,解决了线头阻塞的问题,同时也提高了网络速度的利用率>)
头部
请求头部用来描述本次请求的关键字段信息,由key:val形式的键值对组成,并且每个键值对以\r\n作为结尾
- Connection-控制长/短连接
- Cache-Control-缓存控制
- User-Agent-客户端的属性
- Accept-描述自己所能接收的数据属性
- Content-Length-描述正文长度
- Content-type-描述正文的数据类型
- Get请求方式专属信息:Cookie-带有客户端的身份、状态等信息,客户端每次通信从cookie文件读取数据,通过cookie向服务端传递信息,(用于维护客户端状态信息)但是不够安全。Post则是将这些信息保存在正文中(安全)
空行
目的是间隔头部与正文,\r\n。接收http数据的时候,当连续接收两个\r\n的时候,则就认为头部到此结束。
正文
先获取完整头部,通过头部中的Content-Lenght获取正文长度,然后获取指定长度的正文,通过这种方式每次获取完整一条http请求数据
正文就是提交给服务端的数据
HTTP协议响应格式
HTTP协议响应包含四个内容:首行、请求头部、空行、正文
首行
HTTP/1.1 200 OK 以空格进行间隔包含三个要素,并且最终以\r\n作为结尾
第一要素:版本协议
第二要素:响应状态码
响应状态码是一个数字,这个数字表示本次的请求后服务端所做出响应的结果,这个结果分为五大类型:
- 1xx:描述信息-服务器收到请求,需要请求者继续执行操作
- 2xx:请求成功-操作被成功接收并处理;典型的有200-请求成功。一般用于GET与POST请求
- 3xx:重定向-要求客户端重新请求新的位置;典型的有301-永久移动、302-临时移动
- 4xx:客户端错误-请求包含语法错误或无法完成请求;典型的有400-请求错误;404-请求的资源不存在
- 5xx:服务端错误-服务器在处理请求的过程中发生了错误;典型的有500-服务器内部错误,无法完成请求;502-服务器作为网关或代理尝试执行请求时,从远程服务器接收到了一个无效的响应;504-服务器作为网关或代理,但是没有及时从远程服务器收到请求,响应超时
第三要素:描述状态码
对状态码的描述信息,可以是官方文档对应的描述信息,也可以自定义描述信息
头部
响应头部用来描述本次响应的关键字段信息,由key:val形式的键值对组成,并且每个键值对以\r\n作为结尾
- Connection-控制长/短连接
- Content-type-描述正文的数据类型
- Server-包含有关用作原始服务器处理请求的软件信息
- Transfer-Encoding-指定实体正文传输给客户端的方式,例如chunked-分块传输,将一个正文分为多块进行传输
- Expires-缓存过期时间
- Cache-Control-缓存控制
- Set-Cookie-服务端通过set-cookie向客户端传递信息,会被保存在客户端浏览器的cookie文件中
由于cookie的使用不够安全,从而引入了Session,搭配cookie使用
Session-会话:服务端会为每个登录的客户端创建会话session,会话session保存了一些会话信息(客户端身份、状态信息等),并且session保存在服务端。可以通过cookie将session id返回给客户端,客户端每次通信都会通过cookie带有自己的session id,服务端就通过session id知道这是哪一个客户端了
图解:
cookie和session的区别:cookie是保存在客户端上的数据(用户信息),用于持续与服务端进行信息传递的一种手段;session是一种会话的控制,服务端保存的会话信息包含客户端的身份状态信息,通过cookie/set-cookie传递session_id进行客户端的身份状态识别
空行
目的是间隔头部与正文,\r\n。接收http数据的时候,当连续接收两个\r\n的时候,则就认为头部到此结束。
正文
正文就是提交给服务端的数据
HTTPS
HTTPS协议并不是一个新的协议,而是在HTTP协议基础上进行了一层加密。https协议是基于ssl进行加密实现的加密传输协议
安全传输需要考虑的问题:
1、身份验证问题:防止伪装
2、数据加密问题:防止监听
CA证书:通信双方在通信前先到权威机构请求给自己颁发一个CA证书
通信两方建立连接后,在通信之前先将证书发送给对方,收到对方的证书后,查看证书的权威机构是否是自己所信任的权威机构,如果是则到权威机构进行身份验证,通过后才进行通信
对称加密:服务器和浏览器使用的密钥都是同一个密钥
非对称加密:一个为公钥,一个为私钥,公钥和私钥之间不能相互推导,公钥加密需要私钥解密,私钥加密需要公钥解密
混合加密:通信时先使用非对称加密保护对称密钥协商的过程,直到对称密钥协商完后再使用对称加密进行传输
简单模拟实现HTTP服务器
实现流程
- 从键盘接收IP地址和PORT
- 创建套接字,为套接字绑定地址信息,服务端开始监听请求
- 开始接收客户端请求
- 用一个字符串
req
接收浏览器的请求信息 - 按照http响应信息格式定义首行
first
、头部header
、空白行blank
、正文body
- 将响应信息发送给用户
- 关闭套接字
//http_srv.cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 3)
{
cout << "请按格式输出 ./http_srv ip port" << endl;
return -1;
}
//ip地址
string ip = argv[1];
//端口号
uint16_t port = stoi(argv[2]);
//创建套接字
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(ip.c_str());
addr.sin_port = htons(port);
socklen_t srv_len = sizeof(addr);
int ret = bind(fd, (struct sockaddr*)&addr, srv_len);
if (ret < 0)
{
perror("bind");
return -1;
}
//服务端开始监听
ret = listen(fd, 5);
if (ret < 0)
{
perror("listen");
return -1;
}
while (1)
{
//接收客户端请求
struct sockaddr_in cli_addr;
socklen_t cli_len;
int cli_fd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len);
if (cli_fd < 0)
{
perror("accept");
continue;
}
//用于接收http响应信息
string req;
char tmp[4096] = {0};
ret = recv(cli_fd, tmp, 4096, 0);
if (ret <= 0)
{
perror("recv");
continue;
}
req.assign(tmp, ret);
cout << "req[" << req << "]" << endl;
//定义http响应信息,反馈给用户
//首行
string first = "HTTP/1.1 200 ok\r\n";
//空白行
string blank = "\r\n";
//正文
string body = "<html><body><h1>Hello WhiteShirtI<h1></body></html>";
//头部信息
stringstream header;
header << "Content-Length:" << body.size() << "\r\n";
header << "Connection:close\r\n";
//响应信息发送给用户
send(cli_fd, first.c_str(), first.size(), 0);
send(cli_fd, header.str().c_str(), header.str().size(), 0);
send(cli_fd, blank.c_str(), blank.size(), 0);
send(cli_fd, body.c_str(), body.size(), 0);
close(cli_fd);
}
close(fd);
}
运行结果:
先开启服务端,等待请求
打开网页,输入http://192.168.73.130:8888
(如果是虚拟机必须关闭虚拟机的防火墙systemctl disable firewalld
)
服务端收到请求信息