网络基础2(HTTP,HTTPS,传输层协议详解)
再谈协议
在之前利用套接字进行通信的时候,我们都是利用 “字符串” 进行流式的发送接收,但是我们平常进行交流通信肯定不能只是简单的发送字符串。
比如我们用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报头格式,如何连接,实现可靠性,还了解了超时重传机制,确认应答机制,连接管理机制,序号的作用,滑动窗口,拥塞控制,流量控制,快速重传,延迟应答和捎带应答。
对于网络基础有了一定的了解,但是对于网络层和数据链路层我们依旧未知,还是需要继续努力。
相关文章:
网络基础2(HTTP,HTTPS,传输层协议详解)
再谈协议 在之前利用套接字进行通信的时候,我们都是利用 “字符串” 进行流式的发送接收,但是我们平常进行交流通信肯定不能只是简单的发送字符串。 比如我们用QQ进行聊天,我们不仅需要得到对方发送的消息,还要知道对方的昵称&…...
Java实现籍贯级联选择器
在工作中要求写一个籍贯的级联选择器,记录一下自己写这个级联选择器的过程,因为自己才刚开始工作,有很多地方都没有考虑的很清楚,希望各位大佬能给出建议。 一、需求 A:正常的23个省,籍贯由“省区/县/市”组成…...
每日一学——OSI参考模型
OSI参考模型(Open Systems Interconnection Reference Model)是国际标准化组织(ISO)制定的一个网络通信协议的概念框架。它将网络通信划分为七个层次,每个层次负责不同的功能和任务,从物理层到应用层依次为…...
虚幻5中Lumen提供哪些功能以及如何工作的
虚幻引擎 5 中的 Lumen 是一个完全动态的全局照明和反射系统。它可以在虚幻引擎 5 中使用,因此创作者无需自行设置。它是为下一代控制台和建筑可视化等高端可视化而设计的。那么它提供了哪些功能以及如何工作? 全局照明 当光离开光源时,它会…...
Linux C 语言 mosquitto 方式 MQTT 发布消息
1 说明 采用 mosquitto 库,实现对主题发布消息。 其中服务器有做限制,需要对应的 cilent id ,cafile 、certfile 、keyfile 等配置 2 开发环境 采用ubuntu 直接编译调试 安装mosquitto 库 sudo apt install libmosquitto-dev sudo apt-ge…...
利用NtDuplicateObject进行Dump
前言 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习) 这是国外老哥2020年提出的一种蛮有意思的思路。 我们先来看看大致的思路是…...
【快应用】list组件如何区分滑动的方向?
【关键词】 list组件、滑动方向、scroll 【问题背景】 有cp反馈list这个组件在使用的时候,不知道如何区分它是上滑还是下滑。 【问题分析】 list组件除了通用事件之外,还提供了scroll、scrollbottom、scrolltop、scrollend、scrolltouchup事件&#x…...
【深入了解pytorch】PyTorch扩展:如何使用PyTorch的扩展功能
【深入了解pytorch】PyTorch扩展:如何使用PyTorch的扩展功能 PyTorch扩展:展示如何使用PyTorch的扩展功能1. 自定义损失函数2. 自定义数据加载器3. 自定义优化器总结PyTorch扩展:展示如何使用PyTorch的扩展功能 PyTorch作为一个开源的深度学习框架,在研究和应用领域广受欢…...
Vue3——如何实现页面访问拦截
在现代的Web开发中,页面访问拦截是一个非常常见的需求。通过拦截页面访问,我们可以控制用户在访问特定页面之前需要满足的条件,比如登录状态、权限等。Vue是一个非常流行的JavaScript框架,它提供了许多强大的工具和功能࿰…...
nginx配置gzip
在 Nginx 中启用 Gzip 压缩可以大幅减少传输内容的大小,从而加快网页加载速度。 打开 Nginx 的配置文件,通常是 /etc/nginx/nginx.conf 或者 /etc/nginx/conf.d/default.conf。找到 http 配置块,在其中添加以下代码来开启 Gzip 压缩ÿ…...
ExtJS教程_编程入门自学教程_菜鸟教程-免费教程分享
教程简介 Ext JS是一个流行的JavaScript框架,它为使用跨浏览器功能构建Web应用程序提供了丰富的UI。 Ext JS基本上用于创建桌面应用程序它支持所有现代浏览器,如IE6 ,FF,Chrome,safari 6 等。Ext JS基于MVC / MVVM架构…...
【el-upload】批量上传图片时在before-upload中添加弹窗判断时的踩坑记录
一、初始代码 1. 初始使用组件代码片段 <!-- 上传 --> <DialogUploadFile ref"uploadFile" success"refresh" />// 上传 const uploadHandle () > {if (selections.value.length ! 1) {onceMessage.warning(请选择一条数据操作)return}u…...
【Java基础】- JVM之Dump文件详解
Java基础 - JVM之Dump文件详解 文章目录 Java基础 - JVM之Dump文件详解一、什么是Dump三、为什么需要Dump分析思路 四、Dump记录哪些内容4.1 Java dump 文件的格式和内容段格式行格式 4.2 常用分类heap dump和thread dumpheap dumpthread dump 五、如何生产Dump文件5.1 获取hea…...
基于Vue+wangeditor实现富文本编辑
目录 前言分析实现具体解决的问题有具体代码实现如下效果图总结前言 一个网站需要富文本编辑器功能的原因有很多,以下是一些常见的原因: 方便用户编辑内容:富文本编辑器提供了类似于Office Word的编辑功能,使得那些不太懂HTML的用户也能够方便地编辑网站内容。提高用户体验…...
深入理解 Spring 中的 @RequestBody 和 @ResponseBody 注解及其区别
引言 在现代的 Web 开发中,处理 HTTP 请求和响应是不可或缺的任务。Spring Framework 提供了丰富的功能来简化这些任务,并使开发人员能够更专注于业务逻辑。在本文中,我们将深入探讨 Spring 中的 RequestBody 和 ResponseBody 注解࿰…...
【论文阅读】EULER:通过可扩展时间链接预测检测网络横向移动(NDSS-2022)
作者:乔治华盛顿大学-Isaiah J. King、H. Howie Huang 引用:King I J, Huang H H. Euler: Detecting Network Lateral Movement via Scalable Temporal Graph Link Prediction [C]. Proceedings 2022 Network and Distributed System Security Symposium…...
手动创建一个DOCKER镜像
1. 我们先使用C语言写一个hello-world程序 vim hello.c # include <stdio.h>int main() {print("hello docker\n"); } 2. 将hello.c文件编译成二进制文件, 需要安装工具 yum install gcc yum install glibc-static 开始编译 gcc -static hello.c -o hello 编译…...
SSM(Vue3+ElementPlus+Axios+SSM前后端分离)--搭建Vue 前端工程[一]
文章目录 SSM--搭建Vue 前端工程--项目基础界面实现功能01-搭建Vue 前端工程需求分析/图解代码实现搭建Vue 前端工程下载node.js LTS 并安装: node.js 的npm创建Vue 项目使用idea 打开ssm_vue 项目, 并配置项目启动 Vue3 项目目录结构梳理Vue3 项目结构介绍 配置Vue 服务端口El…...
Idea使用Docker插件实现maven打包自动构建镜像
Docker 开启TCP 服务 vi /lib/systemd/system/docker.service改写以下内容 ExecStart/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock重启服务 #重新加载配置文件 systemctl daemon-reload #重启服务 systemctl restart docker.service此时docker已…...
Tailwind css优于Bootstrap 7个原因
在某些情况下,Tailwind css 比 Bootstrap 更好,因为它是一个低级 CSS 框架,可让您根据需要构建自己的自定义组件。如果使用得当,它非常注重性能,可以显着减少 CSS 负载并确保更快的渲染。如果 Web 性能和自定义是您的首…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...


