文章目录
一、应用层协议
传输层TCP/UDP实现了数据的传输,虽然能够传输数据,但这些传输的数据的格式是什么标准,比如客户端想访问服务器的一个网页,客户端该给服务器发什么数据才能够让服务器识别出客户端想访问网页?服务器识别到以后,又以什么样的格式返回给客户端?
1.1. 结构化数据
程序员写的一个个解决实际问题,满足日常需求的网络程序都是在应用层。
协议是一种约定、socket api的都是按照比特位的方式来进行发送和接收的,如果要传输一些"结构化的数据",怎么办呢?
结构化数据:比如发送QQ消息 、消息的组成有:头像、称谓、信息、时间等等,这种数据就叫做结构化数据
1.2.序列化和反序列化
序列化:发送信息的时候、需要将信息多变一。
反序列化:接收信息、将信息一变多。
序列化和反序列化工具(json、xml两种数据格式 ,所以序列化和反序列化建议使用工具)。
二、网络版计算器
例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。
2.1. 协议
protocol.hpp:
//自定义协议
#pragma once
typedef struct Cal
{
int x;//左操作数
int y;//右操作数
char op;//运算符
int result;//运算结果
int code;//状态码,0为正常,1为除零,2为取模零
}cal;
2.2. 服务端
server.hpp:
#pragma once
#include <iostream>
#include<string>
using namespace std;
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include "protocol.hpp"
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define NUM 5
class Server
{
private:
int port;
int listen_sock;
public:
Server(int _port)
:port(_port)
,listen_sock(-1)
{}
void InitServer()
{
//创建套接字
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
cerr<<"listen_sock error!!"<<endl;
exit(1);
}
//服务器绑定,填充服务器信息
struct sockaddr_in ser_add;
ser_add.sin_family=AF_INET;
ser_add.sin_port=htons(port);
ser_add.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&ser_add,sizeof(ser_add))< 0)
{
cerr<<"bind error"<<endl;
exit(2);
}
//监听套接字
if(listen(listen_sock,NUM)< 0)
{
cerr<<"listen error"<<endl;
exit(3);
}
}
void Cal(int sock)
{
cal ret;
string getStr(100,'\0');
size_t size=recv(sock,(void*)getStr.c_str(),getStr.size(),0);
//反序列化
int begin=0,end=getStr.find(" ",begin);
ret.x=atoi(getStr.substr(begin,end-begin).c_str());
begin=end+1;
end=getStr.find(" ",begin);
ret.y=atoi(getStr.substr(begin,end-begin).c_str());
begin=end+1;
end=getStr.find(" ",begin);
ret.op=*getStr.substr(begin,end-begin).c_str();
if(size>0)
{
ret.code=0;
switch(ret.op)
{
case'+':
ret.result=ret.x+ret.y;
break;
case '-':
ret.result=ret.x-ret.y;
break;
case'*':
ret.result=ret.x*ret.y;
break;
case '/':
if(ret.y==0)//除数不能为0
{
ret.code=1;
}
else
ret.result=ret.x/ret.y;
break;
default:
ret.code=2;//表示输入的操作数不符合
break;
}
}
//序列化
string sendStr;
sendStr=to_string(ret.code)+" "+to_string(ret.result)+" ";
send(sock,(void*)sendStr.c_str(),sendStr.size(),0);
close(sock);//短链接
}
void start()
{
sockaddr_in peer;//获取对方信息
socklen_t len =sizeof(peer);
//建立链接
while(true)
{
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
cerr<<"accept error"<<endl;
continue;
}
if(fork()==0)//子进程
{
if(fork() > 0)//子进程退出
exit(0);
close(listen_sock);//孙子进程关闭监听套接字
Cal(sock);//调用任务函数
exit(0);//短链接、调用完就退出
}
close(sock);
waitpid(-1,NULL,0);//等待任意子进程
}
}
~Server()
{
close(listen_sock);
}
};
server.cpp:
void Usage(string str)
{
cout<<"Usage"<<"\t"<<"\n";
cout<<str<<":"<<"please enter your port"<<endl;
}
int main(int argc , char *argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(5);
}
Server *sv=new Server(stoi(argv[1]));
sv->InitServer();
sv->start();
delete sv;
return 0;
}
2.3. 客户端
client.hpp:
#pragma once
#include <iostream>
#include<string>
using namespace std;
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include "protocol.hpp"
#include <netinet/in.h>
#include <arpa/inet.h>
#include<stdlib.h>
#define NUM 5
class Client
{
private:
string ip;
int port;
int sock;
public:
Client(string _ip,int _port)
:ip(_ip)
,port(_port)
,sock(-1)
{}
void InitClient()
{
//创建套接字
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
cerr<<"sock error!!"<<endl;
exit(1);
}
}
void start()
{
//发送给谁、填写信息
sockaddr_in ser_add;
ser_add.sin_family=AF_INET;
ser_add.sin_addr.s_addr=inet_addr(ip.c_str());//字符串转成网络整形
ser_add.sin_port=htons(port);
if(connect(sock,(struct sockaddr*)&ser_add,sizeof(ser_add))!=0)
{
cerr<<"connect error"<<endl;
exit(2);
}
//链接成功,发送信息
cal mes;
cout<<"please enter one num:";
cin>>mes.x;
cout<<"please enter two num:";
cin>>mes.y;
cout<<"please enter op:";
cin>>mes.op;
//序列化
string sendStr;
sendStr=to_string(mes.x)+" "+to_string(mes.y)+" "+mes.op+" ";
send(sock,(void*)sendStr.c_str(),sendStr.size(),0);
string ans(100,'\0');
recv(sock,(void*)ans.c_str(),ans.size(),0);
//反序列化
int begin=0,end=ans.find(" ",begin);
mes.code=atoi(ans.substr(begin,end-begin).c_str());
begin=end+1;
end=ans.find(" ",begin);
mes.result=atoi(ans.substr(begin,end-begin).c_str());
cout<<"code:"<<mes.code<<" "<<"result:"<<mes.result<<endl;
}
~Client()
{
close(sock);
}
};
client.cpp:
#include "client.hpp"
void Usage(string str)
{
cout<<"Usage"<<"\t"<<"\n";
cout<<str<<":"<<"please enter your ip and port"<<endl;
}
int main(int argc , char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(5);
}
Client *ct = new Client(argv[1],atoi(argv[2]));
ct->InitClient();
ct->start();
delete ct;
return 0;
}
2.4. 效果展示
在发送和接收数据时,可以直接发送或接收结构体,但是这种方法是不推荐的。因为不在同一台机器上时,用结构体来进行发送,内部对齐解释可能会不一样。将结构体内容序列化为字符串然后发送,接收以后再反序列化可以解决这一问题。
三、HTTP协议
虽然我们说, 应用层协议是程序员自己定的。但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
3.1. URL
URL就是平时俗称的网址。
- 协议:使⽤ http或https等协议⽅案名获取访问资源时要指定协议类型。不区分字⺟⼤⼩写,最后附⼀个冒号(:)。
- 登录信息(认证): 指定⽤户名和密码作为从服务器端获取资源时必要的登录信息(身份认证)。此项是可选项。
- 服务器地址 :必须指定待访问的服务器地址。地址可以是类似 hackr.jp 这种 DNS 可解析的名称,或是192.168.1.1 这类 IPv4 地址 名,还可以是 [0:0:0:0:0:0:0:1] 这样⽤⽅括号括起来的 IPv6 地址名。
- 服务器端⼝号:指定服务器连接的⽹络端⼝号。此项也是可选项,若⽤户省略则⾃动 使⽤默认端⼝号。
- 带层次的⽂件路径:指定服务器上的⽂件路径来定位特指的资源。
- 查询字符串:针对已指定的⽂件路径内的资源,可以使⽤查询字符串传⼊任意参 数。此项可选。
- ⽚段标识符:使⽤⽚段标识符通常可标记出已获取资源中的⼦资源(⽂档内的某个 位置)。该项也为可选项。
3.1.1. urlencode(编码) 和 urldecode(解码)
在url之中并,有一些特殊的符号,比如 / ? 等都已经被当做特殊意义理解了,因此这些字符不能够随意出现。
如果某个参数之中,需要这些特殊字符,比如汉字,就必须对这些特殊字符进行转义。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位前面加上%,编码成%XY。
比如"“,它的ASCII码是92,92的十六进制是5c,所以”“的url编码就是%5C。而汉字,比如:“胡"的ascii码是-17670,十六进制是BAFA,url编码是”%BA%FA”。
urldecode就是urlencode的逆过程;
在线URL编码解码工具-UrlEncode编码和UrlDecode解码
3.2. HTTP特征
3.2.1. 无连接的:
http底层是基于tcp的,但是http不关心底层的实现,一旦tcp建立链接成功,不需要http再次进行链接的建立。tcp建立链接和http无关,http直接向对方直接发送 http request即可
3.2.2. 无状态的:
tcp本身是有状态的,与http无关。
http并不会记录用户的任何信息,只会接受请求与响应(request <-> response),记录信息的为 cookie + session
3.2.3. 简单快速:
短链接(一来一回就断开)进行文本(网址、图片、音频)传输。
早期http/1.0版本只有短链接,http/1.1版本具有长链接。
3.3. HTTP协议格式
3.3.1. HTTP请求
- 请求首行:方法 + url(资源所在路径) + 版本 (以行为读取单位,即\n为标识)
常见的方法有GET和POST方法:
- GET通过url传参。url传参是有上限的(取决于系统,web服务器)正文传参理论上是无长度限制的。
- POST通过正文传参。POST相对来说比较安全,POST更加的私密,不会将数据显示在其它人的眼中。如果不对正文加密,POST同样是不安全的。
- 请求报头(header):请求的属性,都是冒号分割的键值对,每组属性之间使用\n分隔,遇到空行标识header部分结束。Content-Length表示空行之后多少个字节是正文部分。
- 空行:将报头和有效载荷分离开来。
- 请求正文(body):空行后面的内容都是请求正文,如果请求正文存在,则在header之中会有一个Content-Length属性来标识body的长。GET方法可以不要正文,POST需要携带正文,这些正文就是要上传的某些数据。
3.3.2. HTTP响应
-
首行:版本号(http/1.1)+ 状态码(200/404) + 状态码描述(对状态码进行解释,200表示OK,404表示not found),以\n结束
-
响应报头(header):请求的属性,都是冒号分割的键值对,每组属性之间使用\n分隔,遇到空行标识header部分结束。Content-Length表示空行之后多少个字节是正文部分。
-
空行:将报头和有效载荷分离开来。
-
响应正文(body):空行后面的内容都是请求正文,如果请求正文存在,则在header之中会有一个Content-Length属性来标识body的长度。
四、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 |
UNLINE | 断开连接关系 | 1.0 |
4.1. GET:获取资源
GET 方法用来请求访问已被 URI 识别的资源。指定的资源经服务器端解析后返回响应内容。也就是说,如果请求的资源是文本,那就保持原样返回;(GET既能从服务器中去获取数据,也能向服务器中提交少量数据,提交的数据在URL中)。
4.2. POST:传输实体主体
虽然用 GET 方法也可以传输实体的主体,但一般不用 GET 方法进行传输,而是用 POST 方法。虽说 POST 的功能与 GET 很相似,但 POST 的主要目的并不是获取响应的主体内容。(给服务器提交某些数据,提交的数据在正文中)。
假设我们要在某个网页进行登录,使用POST请求就会将我们的账号,密码放在请求正文中进行提交。
注意:
- POST方法比GET方法更私密,POST方法传参不会回显到URL中,但仍然是以正文的方式传入的,除非进行加密,否则还是会有安全风险。
- GET方法传参有长度限制,POST方法由于是正文传参,没有长度限制。
4.3. PUT:传输⽂件
PUT ⽅法⽤来传输⽂件。就像 FTP 协议的⽂件上传⼀样,要求在请求报⽂的主体中包含⽂件内容,然后保存到请求 URI 指定的位置。 但是,鉴于 HTTP/1.1 的 PUT ⽅法⾃身不带验证机制,任何⼈都可以上传⽂件 , 存在安全性问题,因此⼀般的 Web ⽹站不使⽤该⽅法。
4.4. HEAD:获得报⽂⾸部
HEAD方法和GET方法一样,只是不返回报文主体部分,用来确认资源的有效性。HEAD方法是不需要服务端返回响应正文的,使用HEAD方法,服务器只会返回响应首行、响应报头、空行。
4.5. DELETE:删除⽂件
DELETE ⽅法⽤来删除⽂件,是与 PUT 相反的⽅法。但是, HTTP/1.1 的 DELETE ⽅法本身和 PUT ⽅法⼀样不带验证机制,所以⼀般的 Web ⽹站也不使⽤ DELETE ⽅法。
4.6. OPTIONS:询问⽀持的⽅法
OPTIONS方法用来查询针对请求URL指定的资源支持的方法。即客户端询问当前服务器都支持哪些方法。
五、HTTP的状态码
- | 类别 | 原因短语 | 例子 |
---|---|---|---|
1xx | Informational(信息性状态码) | 接受的请求正在处理中 | 请求周期比较长的 |
2xx | Success(成功状态码) | 请求正常处理完毕 | 返回状态码200,表示请求正常 |
3xx | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | 登录A网址 跳转->B网站 |
4xx | Client Error (客户端错误状态码) | 服务器无法处理请求 | 400/401/403/404 客户端要的资源服务器找不到(不合理的要求) |
5xx | Server Errot(服务器错误状态码) | 服务器处理请求出错 | 比如,短时间内创建大量进程,导致服务器宕机 |
2XX 成功
2XX 的响应结果表明请求被正常处理了。
200(OK)
表示从客户端发来的请求在服务器端被正常处理了。
204(No Content)
请求处理成功了,但没有资源要返回(没有正文):
206(Partial Content)
客户端进行了范围请求,服务器成功执行这一请求:
3XX响应结果表明浏览器需要进行附加操作以完成请求。
301(Moved Permanently) 永久性重定向
告诉服务器某一资源已被永久放在另一个URL中,以后访问需访问新的URL。
如果域名、服务器、网站架构发生了大幅度的改变,比如启用了新域名、服务器切换到了新机房、网站目录层次重构,这些都算是“永久性”的改变,原来的URI已经不能用了,必须用301“永久重定向”,通知浏览器和搜索引擎更新到新地址。
302(Found) 临时性重定向
客户端要请求的资源临时被放到新的服务器中,以后访问此资源还是访问这个旧服务器。
原来的URI在将来的某个时间点还会恢复正常,常见的应用场景就是系统维护,把网站重定向到一个通知页面,告诉用户过一会儿再来访问。另一种用法就是“服务降级”,比如双十一促销的时候,把订单查询、领积分等不重要的功能入口暂时关闭,保证核心服务能够正常运行。
303(See Other)
要访问的资源已经更新。
4XX 客户端错误
4XX的响应结果表明客户端发生错误。
400(Bad Request)
服务端无法理解客户端发送的请求:请求格式错误。
401(Unauthorized)
认证失败。
403(Forbidden)
客户端请求访问某一资源被服务器拒绝了,即没有资格(权限)访问某一资源。
404(Not Found)
服务器无法找到客户端请求的资源。
5XX 服务器错误
5XX的响应结果表明服务器处理请求出错
500(Internal Server Error)
该状态码表明服务器端在执行请求时发生错误,也有可能是Web应用存在的bug或某些临时的故障。
503(Service Unavailable)
服务器繁忙。
六、HTTP常见Header
- Content-Type: 数据类型(text/html等),描述的是正文的数据类型。
- Content-Length:Body的长度描述的是正文的长度。客户端和服务器都有可能携带。
- Host:客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上。
- User-Agent: 声明用户的操作系统和浏览器版本信息。
- referer:当前页面是从哪个页面跳转过来的。
- location:搭配3xx状态码使用, 告诉客户端接下来要去哪里访问。
- Cookie:用于在客户端存储少量信息. 通常用于实现会话(session保存在服务器端)的功能。
6.1. Cookie
6.1.1. 为什么需要cookie
http是无状态的,比如用户登录一个网址去看电影,看完一部电影之后,想接着看第二部,由于http是无状态的,这时就需要再次输入账号和密码进行登录。http这种无状态的特征会给用户造成非常差的体验感。
cookie就是游览器中的一个文件,这个文件分为内存级和硬盘级(写在本地文件中,比如保存密码)。QQ的记住密码就和cookie类似。
在客户端第一次访问服务器的时候,填写了账号和密码进行了登录,会向服务器上传账号和密码,服务器拿到账号和密码后,会和服务器中注册的账号和密码进行对比,对比成功则用户登录成功。登录成功的同时,服务器会将账号和密码写入到set-cookie之中发给浏览器,浏览器会以文件的形式保存这个set-cookie信息。客户在下次访问的时候,会在http请求当中自动提交cookie,服务器自动解析cookie之中的内容,这样就不要多次填写登录信息了。
6.1.2. cookie安全问题的解决(相对解决)
- 用户第一次请求服务器时,服务器端会生成一个sessionid
- 服务器端将生成的sessionid返回给客户端,通过set-cookie
- 客户端收到sessionid会将它保存在cookie中,当客户端再次访问服务端时会带上这个sessionid
- 当服务端再次接收到来自客户端的请求时,会先去检查是否存在sessionid,不存在就新建一个sessionid重复1,2的流程,如果存在就去遍历服务端的session文件,找到与这个sessionid相对应的文件,文件中的键值便是sessionid,值为当前用户的一些信息
- 此后的请求都会交换这个 Session ID,进行有状态的会话。
七、抓包
7.1. tcpdump
tcpdump:传输层的协议基本可以抓
tcpdump -i 指定sniffer操作的侦听端口,比如:tcpdump -i eth0 , tcpdump -i lo0 。
tcpdump -i any 只要发送至这台主机的都要抓。
-n:主机名这些能显示成数字就显示成数字。
-nn:将更多的信息显示成数字。
三次握手:
四次挥手:
7.2. 应用层抓包工具fiddler
7.2.1. 抓包原理
7.2.2. 抓包内容
7.3. 通过linux命令行获取响应信息
7.4. wget
wegt在底层创建套接字,连接上服务器,然后给服务器发送请求,然后接收服务器发回的数据写到文件里、
八、HTTP简单实现和浏览器的交互
服务器创建套接字以后,通过浏览器访问服务器,此时服务器向浏览器发送HTTP协议,在请求正文中包含HTML代码。
8.1. 代码实现
httpServer.hpp:
#pragma once
#include <iostream>
using namespace std;
#include <string>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class HttpServer
{
private:
int port;
int listen_sock;
public:
HttpServer(int _port)
:port(_port)
,listen_sock(-1)
{}
void InitServer()
{
signal(SIGCHLD,SIG_IGN);
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0)
{
cerr<<"sock error"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(listen_sock,5) < 0)
{
cerr<<"listen error"<<endl;
exit(4);
}
}
void EchoHttp(int sock)
{
char request[2048];
size_t s=recv(sock,request,sizeof(request)-1,0);//!!!假设recv每次可以读完一个报文
if(s>0)
{
request[s]=0;
cout<<request<<endl;//http全是文本,不做处理直接输出
string response="HTTP/1.0 200 OK\r\n";//响应首行
response+= "Content-type: text/html\r\n"; //Content-Type:数据类型(text/html等),响应一个html页面
//空行
response+="\r\n";
//正文
response+="\
<!DOCTYPE html>\
<html>\
<head>\
<meta charset='UTF-8'>\
<title>嘉然今天吃什么</title>\
</head>\
<body>\
<h1>B站关注嘉然</h1>\
<p>关注嘉然,顿顿解馋!</p>\
</body>\
</html>";
send(sock,response.c_str(),response.size(),0);
}
close(sock);//短链接
}
void start()
{
while(true)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
cerr<<"accept error"<<endl;
continue ;
}
cout<<"get a new link"<<endl;
//创建子进程,子进程执行提供网页的服务
if(fork()==0)
{
close(listen_sock);
EchoHttp(sock);
exit(0);//短链接
}
close(sock);
}
}
~HttpServer()
{
if(listen_sock!=-1)
close(listen_sock);
}
};
httpServer.cpp:
#include "httpServer.hpp"
void Usage(string str)
{
cout<<"Usage:"<<str<<endl;
cout<<"please enter port"<<endl;
}
int main(int argc,char *argv[])
{
if(argc!=2)//程序和端口
{
Usage(argv[0]);
exit(1);
}
HttpServer *hp=new HttpServer(atoi(argv[1]));
hp->InitServer();
hp->start();
delete hp;
return 0;
}
8.2. 效果展示
访问ip地址:端口号
当然,也可以使用location进行重定向操作:
另外,协议只是给游览器的一种参考,比如将状态码改成404,还是可以访问到网页,并且捕捉的状态码也是404。
这是因为,状态码只是给游览器的一种参考,真正实现还需要返回一个404的页面。
本次实验网页是编码在代码里面的,通常是将html全部放在web服务器某一个目录之中,这就是通常访问的资源(同理,可以放入音频、视频、等等资源)
九、HTTPS
9.1. HTTP的缺陷
HTTP的速度相对于HTTPS来说,是更快一些的,但是HTTP传输的内容是没有经过加密的,因此用户层的一些隐式数据很容易就被暴露,主要有以下几种不足:
1.通信使用明文(不加密),可能被窃取
2.不验证通信方的身份,因此可能遭遇伪装
3.无法验证报文的完整性,所以有可能遭到篡改
9.2. HTTPS是什么
https本质上也是http,只是,https对用户层的数据进行了加密,使得应用层用户的数据更加的安全。
9.3. 对称加密
客户端有一个密钥,将数据用密钥加密以后发给服务器。服务器用密钥对数据进行解密,就叫做对称加密。
但是客户端如何保证密钥安全地传给服务器就是个问题。
9.4. 非对称加密—密钥协商
非对称加密有两种密钥:公钥、私钥。
通常情况下:公钥用来加密,私钥用来解密。
https密钥协商过程:
服务器将公钥发给客户端,客户端用公钥将自己的密钥加密以后发给服务器,然后服务器用私钥进行解密,拿到客户端发来的密钥。这样双方都有了密钥,开始对称加密通信。
为什么不使用非对称的方式进行加密呢?
非对称加密的比较复杂,并且效率比较低。对称加密相对来说是比较快的。
密钥协商过程是由SSL/TLS来做的。
9.5. 防止数据信息被篡改
如何解决远端服务器的身份认证问题,客户端收到的公钥是否合法?
如何解决中间信息被篡改的问题?
数据经过加密算法(比如hash算法)后,形成与数据具有映射关系的数据摘要(也叫做数据签名/指纹),并对数据摘要用密钥加密,服务器将数据和数据摘要都发给客户端。一旦对内容进行了修改,客户端通过加密算法产生的数据摘要就会与服务器发来的数据摘要不相同,客户端就能识别到内容被修改了。