再谈协议
在之前利用套接字进行通信的时候,我们都是利用 “字符串” 进行流式的发送接收,但是我们平常进行交流通信肯定不能只是简单的发送字符串。
比如我们用QQ进行聊天,我们不仅需要得到对方发送的消息,还要知道对方的昵称,头像等一系列数据,也就是说我们需要得到一个结构化的数据!
而想要正确的得到一个结构化的数据,就需要我们定制一层应用层协议或者使用特定的应用层协议。
应用层协议
无论是哪一层的协议,我们都会给数据添加自定义的报头,应用层协议的报头也不例外。
报头:该层协议所需要的一些特定的数据
而只有添加了报头后,再将数据根据协议内容来序列化或者反序列化才能保证正确传输数据。
序列化:将结构化的数据通过协议内容转化为可以进行网络传输的形式(如字符串)
反序列化:将接收到的数据通过协议内容转化为结构化的数据
而添加报头就需要明确报文和报文边界,有以下三种方式
- 定长:使用固定的长度表示报文
- 特殊符号:用特殊符号分隔报文和报头
- 自描述方式:报文内部包含了报文长度等一些能够描述报文边界位置的信息
Http协议
虽说我们能够自己定义协议,但是定义协议费时费力,还不能出差错。
而现实中已经有很多大佬给我们定制了现成的协议,让我们使用,比如——Http协议;
Http默认端口---80;
URL
- URL:统一资源定位符(也就是网址)
http协议的URL格式如上。
实际上,我们访问的资源都是存储在对应网站的服务器的磁盘上的,因此我们访问的URL也需要带上访问的文件的路径。
而这个服务器地址就是服务器的ip地址。
最后,ip地址加上端口号和文件路径,就能够成功的请求到服务器的资源。
urlencode和urldecode
在URL中,有一些特殊字符被当做特殊意义理解了,比如 '/','@' 之类的,因此当我们使用浏览器进行搜索的时候,浏览器会将搜索的内容做一些特殊处理。
比如我们搜索c++,就会发现 ++ 这两个字符被替换成了 '%2B' 的样式,这就是urlencode;
而 urldecode 则是将对应的字符翻译成原来的模样。
- urlencode : 将中文和特殊字符给转义化
- urldecode :将url请求中的转移化的字符进行解码
每当我们向服务器请求资源时,浏览器就会向服务器发起请求。
Http请求格式
每一个浏览器向服务器发送请求时,浏览器会发送这么长一串报头给服务器。
这个报头有很多信息,比如 Conten-Length(正文长度)等一些信息。
- 首行格式: 【方法】 + 【url】+ 【http协议版本】
- Header:请求的属性,冒号分割的键值对,每组之间用 '\n'分割,在Header之后便是空行
- body:空行后面的内容都是Body, Body允许为空字符串. 如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度
当服务器收到请求后,就会发送对应的响应。
Http响应格式
- 首行:【版本号】+【状态码】+【状态码解释】
- Header:请求的属性,以冒号分割的键值对,每行以'\n'分割,遇到空行Header结束
- body:空行后就是body,允许body为空字符串,若body存在,Header中会由一个Content-Length来表示body长度,若返回的是一个html页面,那个html页面内容就在body中
Http的方法
在这么多方法中,Get和Post是用的最多的,那么Get和Post之间有什么区别呢?
使用Get方法
当我们使用Get方式提交表单的时候,我们发现URL中会存在我们输入的用户名和密码
也就是说Get方法是通过URL来传参的。
使用Post方式
而是用Post方式来访问则不会在URL中看到账号和密码
Post方式中的账号和密码都是存储在正文当中的。
Get和Post的区别
- Get通过URL传参,Post通过正文传参,Post更有私密性
- Get能够传递的参数长度有限,Post则没有该限制
- Get请求的参数会保存在浏览器记录中,Post则不会
虽说Post更具有私密性,但是实际上它们二者都不是安全的,只是Post更加具有私密性罢了
Http状态码
- 常见的状态码有 200(OK),404(Not Found),403(Forbidden),302(Redirect)等等。
其中3开头的状态码需要详细讲一下。
重定向分为临时重定向和永久重定向。
- 临时重定向:状态码302,对旧网址没有影响,但新网址没有排名
- 永久重定向:状态码301,新网址完全继承旧网址,旧网址的排名清零
现象:
- 永久重定向时,浏览器会将输入栏网址自动更新为新网址
- 临时重定向时,浏览器的输入栏网址依旧为旧网址,但是内容是新网址
Http常见Header
- Content-Type:数据类型(text/html之类的)
- Content-Length:Body的长度
- Host:客户端告知服务端,所请求的资源在哪个主机上
- User-Agent:声明用户所用的操作系统和浏览器版本信息
- referer:当前页面是从哪个页面跳转过来的
- location:搭配3xx的状态码,告知客户端跳转到哪个页面
- Cookie:用于在客户端存储少量信息,用于实现Session功能
长连接
一张完整的网页一般由多种元素组成,因此需要多次请求,但是http是基于TCP协议的,而TCP是面向连接的,因此请求一张网页也许会存在频繁创建连接的问题。
而长连接就是为了解决该问题而存在的。
它通过建立一条链接,获取资源的任何行为都必须通过这个链接完成,从而解决频繁创建连接的问题。
http会话保持
会话保持这个功能是用户在实际体验过程中所需要的一个功能,表面现象就是当你登录了一个网站,那么你即使退出了该网站或者退出了该浏览器,下一次访问该网站时,网站依旧知道你是哪个用户。
会话保持有两种方法。
老方法
老方法是通过在客户端保存用户信息,每次访问同一个网站浏览器都会自动推送历史保留信息,从而实现会话保持。
而这个信息称为cookie。
Cookie
- 用来保存用户信息
- cookie文件:浏览器关闭后cookie会以文件形式保存下来
- cookie内存:浏览器关闭后cookie不会以文件形式保存下来
但是老方法有一个问题,那就是cookie信息是保存到客户端的,一般的客户端很难防止木马盗取用户信息,就会导致用户账号被盗。
而新方案则另辟蹊径。
新方案
新方案将用户信息存在服务器中,用session保存文件,并且返回对应的session id,而每次客户端请求就是通过session id 来访问对应的session文件。
虽然客户端的session id 依旧可能会被盗,但是这样至少可以保证用户的信息没有被泄露。
而且服务器可以通过查看用户的 ip 地址等方案来判断 session id 是否需要失效,从而保证了用户信息的安全。
Https协议
了解了一些http协议的细节,那么自然也需要来了解Https协议。
https协议实际上就是单纯的在http协议和传输层之间添加了一层加密协议,从而保证用户信息的安全。
由于http协议内容都是明文传输的,因此需要添加加密协议防止用户信息被篡改。
什么是加密
- 将明文通过一系列变换生成密文。
- 将密文通过一系列变换生成明文。
一般加密过程中,都需要多个密钥来辅助加密和解密。
为什么加密
有时候我们上网下载东西时,明明搜索下载的是A,但是下载完却发现下载的是B,这就是我们在请求下载时,响应被劫持了。
我们在网络上传输的任何数据包都会经过运营商的网络设备(路由器等),运营商的网络设备就可以解析出你传输的数据内容并进行篡改。
当我们未被劫持时,我们下载的东西就是正确的。
而当我们被劫持了,下载的东西就是别的软件了。
因此我们需要通过加密来防止这种中间人攻击。
加密方式
为了防止中间人攻击,https退出了两种加密方式。
对称加密
- 采用单钥密码系统的加密方法,一个密钥可以用来加密和解密。
- 常见算法:DES,3DES,AES,TDEA,RC2等。
- 特点:算法公开,计算量小,加密速度快,加密效率高。
非对称加密
- 需要两个密钥来进行加密和解密,分别是公开密钥(public key)和私有密钥(private key)。
- 常见算法:RSA,DSA,ECDSA。
- 特点:算法强度复杂,安全性依赖于算法和密钥,但是由于算法复杂,使得加密速度没有对称加密快。
非对称加密有一个特点,那就是两个密钥可以反着使用。
可以用公钥加密,私钥解密,也可以用私钥加密,公钥解密。
数据摘要&&数据指纹
- 数据摘要是通过单向散列函数对信息进行运算,生产一段固定长度的数字摘要。
- 常见算法:MD5,SHA1,SHA256等,由于是将无限生产有限,有可能出现碰撞,但是概率很小。
- 特点:并不是一种加密方式,无法通过数据摘要反推原信息,通常用来进行数据对比。
数字签名
- 将数据摘要进行加密生产的签名。
https的加密方案
当我们了解到https的加密手段后,我们再来看看加密方案。
方案一:都使用对称加密
在客户端和服务器之间的交流之中,都是通过客户端发送请求后,服务器再将密钥X发送给客户端。
乍一看,也许没问题,但是如果有中间人攻击的话,很容易就能将数据劫持掉。
而且由于密钥是明文传送的,黑客持有了密钥,后续的加密操作也没有作用了。那么无论客户端发送什么密文给服务器,黑客都能够随意修改。
也许有同学会想,那我将密钥给加密怎么样呢?
这就陷入了一个套娃的循环了。
方案二:都采用非对称加密
如图所示,采用非对称加密时,服务器发送的响应必须通过私钥加密,但是私钥加密的密文可以通过公钥解密,此时黑客已经劫持了公钥,那么响应就能够被随便修改了。
方案三:双方都是用非对称加密
当双方都采用非对称加密时,客户端将数据通过 FS 加密,因此只有服务器能够解密,而服务器发送的响应能够通过客户端的 KS 加密,因此只有客户端能够解密。
但是这样有一个缺点:慢。
之前提过,非对称加密很慢,而这样双方都使用非对称加密就更慢了。
方案四:对称加密+非对称加密
像这样,服务器明文发送了公钥给客户端,客户端内部决定密钥为 X 然后通过 FS 加密发送给服务器,这样客户端和服务器就通过 密钥 X 来加密,而且即便中间人后面来劫持也无法篡改密文。
但是这样依旧有一个问题——那就是假如一开始中间人就开始劫持会怎么样?
如果中间人一开始就劫持了,那么中间人就能够篡改发送的公钥 FS,这样后面的加密解密就都在中间人的监视下了。
而这个问题上面四个方案都存在。
中间人攻击
在上面几个方案中,只要中间人一开始就已经开始攻击了,那么中间人能够轻易获取公钥并篡改公钥为中间人自己的公钥,而且服务器和客户端都无法察觉,这样客户端和服务器之间的交流都会被中间人获取到。
- 服务器具有⾮对称加密算法的公钥S,私钥S'
- 中间⼈具有⾮对称加密算法的公钥M,私钥M'
- 客⼾端向服务器发起请求,服务器明⽂传送公钥S给客⼾端
- 中间⼈劫持数据报⽂,提取公钥S并保存好,然后将被劫持报⽂中的公钥S替换成为⾃⼰的公钥M, 并将伪造报⽂发给客⼾端
- 客⼾端收到报⽂,提取公钥M(⾃⼰当然不知道公钥被更换过了),⾃⼰形成对称秘钥X,⽤公钥M加密X,形成报⽂发送给服务器
- 中间⼈劫持后,直接⽤⾃⼰的私钥M'进⾏解密,得到通信秘钥X,再⽤曾经保存的服务端公钥S加密后,将报⽂推送给服务器
- 服务器拿到报⽂,⽤⾃⼰的私钥S'解密,得到通信秘钥X
- 双⽅开始采⽤X进⾏对称加密,进⾏通信。但是⼀切都在中间⼈的掌握中,劫持数据,进⾏窃听甚⾄修改,都是可以的
为了解决这个问题,我们需要了解一个新概念。
证书
CA认证
每个服务器在使用HTTPS之前,都要向CA申请一份数字证书,数字证书中包含申请者信息,公钥信息等。
而有证书的服务器会将证书传输给客户端,客户端直接从证书中获取公钥。
每个证书中包含了以下信息:
- 证书发布机构
- 证书有效期
- 公钥
- 证书所有者
- 签名
CA机构中有他们自己维护的公钥和私钥,由此来保证证书的可靠性。
那么具体过程是如何做的呢?
数据签名
CA机构的证书通过数据签名来保证证书的可靠性。
当服务器申请证书时,CA在对服务器进行审核后,会为该网站专门形成数字签名。
签名
- CA首先将服务器的数据通过单向散列函数来生成数据指纹。
- 然后通过CA的私钥来生成数据签名
- 最后将明文信息和数据签名放在一起形成证书
验证
- 最后客户端验证则是将证书拆开
- 数据签名通过CA公钥解密成数据指纹(每个浏览器中都有CA的公钥)
- 明文信息则通过散列函数生成散列值
- 最后判断数据指纹是否相同
需要验证的信息:
- 证书是否过期
- 证书的发布机构是否可信
- 验证证书是否被篡改
方案五:采用证书+对称加密+非对称加密
最后方案5是在方案4的基础上加入证书。
- 在最开始建立连接后,服务器发送的并非单纯的公钥,而是证书
- 当客户端收到证书的时候就需要验证证书是否可信
- 之后再通过证书中包含的服务器的公钥来加密密钥
- 这样后续密文的解密和加密都十分便捷了
证书有没有可能被中间人篡改?
- 即便中间人篡改了证书,他依旧没有CA机构的私钥,就无法在hash后用私钥加密形成签名,也就没办法对篡改后的证书形成匹配的签名
- 若强行篡改,那么客户端会检测到明文和签名解密后的值不同,客户端会终止传输信息,防止泄露。
证书有没有可能被整个掉包?
- 中间人没有CA私钥,因此无法制作假的证书
- 中间人只能申请真的证书来替换证书
- 但是证书中还有域名,若是域名不一致,依旧会被检测到
- 记住:中间人没有CA私钥,任何证书都无法被合法修改,包括自己
为什么摘要需要使用CA私钥加密?
由于摘要算法的特性:定长,分散,不可逆,导致我们判断证书是否相同的标准是摘要是否一致,若是不采用CA私钥加密,客户端没有CA的公钥来进行解密,中间人只需要将内容修改,同时将摘要修改,那么客户端就无法分辨了。
而有了CA私钥加密,就能够保证中间人无法篡改证书了。
为什么不直接加密而先形成摘要呢?
为了减少密文的长度,加快数字签名的速度。
HTTPS的完整流程
总结:
HTTPS工作一共有三组密钥。
- 非对称加密:用于检验证书是否被篡改。客户端天然持有CA机构的公钥,服务器发送证书给客户端,客户端通过该公钥验证证书是否被篡改
- 非对称加密:用于协商对称加密的密钥。客户端在证书中获取服务器的公钥,用公钥给密钥加密,发送给服务器
- 对称加密:服务器和客户端通过该密钥进行加密解密。
传输层
Http和Https两个协议都是在应用层的协议,而传输层则是负责将数据从发送端传输到接收端
再谈端口号
端口号是用于标示一台主机上进行通信的不同程序;
在TCP/IP协议中,用“源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号”这五元组来表示一个通信。
端口号划分
我们平时使用端口号时,会发现有的端口号不可以使用。
这是因为端口号是有范围的。
- 0~1023:知名端口号,由HTTP,SSH等广为人知的应用层协议使用
- 1024~65535:操作系统动态分配的端口号
一些知名端口号
- SSH服务器:22端口号
- ftp服务器:21端口号
- telnet服务器:23端口号
- HTTP服务器:80端口号
- HTTPS服务器:443端口号
平时我们自己使用端口号时,需要避开这些端口号。
UDP协议
UDP协议格式
想要了解UDP协议,就必须了解它的协议格式。
所谓协议实际上可以看做是结构化的数据。
UDP也不例外,它的报头采用的是定长大小的策略,报文包含了8字节大小的报头。
报头中包含了源端口号,目的端口号,报文长度等信息,用来传递信息。
- 16位UDP长度包括报头和报文的长度
- 若是校验出错,则会直接丢弃
UDP特点
- 无连接:知道目的端口号和IP地址即可,不用建立连接
- 不可靠:没有确认机制和重传机制,若因网络故障而无法发送信息给对方,UDP也不会返回任何错误信息
- 面向数据报:不能够灵活的控制读写数据的次数和数量
面向数据报
UDP的特点中需要注意的是面向数据报这个特点。
使用UDP协议传输的数据一次传输的数据不能拆分也无法合并,UDP会原样发送。
注意事项
由于UDP的UDP长度是16位,因此UDP报文的长度最长不过64K(包括报头),因此我们使用UDP传输数据时,需要先在应用层将数据分成64K大小后再传输,接收端则在接收后手动拼装。
UDP的缓冲区
- 发送缓冲区:UDP并没有发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输操作
- 接收缓冲区:虽然能够接受报文,但是无法保证收到的报文顺序和发送的报文顺序一致
UDP客户端通过 send/write 将数据发送到网络中,然后服务器把数据放到接收缓冲区中后,服务器通过报头判断自己是否为目的地,再从接收缓冲区中获取信息,否则直接丢弃。
而UDP协议之中,它的socket既可以读又可以写,因此是双全工的。
基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
此外还有用户自己UDP程序在应用层配置的协议。
TCP协议
TCP协议全称为“传输控制协议”,正如它的名字,它需要对数据的传输进行详细的控制。
TCP协议格式
- 16位校验和:发送端填充,CRC校验,不通过认为数据有问题,丢弃。检验部分是整个报文
而我们都知道TCP协议的特点是:有连接,可靠,面向字节流。其三大特点就是由其报头造成的。
TCP报头
TCP长度
TCP的报头是一个自描述的变长报头。
首先,它的报头固定大小为20字节,其中有一个变量是4位首部长度,用来表示报头+选项的总长度。
报头总长度:4位首部长度 * 4字节。
4位首部长度的单位是4字节,假设报头是40字节,那么4位首部长度值为10.
TCP解包就是通过4位首部长度确定报头长度,然后再一直读取到下一个报头的位置,就读取了一个完整的报文了。
16位窗口大小
在TCP传输中,传输数据的速度不可过快过慢,应该根据对方的接收缓冲区的剩余空间大小来决定发送多少数据。
因此使用TCP发送数据时,都会往16位窗口大小填充自己接收缓冲区的剩余空间大小来进行流量控制。
六个标记位
我们TCP平时可能会传各种各样的数据,有的是普通的数据段,有的是确认数据段(ACK)。
因此TCP协议使用了六个标记位来表示不同的报文。
- ACK:确认标记位,表示是否为确认报文
- PSH:对方的接收缓冲区已满时,告知对方尽快读取缓冲区中的数据
- URG:表示紧急指针是否有效
- RST:对方要求重新建立连接,带有RST的报文称为复位报文段
- SYN:请求报文段,携带该标记位的称为同步报文段
- FIN:通知对方,本端要关闭了,带有FIN的称为结束报文段
其中有几个标记位需要展开说明。
URG标记位
一般报文都会有序号保证报文按序到达,但是有时候也有高优先级的报文,这时候就需要插队了。
带有URG标识位的报文就表示该数据要尽快读取。
这种报文就需要去16位紧急指针,该指针通过记录紧急数据在有效载荷的偏移量来指向紧急数据。
一般紧急数据只有一字节。
RST标记位
若是三次握手与四次挥手失败了,或者说通信过程中单方面出错了,出错的一方认为连接不存在了, 但是对方依旧认为连接存在,对方依旧会发送报文,此时出错的一方收到报文后就会返回一个带有RST的报文,来重新建立连接。
TCP的可靠性
TCP有一个最大的特点就是它的可靠性,它能够保证数据能够成功传输给对方,若是失败也会告知用户,那么它是如何做到的呢?
用过网购的童鞋们都知道,我们收到网购的东西后,需要在软件上确认收货商家才能收款,否则商家就无法确认你是否收到了商品。
而TCP也是如此,当客户端发送请求或者数据给服务器时,服务器需要返回一个确认数据段给客户端,这样客户端才能知道服务器确实收到了数据。
当然,这里服务器并不是单纯的返回一个确认数据段,而是一段报文,该报文的报头有一段数据就是确认数据段,在TCP中,这段确认数据段就是报头中的32位确认序号。
确认应答(ACK)机制
每次TCP的发送段发送数据的时候,TCP协议会给每一个字节的数据添加一个序号,这个序号记录在32位序号中,当接收段接收数据的时候,接收端会根据这个序号进行排序,并判断是否丢失数据。
而当接收端返回一个ACK时,就会在32位确认序号中返回最后一个位序号的值+1,告知对方已成功接收确认序号之前的所有数据,只用从确认序号开始发送数据即可。
这样就保证接收端能够将报文全部接收,并且发送端能够知道该从哪里继续发送。
此外由于TCP协议也是全双工的,双方都要相互发送数据,相互应答,因此会有两组序号分别记录对方的位序号。
超时重传机制
为了保证可靠性,TCP还有超时重传。
该机制分为两种情况。
- 数据未发送给对方,在一定时间间隔后未收到应答就重传。
- 数据发送到对方了,但是对方的应答发送失败了。
- 这样A在一定时间后就会重新发送一次数据。
第一种情况很简单,重点是第二种情况,主机B重复收到了相同的数据,那么它就需要进行去重操作。
而去重操作也是通过之前的32位序号进行——通过对比数据序号来去重。
对于主机A来说,它需要重新发送相同的数据,那就是说发送的数据并不直接移出缓冲区,而是维持一段时间,也许是等到应答后,也许是操作系统自己决定。
超时的时间如何定?
理想情况下就是找到一个最小的时间,保证确认应答会在这个时间内返回。
但是实际上由于网速不同,这个时间长短时变化的,过长会降低效率,过短会频繁重复传包
因此TCP是动态的计算这个时间间隔。
- 任何操作系统都是以500ms为一个单位,时间间隔都是500ms的整数倍。
- 重发一次依旧等不到应答就等待2*500ms的时间。
- 依旧等不到应答就重发等待4*500ms,以指数增长。
- 重传一定次数,就认为对方异常,关闭连接。
连接管理机制
TCP作为面向连接的协议,它具有一套机制用来管理连接。
一般来说,TCP每次连接都需要三次握手的,每次断开连接则需要四次挥手。
三次握手
三次握手的过程
- 客户端应用层调用connect函数,客户端TCP层状态变为 SYN_SENT ,并且向服务器发送包含SYN标记位的报文,作为请求连接
- 服务器应用层时在accept函数阻塞等待客户端请求,当收到包含SYN标记位的报文时,服务器TCP层状态变为 SYN_RCVD,并返回一个包含 SYN+ACK 标记位的报文
- 之后客户端收到报文后,TCP层状态变为ESTABLISHED,引用层connect函数返回,客户端认为连接已建立,并返回包含ACK标记位的报文
- 服务器接收到ACK标记位的报文后,也认为成功建立连接,状态变为 ESTABLISHED,accept函数返回
正常来说,一次成功的建立连接过程就如上,之后就能够传输数据了。
但是网络通信并不一定都一帆风顺,数据总有丢包风险,因此我们的三次握手过程也有可能失败。
在三次握手过程中,客户端发送SYN时,确认应答机制会起作用,告知客户端服务器是否收到数据;而客户端一旦收到 SYN+ACK时,它就认为已经成功建立连接了,因此此时客户端就不会等待应答,而是直接传输数据给服务器了。
但是从服务器上来看,它必须收到 ACK 才会认为成功建立连接,但是ACK是会丢包的。
因此三次握手可能会出现客户端认为建立连接,但是服务器却不认为在建立连接的情况。
这种情况下,一般就是客户端发送数据给服务器后,服务器发现未建立连接,就会返回一个带有RST的报文用来重新建立连接,或许也有其他方案,不过大差不差。
为什么是三次握手?
三次握手为什么不是一次两次或者是四次握手呢?首先我们要先连接三次握手的优点。
对于TCP协议来说,通信双方都是对等的,双方可以互相读写信息,也就是所谓的全双工。
而三次握手就是最小成本验证全双工通信是通畅的。对于客户端来说,它不仅需要发送SYN报文给服务器,还需要读取 SYN+ACK 报文,对于服务器来说,它也是要读取 SYN 和 ACK 两个报文,发送 SYN+ACK 两个报文,正好双方都至少读写了一次。
正因为双方都至少读写了一次,双方才都能得到对方的起始序列号,而二次握手可能只有客户端能够得到服务的的起始序号。
此外,三次握手能够有效的防止单机攻击服务器。
SYN洪水:一种攻击手段,通过给服务器发送海量连接请求,使得服务器需要维护多个半连接,导致服务器资源被占用。
若是一次握手就能够成功建立连接,对于客户端来说,它只需要发送一个链接就好了,也只需要维护一个链接,但是服务器是同时维护多个链接的,维护每一个连接都是需要成本的,若是一次握手就会使的客户端攻击服务器的成本降低,二次握手也是一样。
而多次握手则是成本又比三次握手高,因此采用三次握手。
四次挥手
四次挥手过程
需要注意,四次挥手并不一定是客户端关闭,服务器也能主动关闭。
- 客户端在应用层调用 close 函数,TCP层状态变为 FIN_WAIT_1,并且发送 FIN 报文给服务器
- 服务器接收到后,TCP层状态变为 CLOSE_WAIT,应用层 read 函数返回0,并返回一个ACK 报文
- 客户端接收到后TCP层状态变为 FIN_WAIT_2,,过一段时间后,服务器调用close,状态变为 LAST_ACK,并发送 FIN 报文给 客户端
- 客户端收到后状态变为 TIME_WAIT,并返回 ACK 报文变为 CLOSED状态,服务器接收到 ACK 后状态变为 CLOSED
挥手期间双方状态发送变化
CLOSED_WAIT状态
TCP层协议规定,被动关闭连接的一方需要处于CLOSE_WAIT状态,这个状态会一直持续到服务器处理完数据,调用 close 函数会结束。
那么也就是说我将 Sever 的 close 函数给注释掉,Sever就一直是这个状态。
此时客户端还在连接中,看下状态。 当我们关闭客户端时,再看看状态。
发现TCPSever确实一直处于CLOSE_WAIT状态。
大量出现CLOSE_WAIT原因
- 服务器没有进行 close 的动作
- 服务器有压力,一直在推送消息给客户端,无法进行close
TIME_WAIT状态
当我们使用TCP协议使用一些端口来启动服务器时,用这个端口关闭的服务器在一定时间内无法再次使用这个端口,就是由于TIME_WAIT状态。
- TCP协议规定主动关闭连接的一方要处于TIME_WAIT状态,等待2个 MSL 后才能回到CLOSED状态。
- MSL在不同操作系统上的时间不同,可以通过 "cat /proc/sys/net/ipv4/tcp_find_timeout"来查看。
- 一般来说MSL就是一个TCP报文的最大存活时间。
那么为什么要处于TIME_WAIT状态呢?
- 主动关闭的一方需要返回一个ACK给被动关闭的一方,但是ACK可能丢包,确认应答机制导致主动关闭的一方需要重新接收FIN来补发一个ACK。
- 双方断开连接时,网络中也可能还有滞留的数据,需要保证这些报文已经消失。
而主动关闭连接的一方等待两个MSL后,就能够保证至少补发一个ACK报文,并且保证网络中的滞留数据消失。
解决办法
那么我们如何才能够快速重新使用这个端口呢?就需要调用下面这个函数。
使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个 socket描述符
当我们调用这个函数后就能够重复使用同一个端口了。
滑动窗口
在确认应答机制中,并不是每次发送数据都需要应答的,因为若是每次都应答会降低性能,因此每次发送数据实际上是一次发送多个数据。
- 发送前四段数据不用等待应答。
- 返回的应答确认序号一定是对方已经接收到的数据的序号+1,就能够确认是否丢包。
在TCP的报头中,有一个16位窗口大小,其值是指不用等待确认应答,而可以继续发送数据的最大值, 其大小根据对方的接收缓冲区的剩余空间的大小而变化。
根据窗口大小,我们能够一次发送多个数据,但是这些数据有可能在网络中丢包,根据确认应答机制需要重新发送数据,而这些发送出去而未收到应答的数据就需要滑动窗口来管理。
滑动窗口如何管理的?
实际上滑动窗口并不是一个真正的窗口,它实际上是两个下标。
- 收到对方的应答后,win_start会根据对方报头的32位确认序号来向后移动。
- win_end则会根据对方的接受缓冲区的大小来向后移动。
- 滑动窗口的大小并不固定,根据对方的接收能力有关。
如果收到的应答是滑动窗口中间或结尾的ACK怎么办?
也许收到的应答并不是2001,而是 4001或者直接是结尾呢?
这样只有两种情况。
- 丢包了,数据没丢,但是应答丢了。
- 数据丢包了。
首先是第一种情况,这种情况下窗口是直接滑动的,因为序号的定义就是对方已接收到该序号之前的所有数据,因此可以直接滑动。
第二种情况也很简单,根据序号定义,应答的ACK应该是对方所接收到的数据的序号+1,因此接收端就知道哪些数据丢包了,就会补发一次。
- 当发送端连续收到重复的应答报文后,会补发一次报文。
- 比如途中发送端接收到三次 "1001"的报文后,它就重新发送一次 "1001--2000"的报文。
- 之后接收端返回的应答就是 "7001" 了。
- 这个机制称为“高速重发机制”,也叫快重传。
序号还支持滑动窗口的滑动规则。
一直向右滑动,空间不够了怎么办?
从刚刚的说法来说,发送缓冲渠的空间不是无穷的,因此滑动窗口一定回到缓冲区的尾部。
实际上发送缓冲区的结构是一个环形数组,因此不会出现这种情况。
流量控制
接收段处理数据的速度有限,如果发的太快,接收端处理不及时,会出现丢包的情况,因此需要流量控制。
- 接收端将自己的缓冲区的剩余空间大小放在报文中的窗口大小字段中,通过ACK通知发送端。
- 窗口越大,吞吐量越大。
- 接收端发现缓冲区快满了,就会减小窗口大小。
- 若是窗口设置为0,发送端停止发送数据,但是会定期发送窗口探测数据。
16位窗口最大为65535,但是TCP窗口不止65535,在报头的选项中,有一个窗口扩大因子M,TCP窗口大小为窗口字段的值右移M位。
拥塞控制
一般在网络上,同时会有很多计算机互相通信,即使TCP有滑动窗口来可靠的发送大量数据,但是如果一口气就发送大量数据依旧会造成一些问题。
在客户端和服务器互相通信的时候,网络中依旧有很多计算机在互相通信,这样会造成网络阻塞,因此服务器只应答了几条报文。
像这种大量的丢包,我们就认为是网络出现了阻塞。
为了应对这种情况,TCP引入了慢启动机制,先发少量数据来查看网络拥堵状态,再来决定传输的速度。
- 此处引入拥塞窗口概念
- 传输开始时,拥塞窗口概念为1
- 每收到一个应答,拥塞窗口+1
- 每次发送数据的时候,将拥塞窗口和接收段主机的窗口大小比较,取较小值作为实际发送的窗口
像上图中的拥塞窗口大小按指数级增长,但是拥塞窗口不能这样增长,否则后期会增长过快。
因此需要一个阈值来控制速度,当拥塞窗口大小超过该阈值时,就需要按线性方式增长。
- TCP刚启动时,慢启动阈值等于窗口最大值
- 超时重发时,慢启动阈值会变成原来的一半,拥塞窗口大小置1
通过拥塞控制,TCP能够尽快的将数据传给对方,而不对网络造成太大压力。
延迟应答
有时候,接收端主机若是刚接收到报文就立马应答,它的接收缓冲区可能剩余空间很小,返回的窗口也很小,这样发送端发送的数据也会变少。
- 比如接收端接收到数据后还剩500k的窗口
- 但是它的处理速度很快,也许200ms后就能够处理500k的数据,这样窗口就有1m了
- 因此接收端有时候会等一会再应答,这样能够接收更多的数据
这就是延迟应答,但是延迟应答也是有条件的。
数量限制:每隔N个包应答一次
时间限制:超过最大延迟时间就应答一次
N一般为2,最大延迟时间为200ms
这样返回的窗口越大,其传输的速度反而更快了。
捎带应答
有时候服务器和客户端之间是 “一发一收” 的状态,这个时候服务器就能够顺带把ACK和回复的报文放一起。
比如四次挥手的过程,服务器能够将最后的 ACK 和 FYN 放一起给客户端。
面向字节流
创建TCP的socket时,还会创建对应的接收缓冲区和发送缓冲区。
- 调用write会把数据先拷贝到发送缓冲区中
- 过长则拆分成多个数据包发送
- 过短则放在发送缓冲区等待合适的时机或者长度够了再发
- 接收数据的时候也是先从网卡拷贝到接收缓冲区
- 然后应用层调用read来从接收缓冲区拿数据
- 这种一个链接可以写也可以读的模式称为全双工
由于缓冲区的存在,TCP的读写互不影响。
可以一次写100个字节的数据,也可以写100次一个字节的数据,读取也是。
粘包问题
对于TCP来说,TCP没有报文长度的字段,只有一个序号的字段。
对于TCP来说,它可以按照序号将数据排序,但是从应用层来说,它只看到了一连串的字符数据,这样应用层就分辨不了哪个数据是哪个报文的。
为了避免这个问题,就需要明确包与包之间的边界。
- 对于定长的包,可以按固定大小读取。
- 变长的包,可以在包头的位置约定一个包总长的字段
- 也可以在包与包之间约定一个特殊的分隔符
但是对于UDP来说就不存在这种问题了。
一个是UDP报头有一个报文长度的字段,并且UDP是一个报文一个报文发给应用层的。
TCP异常
-
进程终止:这种情况会释放文件描述符,依旧可以发送FYN。
-
机器重启:这种情况下类似于进程终止。
-
机器断开电源/拔网线:这种情况下如果有写入操作,接收端会发现连接不在了,就会reset,并且TCP内部也有定时器,定时询问连接是否存在。
TCP小结
可靠性:
- 校验和
- 序号
- 确认应答
- 超时重传
- 连接管理
- 流量控制
- 拥塞控制
提高性能
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
基于TCP的应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
TCP与UDP对比
其实二者并无优劣之分,只是需要根据不同的应用场景来使用不同的协议。
TCP适用于可靠传输的场景,而UDP适用于高速传输和对实时性要求高的场景。
Listen函数的第二个参数
第二个参数backlog实际上是为上层维护的链接队列的长度,这个长度不可没有,也不可太长。
我们将gbacklog设为2,然后注释掉accept。
然后用四个客户端来连接这个服务器,然后使用netstat命令查看服务器连接情况。
我们发现连接队列中只有三个连接是被维护着的,也就是全连接状态,还有一个连接从服务器角度来看还在SYN_RECV中,也就是所谓的半连接状态。
这样backlog的含义就知道了,它是指服务器维护的全连接队列的长度,长度为backlog + 1.
如果服务器一直未accept,或者说服务器内部的链接已满,还未退出,那么这个全连接队列就无法accept。
全连接队列:已建立连接,但是未accept的链接。
总结
以上就是应用层的HTTP+HTTPS的细节,以及传输层UDP以及TCP详解。
了解了HTTP的报头,协议格式,以及方法, 状态码,session,cookie,还有HTTPS是如何加密的。
了解了UDP报头,协议格式等。
了解了TCP报头格式,如何连接,实现可靠性,还了解了超时重传机制,确认应答机制,连接管理机制,序号的作用,滑动窗口,拥塞控制,流量控制,快速重传,延迟应答和捎带应答。文章来源:https://www.toymoban.com/news/detail-639363.html
对于网络基础有了一定的了解,但是对于网络层和数据链路层我们依旧未知,还是需要继续努力。文章来源地址https://www.toymoban.com/news/detail-639363.html
到了这里,关于网络基础2(HTTP,HTTPS,传输层协议详解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!