负责应用程序之间的数据沟通(我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层)
结构化数据的传输(通过结构体在内存中对数据对象进行组织,将二进制数据传输出去)
使用结构体进行数据对象的二进制结构化组织,进行数据传输/可持久化数据存储
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.
约定方案一:
约定方案二:
// proto.h 定义通信的结构体
typedef struct Request {
int a;
int b;
} Request;
结构化数据传输好处:
二进制序列化优点:反序列化解析速度快
常见的数据序列化方式还有很多:json序列化;protobuf序列化
无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是应用层协议
HTTP协议:超文本传输协议
优点:http协议给程序员留有一定的自定制空间
统一资源定位符(定位网络中唯一的一份资源)- URL
统一资源定位符如何定位网络中唯一的资源? - URL的格式以及所包含的要素
网址的元素:
协议方案名称://用户名:密码@服务器IP地址:端口/资源路径?查询字符串#片段标识符
用户提交给服务器的查询字符串中的val需要进行url编码 — 因为url中有很多的特殊字符具有特殊含义,若用户提交的数据中也包含有相同的特殊字符就会造成歧义,因此需要对val进行url编码操作
将特殊字符的每一个字节,都转换成为16进制数字的字符,例如: + -> 2b ;万一要是用户本身提交的数据就有2b,也会造成歧义;因此对每一个字节进行转换之后,需要在前边加上%表示紧跟其后的两个字符经过了url编码 + -> %2b
得到查询字符串后,在val中遇到%,则认为紧跟其后的两个字符需要解码 - 将两个字符转换为数字 %2b -> 2 11;
第一个数字左移4位或者第一个数字乘以16+后边的数字 :2*16 + 11
http协议的实现(http协议的数据结构)
fiddler工具:浏览器的抓包工具,抓取浏览器与服务器之间的通信数据
包含三大信息,以空格进行间隔,并且以\r\n作为结尾; 请求方法 URL 协议版本
请求方法:
HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法。
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
GET/POST:get也能够向服务器提交数据,但是提交的数据是在url的查询字符串中(get是没有正文的)/而post提交的数据是在正文中
get提交数据是不太安全的,并且url的长度是有限制的,早期1K,现在很多都是4K/8K
方法 | 描述 |
---|---|
PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | 请求服务器删除指定的页面。 |
CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
OPTIONS | 允许客户端查看服务器的性能。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
URL:
主要信息就是请求的资源路径以及提交的查询字符串
协议版本:
HTTP/1.1 0.9/1.0/1.1/2.0
包含3大信息,以空格进行间隔,以\r\n作为结尾; 协议版本 响应状态码 状态码描述\r\n
协议版本:0.9/1.0/1.1/2.0
响应状态码:向客户端反应本次请求的处理结果状态 — 包含5大种类:1xx / 2xx / 3xx / 4xx / 5xx
一个个key: val形式的键值对,键值对之间以\r\n作为间隔
Cookie与Session有什么区别:
\r\n - 间隔头部与正文
头部中最后一个头部信息也是\r\n作为结尾的;空行的重要性在于判断是否接收了完整的http头部信息
first_line\r\nkey: val\r\nkey: val\r\n……key: val\r\n\r\ncontent
通常接受http数据的流程:
http协议是应用层协议,在传输层使用的是tcp协议
搭建一个http服务器,收到请求之后打印出来,然后组织一个响应hello world给客户端就行
// 使用封装的TcpSocket类实例化对象实现tcp服务端程序
#include
#include
#include
#include "tcpsocket.hpp"
int main(int argc, char *argv[]){
if(argc != 3){
printf("em:./tcp_srv 192.168.122.132 9000\n");
return -1;
}
std::string ip = argv[1];
uint16_t port = std::stoi(argv[2]); // stoi将字符串转换为数字
TcpSocket lst_sock;
CHECK_RET(lst_sock.Socket());
CHECK_RET(lst_sock.Bind(ip, port));
CHECK_RET(lst_sock.Listen());
while(1){
TcpSocket cli_sock;
bool ret = lst_sock.Accept(&cli_sock);
if(ret == false){
continue;
}
std::string http_req;
cli_sock.Recv(&http_req);
printf("req:[%s]\n", http_req.c_str());
// 响应-首行(版本/状态码/描述)-描述(Content-Length)-空行-正文
std::string body = "Hello, Miss Xia Yanxin! I Love you~~
";
std::string blank = "\r\n";
std::stringstream header;
header << "Content - Length: " << body.size() << "\r\n";
header << "Content-Type: text/html\r\n";
std::string first_line = "HTTP/1.1 200 OK\r\n";
cli_sock.Send(first_line);
cli_sock.Send(header.str());
cli_sock.Send(blank);
cli_sock.Send(body);
cli_sock.Close();
}
lst_sock.Close();
return 0;
}
// 封装实现一个tcpsocket类,向外提供简单接口:
// 使外部通过实例化一个tcpsocket对象就能完成tcp通信程序的建立
#include
#include
#include
#include
#include
#include
#define BACKLOG 10
#define CHECK_RET(q) if((q)== false){return -1;}
class TcpSocket{
public:
TcpSocket():_sockfd(-1){
}
int GetFd(){
return _sockfd;
}
void SetFd(int fd){
_sockfd = fd;
}
// 创建套接字
bool Socket(){
// socket(地址域,套接字类型,协议类型)
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(_sockfd < 0){
perror("socket error");
return false;
}
return true;
}
void Addr(struct sockaddr_in *addr, const std::string &ip, uint16_t port){
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(addr->sin_addr.s_addr));
}
// 绑定地址信息
bool Bind(const std:: string &ip, const uint16_t port){
// 定义IPv4地址结构
struct sockaddr_in addr;
Addr(&addr, ip, port);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
perror("bind error");
return false;
}
return true;
}
// 服务端开始监听
bool Listen(int backlog = BACKLOG){
// listen(描述符,同一时间的并发链接数)
int ret = listen(_sockfd, backlog);
if(ret < 0){
perror("listen error");
return false;
}
return true;
}
// 客户端发起连接请求
bool Connect(const std::string &ip, const uint16_t port){
// 1.定义IPv4地址结构,赋予服务端地址信息
struct sockaddr_in addr;
Addr(&addr, ip, port);
// 2.向服务端发起请求
// 3.connect(客户端描述符,服务端地址信息,地址长度)
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
if(ret < 0){
perror("connect error");
return false;
}
return true;
}
// 服务端获取新建连接
bool Accept(TcpSocket *sock, std::string *ip = NULL, uint16_t *port = NULL){
// accept(监听套接字,对端地址信息,地址信息长度)返回新的描述符
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
// 获取新的套接字,以及这个套接字对应的对端地址信息
int clisockfd = accept(_sockfd, (struct sockaddr*)&addr, &len);
if(clisockfd < 0){
perror("accept error");
return false;
}
// 用户传入了一个Tcpsocket对象的指针
// 为这个对象的描述符进行赋值 --- 赋值为新建套接字的描述符
// 后续与客户端的通信通过这个对象就可以完成
sock->_sockfd = clisockfd;
if(ip != NULL){
*ip = inet_ntoa(addr.sin_addr); // 网络字节序ip->字符串IP
}
if(port != NULL){
*port = ntohs(addr.sin_port);
}
return true;
}
// 发送数据
bool Send(const std::string &data){
// send(描述符,数据,数据长度,选项参数)
int ret = send(_sockfd, data.c_str(), data.size(), 0);
if(ret < 0){
perror("send error");
return false;
}
return true;
}
// 接收数据
bool Recv(std::string *buf){
// recv(描述符,缓冲区,数据长度,选项参数)
char tmp[4096] = {0};
int ret = recv(_sockfd, tmp, 4096, 0);
if(ret < 0){
perror("recv error");
return false;
}
else if(ret == 0){
printf("connection break\n");
return false;
}
buf->assign(tmp, ret); // 从tmp中拷贝ret大小的数据到buf中
return true;
}
// 关闭套接字
bool Close(){
close(_sockfd);
_sockfd = -1;
return true;
}
private:
int _sockfd;
};
云服务器:服务器绑定ifconfig看到的内网地址;但是在浏览器上访问的时候,要访问外网地址;尤其要注意的是要在云服务器的控制台去设置安全做,开启防火墙端口
虚拟机:记住要先关闭防火墙,否则主机无法访问进来
su root 切换管理员
systemctl stop firlwalld 关闭防火墙(虚拟机重启后,又回打开,若要一直关闭可以采用下边的命令禁用防火墙)
systemctl disable firlwalld 禁用防火墙
其实就是加密后的HTTP协议(https设置的端口号为 443 / http: 80)
https到底是如何进行加密传输的?
通过 ssl加密( 非对称加密算法 / 对称加密算法) + 签名证书 保证数据的隐私安全传输
数据直接在网络中传输,很容易被劫持修改,有很大的安全隐患 — 所以要对传输过程进行加密
如何加密就如何解密(加密算法和解密算法是一样的)
对称加密算法的优缺点
解决方案:最好能够每次通信都动态协商一个新的对称加密算法 — 有可能被劫持
加密和解密的方法不同 — 服务端生成一个公钥和私钥,将公钥传递给客户端,客户端使用公钥加密,服务端使用私钥揭秘。
公钥和私钥
通过加密算法-RAS算法,得到的一对密钥(就是两串数据),公钥用于对数据加密,私钥用于对加密后的数据进行解密;
因为加密方式和解密方式不同,因此很难被破解。就算中间公钥被人劫持,客户端使用公钥加密后的数据只能用私钥进行解密。
非对称加密算法的优缺点
将客户端与服务端进行动态协商对称加密算法过程使用非对称加密;然后使用协商后的对称加密算法对数据通信过程进行加密;这样就即保证了安全,也保证了效率。
但是若中间黑客,劫持了公钥数据,然后将自己的公钥发送给客户端
因此公钥的传输也是存在安全隐患:对方的身份问题
如何确定发送公钥的这个服务端就是我心目中的那个服务端?
签名证书(CA):进行身份验证,并且传输公钥信息
注意:
公司生成一对密钥之后,拿着密钥去权威机构掏钱颁发生成一个签名证书,
证书中包含:公钥信息,权威机构信息,当前公司机构信息,有效时间…
ssl加密过程:
A. GET用于信息获取,而且应该是安全的和幂等的,POST表示可能修改变服务器上的资源的请求
B. POST比GET安全,因为采用了SSL加密
C. GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据
D. POST提交,把提交的数据放置在是HTTP包的包体中,GET提交的数据会在地址栏中显示出来
正确答案:A,C,D
答案解析
GET与POST方法有以下区别:
在客户端, Get 方式在通过 URL 提交数据,数据在URL中可以看到;POST方式,数据放置在HTML HEADER内提交。
GET方式提交的数据最多只能有1024字节,而POST则没有此限制。
安全性问题。正如在( 1 )中提到,使用 Get 的时候,参数会显示在地址栏上,而 Post 不会。所以,如果这些数据是中文数据而且是非敏感数据,那么使用 get没什么影响 ;如果用户输入的数据不是中文字符而且包含敏感数据,那么还是使用 post 为好。
安全的和幂等的。所谓 安全的 意味着该操作用于获取信息而非修改信息。幂等的意味着对同一 URL 的多个请求应该返回同样的结果。完整的定义并不像看起来那样严格。换句话说, GET 请求一般不应产生副作用。从根本上讲,其目标是当用户打开一个链接时,她可以确信从自身的角度来看没有改变资源。比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。反之亦然。 POST 请求就不那么轻松了。 POST 表示可能改变服务器上的资源的请求。仍然以新闻站点为例,读者对文章的注解应该通过 POST 请求实现,因为在注解提交之后站点已经不同了(比方说文章下面出现一条注解)。
https采用了SSL加密
如果本篇博文有帮助到您,请留赞激励一下博主呐~~