网络 应用层 | HTTP的认识及实现(模拟实现一个简单的服务器)

概念

应用层:是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-服务器作为网关或代理,但是没有及时从远程服务器收到请求,响应超时

第三要素:描述状态码
对状态码的描述信息,可以是官方文档对应的描述信息,也可以自定义描述信息

HTTP状态码大全

头部

响应头部用来描述本次响应的关键字段信息,由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服务器

实现流程

  1. 从键盘接收IP地址和PORT
  2. 创建套接字,为套接字绑定地址信息,服务端开始监听请求
  3. 开始接收客户端请求
  4. 用一个字符串req接收浏览器的请求信息
  5. 按照http响应信息格式定义首行first、头部header、空白行blank、正文body
  6. 将响应信息发送给用户
  7. 关闭套接字
//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)
在这里插入图片描述

服务端收到请求信息
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteShirtI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值