1.概述
OSI七层模型是万能的国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连的理想标准
#从分层上来区分:
物理层:透明的传输比特流
数据链路层:建立逻辑链接、进行硬件地址寻址、差错效验等功能
网络层:进行逻辑地址寻址,实现不同网络之间的路径选择
传输层:定义传输数据的协议端口号,以及流控和差错效验
会话层:建立、管理、终止会话。(在五层模型中已经合并到了应用层)对应主机进程,指本地主机与远程主机正在进行的会话
表示层:数据的表示、安全、压缩。(在五层模型中已经合并到了应用层)
应用层:网络服务于最终用户的一个接口
七层模型 | 五层模型 | 四层模型 |
---|---|---|
应用层 | ||
表示层 | 应用层 | 应用层 |
会话层 | ||
传输层 | 传输层 | 传输层 |
网络层 | 网络层 | 网络层 |
数据链路层 | 数据链路层 | 链接层/实体层 |
物理层 | 物理层 |
1.2 TCP/IP协议簇
#TCP
TCP是面向连接的一种传输控制协议。
TCP通过三次握手建立连接, 通过四次挥手断开连接。
TCP连接之后,客户端和服务器可以互相发送和接收消息,
在客户端或者服务器没有主动断开之前,连接一直存在,故称为长连接。
特点:连接有耗时,传输数据无大小限制,准确可靠,先发先至。
#UDP
UDP是无连接的用户数据报协议。
所谓的无连接就是在传输数据之前不需要交换信息,
没有握手建立连接的过程,只需要直接将对应的数据发送到指定的地址和端口就行。
特点: 不稳定,速度快,可广播,一般数据包限定64KB之内,先发未必先至。
#HTTP
>> HTTP/1.0是基于TCP协议的应用,请求时需建立TCP连接,而且请求包中需要包含
请求方法,URI,协议版本等信息,请求结束后断开连接,完成一次请求/响应操作。故称为短连接。
>> HTTP/1.1中的keep-alive所保持的长连接则是为了优化每次HTTP请求中TCP连接三次握手的麻烦和资源开销,
只建立一次TCP连接,多次的在这个通道上完成请求/响应操作。
值得一提的是,服务器无法主动给客户端推送消息。
#WebSocket
WebSocket也是一种协议,并且也是基于TCP协议的。
具体流程是WebSocket通过HTTP先发送一个标记了 Upgrade 的请求,
服务端解析后开始建立TCP连接,省去了HTTP长连接每次请求都要上传header的冗余,
可以理解为WebSocket是HTTP的优化,但WebSocket不仅仅在Web应用程序上得到支持。
#HTTP、WebSocket与TCP的关系
HTTP通信过程属于“你推一下,我走一下”的方式,客户端不发请求则服务器永远无法发送数据给客户端,
WebSocket则在进行第一次HTTP请求之后,其他全部采用TCP通道进行双向通讯。
所以,HTTP和WebSocket虽都是基于TCP协议,但是两者属于完全不同的两种通讯方式。
1.x TCP协议和UDP协议的区别
1.TCP协议面向连接,UDP协议面向非连接 (有无链接)
2.TCP协议传输速度慢,UDP协议传输速度快 (传输速度)
3.TCP协议保证数据顺序,UDP协议不保证 (数据的有序性,在IP层时,数据包会变得无序)
4.TCP协议保证数据正确性,UDP协议可能丢包 (TCP保证数据的可靠性)
5.TCP协议对系统资源要求多,UDP协议要求少 (TCP和UDP占用的资源)
2.相关协议
2.1 WebSocket协议
2.1.1概述
WebSocket是一种在单个TCP连接上进行全双工通信的协议。即允许服务端主动向客户端推送数据。
WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。
WebSocket API也被W3C定为标准, 是HTML5新增的协议。
WebSocket API中,浏览器和服务器只需要完成一次握手,
两者之间就直接可以创建持久性的连接,并进行双向数据传输。
HTTP 有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个 HTTP 请求合并为一个,
但是 Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,
只是为了兼容现有浏览器,所以在握手阶段使用了 HTTP 。
2.1.1 为什么传统的HTTP协议不能做到WebSocket实现的功能
这是因为HTTP协议是一个请求-响应协议,请求必须先由浏览器发给服务器,
服务器才能响应这个请求,再把数据发送给浏览器。
换句话说,浏览器不主动请求,服务器是没法主动发数据给浏览器的。
这样一来,要在浏览器中搞一个实时聊天,或者在线多人游戏的话就只能借助Flash这些插件。
也有人说,HTTP协议其实也能实现啊,比如用轮询或者Comet。
#ajax轮询
ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
这个机制的缺点一是实时性不够,二是频繁的请求会给服务器带来极大的压力。
#Comet
本质上也是轮询,但是在没有消息的情况下,服务器先拖一段时间,等到有消息了再回复。
这个机制暂时地解决了实时性问题,但是它带来了新的问题:
以多线程模式运行的服务器会让大部分线程大部分时间都处于挂起状态,极大地浪费服务器资源。
另外,一个HTTP连接在长时间没有数据传输的情况下,链路上的任何一个网关都可能关闭这个连接,
而网关是我们不可控的,这就要求Comet连接必须定期发一些ping数据表示连接“正常工作”。
以上两种机制都治标不治本,所以,HTML5推出了WebSocket标准,
让浏览器和服务器之间可以建立无限制的全双工通信,任何一方都可以主动发消息给对方。
WebSocket对客户端和服务端的要求
#浏览器
要支持WebSocket通信,浏览器得支持这个协议,这样才能发出ws://xxx的请求。
目前,支持WebSocket的主流浏览器如下:
>> Chrome
>> Firefox
>> IE >= 10
>> Sarafi >= 6
>> Android >= 4.4
>> iOS >= 8
#服务器
由于WebSocket是一个协议,服务器具体怎么实现,取决于所用编程语言和框架本身。
1) Node.js
本身支持的协议包括TCP协议和HTTP协议,要支持WebSocket协议,
需要对Node.js提供的HTTPServer做额外的开发。
2) java
3) python
2.1.2 WebSocket的握手通信过程
>> WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。
>> WebSocket 是独立的、创建在 TCP 上的协议。
>> Websocket 通过HTTP/1.1 协议的101状态码进行握手。
>> 为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为握手(handshaking)。
1) 首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:
"GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13"
#该请求和普通的HTTP请求有几点不同:
>> GET请求的地址不是类似/path/,而是以ws://开头的地址;
>> 请求头Upgrade: websocket和Connection: Upgrade表示这个连接将要被转换为WebSocket连接;
>> Sec-WebSocket-Key: 是浏览器经过Base64加密后的密钥,用来和response里面的Sec-WebSocket-Accept进行比对验证
>> Sec-WebSocket-Extensions是对WebSocket的协议扩展
>> Sec-WebSocket-Version指定了WebSocket的协议版本。
>> 请求返回101状态码, 如果不是101状态码,表示握手升级的过程失败了
101是Switching Protocols,表示服务器已经理解了客户端的请求,
并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。
在发送这个响应后的空档,将http升级到webSocket。
2) 随后,服务器如果接受该请求,就会返回如下响应:
"HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string"
该响应代码101表示本次连接的HTTP协议即将被更改,
更改后的协议就是Upgrade: websocket指定的WebSocket协议。
版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。
如果仅使用WebSocket的API,就不需要关心这些。
现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。
消息有两种,一种是文本,一种是二进制数据。
通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。
#为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?
实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,
但是HTTP协议的请求-应答机制限制了全双工通信。
WebSocket连接建立以后,其实只是简单规定了一下:
接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧。
#安全的WebSocket连接机制和HTTPS类似:
首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,
然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议。
2.1.3 WebSocket 的其他特点
>> 建立在 TCP 协议之上,服务器端的实现比较容易。
>> 与 HTTP 协议有着良好的兼容性。
默认端口也是80和443,并且握手阶段采用 HTTP 协议,
因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
>> 数据格式比较轻量,性能开销小,通信高效。
>> 可以发送文本,也可以发送二进制数据。
>> 没有同源限制,客户端可以与任意服务器通信。
>> 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
2.1.4 WebSocket适用场景
追求数据的实时更新的应用, 例如:
>> 股票交易数据
>> 数据货币交易数据等
>> 消息传递应用程序
>> 在线多人游戏
>> 聊天室
>> 现场体育更新
>> 社交媒体
参考资料
http://www.websocket.org/index.html
https://segmentfault.com/a/1190000012709475
https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096
https://www.cnblogs.com/nnngu/p/9347635.html
https://www.cnblogs.com/bianzy/p/5822426.html
https://www.cnblogs.com/jingmoxukong/p/7755643.html (nginx代理WebSocket)
https://segmentfault.com/a/1190000012948613
https://www.jianshu.com/p/42260a2575f8
2.2 RPC
https://www.jianshu.com/p/45cbc2252c11 (参考资源)
2.3 IP协议
https://www.jianshu.com/p/9e63767c04e1
2.3.1 IP理论部分
IP数据包由IP头部和数据负载两部分组成。
IP数据包长度不固定,其中头部长度不固定,负载长度也不固定。
1.版本:占4位,表示IP协议的版本。如果是IPv4,则取值为0100,如果是IPv6,则取值为0110
2.IP包头长度:
占4位,从0000-1111,也就是最小为0,最大为15。
实际上,IP包头的长度至少为20字节,最大为60字节。
IP包头长度的值 * 4就是ip包头所占的字节数。
举例来说,IP包头长度的值是0110,值为6,那么IP包头的长度就是6 * 4 = 24。
固定区域20字节,可选区域4字节。
3.服务类型:占8位,包含优先级、标志位等,实际中很少使用,不做介绍。
4.IP包总长度:占16位,表示该IP包的总长度。即IP包头长度+IP包数据长度。
5.标识:占16位,在数据包分段重组时,标识表示包的序列号。
当数据包比较大时,会分成多个IP包发送,每个IP包到达目的地的时间是不确定的,此时就需要根据标识进行重组。
标识符与下面的标志、偏移量结合使用。
6.标志:占3位。从左至右依次是MF、DF、未使用字段。
MF = 1,表示后面还有分段数据包,MF = 0表示后面没有分段数据包,也就是最后一个。
DF = 1,表示该数据包不能被分段,DF = 0表示数据包可以被分段。
7.偏移量:占13位。表示该数据段在上层初始数据报文中的偏移量。和标识符、标志位结合使用。
8.生存时间:占8位。
生存时间由操作系统初始化,每经过一次转发,生存时间减1,如果生存时间为0,则该包丢弃。
生存时间是为了防止数据包在网络中无休止发送,占用网络资源。
9.协议:占8位。常用的UDP的值是17,TCP的值是6。
10.首部校验和:占16位。对IP数据包首部校验得到的值,校验和有具体的算法。
11.源IP地址:占32位
12.目的IP地址:占32位。
上面的内容共占20个字节,这些部分是固定的,每一个IP包的头部都包含这些,所以IP包头的长度至少20字节。
下面的可选部分包含安全处理机制、记录路径、时间戳等信息,长度为0-40字节。
再下面就是IP包的数据部分。IP包的数据包含TCP包、UDP包两种。
#注意
下图中的位指的是bit,上文中说包的长度是单位是字节,1字节=8位。
2.3.2 IP抓包实践
2.4 TCP协议
https://www.jianshu.com/p/e41a329ef353 (握手等过程参考这里)
2.4.1 TCP理论部分
TCP包的格式和IP包格式类似,同样由头部和数据两部分组成。
TCP包的长度不固定,其中头部长度不固定,数据部分长度不固定。
由IP部分的内容可知,TCP包实际上就是IP包的数据部分。
#TCP头部
1.源端口:占16位。
2.目的端口:占16位。
3.序号:sequence number,占32位。表示从发送端向接收端发送的字节流编号。
4.确认号:Acknowledgement number,占32位。表示接收端所期望接收到的下一序号。
5.数据偏移:
占4位。数据偏移其实就是TCP包的头部长度,理论取值从0000-1111,最小为0,最大为15。
同IP头部一样,TCP包的头部长度至少为20字节,最多为60字节,且计算方式和IP头部长度的计算方式也一样。
如果数据偏移的值为6,那么TCP包的头部长度为6 * 4 = 24。
6.保留值:占6位。目前没用,为了之后新功能所保留。
7.6位标志位。分别介绍:
>> URG:紧急标志位,为1说明紧急指针有效。
>> ACK:确认标志位,为1说明确认序号有效。
>> PSH:值为1,表示需要将数据立刻发送给应用程序。
>> RST:值为1时,表示需要重连。
>> SYN:握手时使用。值为1,表示连接请求报文。
>> FIN:值为1时,需要断开连接。
8.窗口大小:占16位,表示接收端的窗口大小,用于控制网络流量速率。
9.校验和:占16位,和IP包头部中的校验和作用一致。
10.紧急指针:占16位,和上面提到的URG字段结合使用。
11.可选部分,包含窗口扩大选项、时间戳等。最小为0,最大为40字节。
TCP包固定头部20字节,可选部分在0-40字节之间。
剩下的就是TCP包的数据部分。
2.4.2 TCP抓包实践
2.4.3 TCP 三次握手 & 四次挥手
TCP 标志位
1、SYN(synchronous建立联机)
2、ACK(acknowledgement 确认)
3、PSH(push传送)
4、FIN(finish结束)
5、RST(reset重置)
6、URG(urgent紧急)
Sequence number(顺序号码)
Acknowledge number(确认号码)
TCP状态
LISTEN - 侦听来自远方TCP端口的连接请求;
SYN-SENT -在发送连接请求后等待匹配的连接请求;
SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;
ESTABLISHED- 代表一个打开的连接,数据可以传送给用户;
FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;
FIN-WAIT-2 - 从远程TCP等待连接中断请求;
CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
CLOSING -等待远程TCP对连接中断的确认;
LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;
TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认;
CLOSED - 没有任何连接状态.
建立连接协议(三次握手)
>> 第一次握手:
客户端发送syn包(syn=x)的数据包到服务器,并进入SYN_SEND状态,等待服务器确认;
>> 第二次握手:
服务器收到syn包,必须确认客户的SYN(ack=x+1),
同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
>> 第三次握手:
客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),
此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP连接都将被一直保持下去。
连接终止协议(四次握手)
client和server释放资源
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。
这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。
收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。
客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
TCP协议的连接是全双工连接,一个TCP连接存在双向的读写通道。
简单说来是“先关读,后关写”,一共需要四个阶段。
以客户机发起关闭连接为例:
1.服务器读通道关闭
2.客户机写通道关闭
3.客户机读通道关闭
4.服务器写通道关闭
#详细过程:
第一阶段:
客户机发送完数据之后,向服务器发送一个FIN数据段,序列号为x+2;
1.服务器收到FIN(x+2)后,返回确认段ACK,序列号为x+3,关闭服务器读通道;
2.客户机收到ACK(i+1)后,关闭客户机写通道;
(此时,客户机仍能通过读通道读取服务器的数据,服务器仍能通过写通道写数据)
第二阶段:
服务器发送完数据之后,向客户机发送一个FIN数据段,序列号为y+1;
3.客户机收到FIN(j)后,返回确认段ACK,序列号为y+2,关闭客户机读通道;
4.服务器收到ACK(y+2)后,关闭服务器写通道。
这是标准的TCP关闭两个阶段,服务器和客户机都可以发起关闭,完全对称。
https://blog.csdn.net/yangyangye/article/details/38851271 (详解)
https://blog.csdn.net/zzhongcy/article/details/21992471
2.4.4 一些问题
1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
#建立连接时
因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,
它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。
#断开连接时
但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;
但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,
也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,
所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
2. 为什么不能用两次握手进行连接?
3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),
也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。
作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,
S收到了这个分组,并发送了确认应答分组。
按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。
可是,C在 S的应答分组在传输中被丢失的情况下,将不知道S是否已准备好,
不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。
在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。
而S在发出的数据分组超时后,重复发送同样的数据分组。这样就形成了死锁。
就好比打电话, A打给B, A要告诉B ,我打给你了, B要回应给A听,OK,连接成功了;
然后B也要得到A的确认,才能开始正式通话。
3.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
什么是2MSL?
MSL即Maximum Segment Lifetime,也就是报文最大生存时间,引用《TCP/IP详解》中的话:
“它(MSL)是任何报文段被丢弃前在网络内的最长时间。”
那么,2MSL也就是这个时间的2倍,当TCP连接完成四个报文段的交换时,
主动关闭的一方将继续等待一定时间(2-4分钟),即使两端的应用程序结束。
1)为什么需要这个2MSL呢?
>> 虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,
按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);
但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,
因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,
所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
>> 报文可能会被混淆,意思是说,其他时候的连接可能会被当作本次的连接。
直接引用《The TCP/IP Guide》的说法:
The second is to provide a “buffering period” between the end of this connection and any subsequent ones.
If not for this period, it is possible that packets from different connections could be mixed, creating confusion.
2)TIME_WAIT状态存在的理由:
>> 可靠地实现TCP全双工连接的终止
在进行关闭连接四路握手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,
服务器将重发最终的FIN,因此客户端必须维护状态信息允许它重发最终的ACK。
如果不维持这个状态信息,那么客户端将响应RST分节,
服务器将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
因而,要实现TCP全双工连接的正常终止,必须处理终止序列四个分节中任何一个分节的丢失情况,
主动关闭 的客户端必须维持状态信息进入TIME_WAIT状态。
>> 允许老的重复分节在网络中消逝
TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,
迷途的分节在路由器修复后也会被送到最终目的地,这个原来的迷途分节就称为lost duplicate。
在关闭一个TCP连接后,马上又重新建立起一个相同的IP地址和端口之间的TCP连接,
后一个连接被称为前一个连接的化身 (incarnation),那么有可能出现这种情况,
前一个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。
为了避免这个情 况,TCP不允许处于TIME_WAIT状态的连接启动一个新的化身,
因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个TCP连接的时候,
来自连接先前化身的重复分组已经在网络中消逝。
>> 对第二点的补充说明.
防止lost duplicate对后续新建正常链接的传输造成破坏。
lost duplicate在实际的网络中非常常见,经常是由于路由器产生故障,路径无法收敛,
导致一个packet在路由器A,B,C之间做类似死循环的跳转。
IP头部有个TTL,限制了一个包在网络中的最大跳数,因此这个包有两种命运:
要么最后TTL变为0,在网络中消失;
要么TTL在变为0之前路由器路径收敛,它凭借剩余的TTL跳数终于到达目的地。
但非常可惜的是TCP通过超时重传机制在早些时候发送了一个跟它一模一样的包,
并先于它达到了目的地,因此它的命运也就注定被TCP协议栈抛弃。
另外一个概念叫做incarnation connection,指跟上次的socket pair一摸一样的新连接,
叫做incarnation of previous connection。
lost duplicate加上incarnation connection,则会对我们的传输造成致命的错误。
大家都知道TCP是流式的,所有包到达的顺序是不一致的,依靠序列号由TCP协议栈做顺序的拼接;
假设一个incarnation connection这时收到的seq=1000,
来了一个lost duplicate为seq=1000, len=1000,
则tcp认为这个lost duplicate合法,并存放入了receive buffer,导致传输出现错误。
通过一个2MSL TIME_WAIT状态,确保所有的lost duplicate都会消失掉,避免对新连接造成错误。
3)该状态为什么设计在主动关闭这一方:
(1)发最后ack的是主动关闭一方
(2)只要有一方保持TIME_WAIT状态,就能起到避免incarnation connection在2MSL内的重新建立,不需要两方都有
4)对服务器的影响
当某个连接的一端处于TIME_WAIT状态时,该连接将不能再被使用。
事实上,对于我们比较有现实意义的是,这个端口将不能再被使用。
某个端口处于TIME_WAIT状态(其实应该是这个连接)时,
这意味着这个TCP连接并没有断开(完全断开),那么,如果你bind这个端口,就会失败。
对于服务器而言,如果服务器突然crash掉了,那么它将无法在2MSL内重新启动,因为bind会失败。
解决这个问题的一个方法就是设置socket的SO_REUSEADDR选项。
这个选项意味着你可以重用一个地址。
5)如何正确对待2MSL TIME_WAIT
RFC要求socket pair在处于TIME_WAIT时,不能再起一个incarnation connection。
但绝大部分TCP实现,强加了更为严格的限制。
在2MSL等待期间,socket中使用的本地端口在默认情况下不能再被使用。
若A 10.234.5.5:1234和B 10.55.55.60:6666建立了连接,A主动关闭,
那么在A端只要port为1234,无论对方的port和ip是什么,都不允许再起服务。
显而易见这是比RFC更为严格的限制,RFC仅仅是要求socket pair不一致,
而实现当中只要这个port处于TIME_WAIT,就不允许起连接。
这个限制对主动打开方来说是无所谓的,因为一般用的是临时端口;
但对于被动打开方,一般是server,就悲剧了,因为server一般是熟知端口。
比如http,一般端口是80,不可能允许这个服务在2MSL内不能起来。
解决方案是给服务器的socket设置SO_REUSEADDR选项,
这样的话就算熟知端口处于TIME_WAIT状态,在这个端口上依旧可以将服务启动。
当然,虽然有了SO_REUSEADDR选项,但sockt pair这个限制依旧存在。
比如上面的例子,A通过SO_REUSEADDR选项依旧在1234端口上起了监听,
但这时我们若是从B通过6666端口去连它,TCP协议会告诉我们连接失败,原因为Address already in use.
2.4.5 tcp长连接和短连接
HTTP协议是基于请求/响应模式的,因此只要服务端给了响应,本次HTTP连接就结束了,
或者更准确的说,是本次HTTP请求就结束了,根本没有长连接/短连接这一说。
之所以说HTTP分为长连接和短连接,其实本质上是说的TCP连接。
TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,
因此TCP连接才有真正的长连接和短连接这一说。
HTTP协议说到底是应用层的协议,而TCP才是真正的传输层协议,只有负责传输的这一层才需要建立连接。
TCP在真正的读写操作之前,server与client之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立通过三次握手,释放则需要四次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的。
TCP短连接
模拟一种TCP短连接的情况:
1.client 向 server 发起连接请求
2.server 接到请求,双方建立连接
3.client 向 server 发送消息
4.server 回应 client
5.一次读写完成,此时双方任何一个都可以发起 close 操作
在步骤5中,一般都是 client 先发起 close 操作。当然也不排除有特殊的情况。
从上面的描述看,短连接一般只会在 client/server 间传递一次读写操作!
#短连接的操作步骤是
建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接
TCP长连接
再模拟一种长连接的情况:
1.client 向 server 发起连接
2.server 接到请求,双方建立连接
3.client 向 server 发送消息
4.server 回应 client
5.一次读写完成,连接不关闭
6.后续读写操作...
7.长时间操作之后client发起关闭请求
#长连接的操作步骤是
建立连接——数据传输...(保持连接)...数据传输——关闭连接
TCP长/短连接的优点和缺点
#长连接
#优点:
可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。
对于频繁请求资源的客户来说,较适用长连接。
#缺点:
client与server之间的连接如果一直不关闭的话,会存在一个问题,
随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,
如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;
如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,
这样可以完全避免某个蛋疼的客户端连累后端服务。
#短连接
#优点:
对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。
#缺点:
但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。
TCP长/短连接的应用场景
#长连接
多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,
再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,
再次处理时直接发送数据包就OK了,不用建立TCP连接。
例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,
而且频繁的socket 创建也是对资源的浪费。
#短链接
像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。
2.5 UDP协议
2.5.1 UDP基本理论
UDP数据传输的特点
#面向无连接
UDP 不需要与 TCP一样在发送数据前进行三次握手建立连接,UDP想发数据就直接发送了;
并且UDP只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
#不可靠
首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠的;
并且收到什么数据就传递什么数据,也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据;
再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据;
即使网络条件不好,也不会对发送速率进行调整,这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,
但是优点也很明显,在某些实时性要求高的场景(比如直播、电话会议等)就需要使用 UDP 而不是 TCP;
#单播、多播、广播功能
由于 UDP 不会建立连接,因此它可以给任何人传递数据,
不止支持一对一的传输方式,同样支持一对多、多对多、多对一的方式;
#UDP是面向报文的
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层.
UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界;
#头部开销小,传输数据高效
UDP 的头部开销小,只有八字节,在传输数据报文时是比较高效的.在某些实时性要求高的场景,例如直播、电话会议、媒体传输等场景经常使用 UDP协议;
相对TCP来说,UDP是无序、无状态的,因此UDP包相对TCP包来说要简单一些。
UDP包实际上也是IP包的数据部分。
UDP包由头部和数据两部分组成,头部长度固定,数据部分长度不固定。
1.UDP头部的长度为固定8字节。
>> 源端口:占16位
>> 目的端口:占16位
>> UDP长度:包含头部和数据包总长度,注意和TCP包、IP包的区别
>> 校验和:占16位,同IP包、TCP包校验和。
2.UDP的数据部分。
#注意
UDP头部中UDP长度,这个地方和TCP、IP包有明显的区别。
因为UDP包头部长度是固定8个字节,所以该部分的取值最小为1000。
2.5.2 UDP抓包实践
wireshark
2.6 HTTP协议
https://www.jianshu.com/p/e41a329ef353 (参考这里)
https://www.jianshu.com/p/7c01759c28dd (https加密传输参考这里)
2.6.1 HTTP基本理论
2.6.2 HTTP抓包实践
2.7 DNS协议
2.7.1 DNS基本理论
DNS包属于UDP包,实际上,DNS包是UDP包的数据部分。
DNS包同样分为头部和数据部分,头部长度固定,数据部分长度不固定。
#DNS数据包结构
1.TranscationID:
会话标识,占2个字节。DNS请求报文和DNS应到报文的TranscationID是相同的。
2.Flags:
占2个字节,包含多个标志位:
2.1 QR:
占1位。0查询报文,1响应报文
2.2 Opcode:
占4位。0标准查询,1反向查询,2服务器状态查询,3~15保留未用
2.3 AA:
占1位。为1,表示授权回答
2.4 TC:
占1位。为1,表示报文被截断
2.5 RD:
占1位。0,表示客户端期望域名解析服务器采用迭代的方式解析;1表示客户端期望域名解析服务器采用递归的方式解析
2.6 RA:占1位。
>> 0,表示域名解析服务器采用迭代的方式解析;
>> 1表示域名服务器采用递归的方式解析
2.7 Zero:占3位。全0,保留位。
2.8 Rcode:占4位。响应码.
>> 0表示无差错;
>> 1表示查询格式错误;
>> 2表示服务器失效;
>> 3表示域名错误;
>> 4表示查询没有被执行;
>> 5表示查询被拒绝。
>> 6~15保留。
3.Questions:占2个字节,表示查询问题区域的数量
4.Answers RRS:占2个字节,表示回答区域的数量
5.Authority RRs:占2个字节,表示授权区域的数量
6.Additional RRs:占2个字节,表示附加区域的数量
7.Quries:问题区域,长度不固定,可以有多个。
7.1 Name区域:就是查询的域名,如www.baidu.com,Name区域也有固定的格式。
由于域名的长度不固定,所以name区域长度不固定。
7.2 Type: 查询类型,占2个字节。DNS有多种查询类型:
查询类型 助记符 功能
1 A 获得IPv4地址
28 AAAA 获得IPv6地址
15 MX 邮件服务器
2 NS 指定域名服务器
5 CNAME 将域名指向另一个域名时
7.3 Class,查询类,通常为1,表示Internet数据。
8.Answers:回答区域,长度不固定。
回答区域的格式和授权区域、附加区域的格式是类似的,因此,只介绍回答区域的格式。
8.1 Name:查询的域名,同问题区域的域名。
但是格式和问题区域的域名不同,为了减小包大小,当包中出现重复域名的时候,回答区域的域名部分使用偏移量来表示。
当使用偏移量来表示时,占2个字节。不使用偏移量时,长度不固定(和域名本身长度有关)。
使用偏移量来表示时,首字节固定为C0,用于识别,后面字节用于表示偏移量。
通过上面的介绍可知,DNS包的头部固定占12字节,头部之后就是查询问题区域,
查询问题区域的第一部分就是域名。因此,常见的偏移量是C00C。
8.2 Type:同请求部分
8.3 Class:同请求部分
8.4 Time To Live:生存时间,占4个字节。实际上是客户端缓存的时间。
通常情况,域名所对应的IP不会经常改变。
因此,为了提高网络传输效率,可以将结果缓存,下次访问时跳过域名解析。
8.5 数据长度:占2个字节,下文数据部分的长度。
8.6 数据:不定长。
9.Authoritative:同回答区域。
10.Additional:同回答区域。
2.7.2 DNS抓包实践
3.网络通信过程
使用集线器组成一个网络
1)当有多台电脑需要组成一个网时,那么可以通过集线器(Hub)将其链接在一起
2)一般情况下集线器的接口较少
3)集线器有个缺点,它以广播的方式进行发送任何数据,
即如果集线器接收到来自A电脑的数据本来是想转发给B电脑,
如果此时它还连接着另外两台电脑C、D,
那么它会把这个数据给每个电脑都发送一份,因此会导致网络拥堵
使用交换机组成一个网络
1)克服了集线器以广播发送数据的缺点,当需要广播的时候发送广播,当需要单播的时候又能够以单播的方式进行发送
2)它已经替代了之前的集线器
3)企业中就是用交换机来完成多台电脑设备的链接成网络的
使用路由器连接多个网络(组包, 拆包过程)
数据传递的过程, 是mac地址的交换与转发, 自始至终, 源ip和目的ip是固定的
路由器一般是默认网关, 具有数据转发能力的设备
当数据传递时, 若一台设备发送给另一台设备时:
会先执行arp -a命令, 查看缓存中是否有另一个设备的mac地址(链路层),
若没有, 则通过默认网关进行广播, 广播中有一类mac地址(FF:FF:FF:FF:FF:FF), 所有设备都能收到, 待链路层认证通过后, 进入ip层验证ip,
若ip认证通过, 则返回该设备的mac,
若ip认证不通过, 丢掉包, 不响应
通信过程
1)在浏览器中输入一个网址时,需要将它先解析出ip地址来(DNS解析, 各电脑都有DNS地址:DNS服务器由国家管控)
2)当得到ip地址之后,浏览器以tcp的方式3次握手链接服务器
3)以tcp的方式发送http协议的请求数据 给 服务器
4)服务器tcp的方式回应http协议的应答数据 给浏览器
1)MAC地址:在设备与设备之间数据通信时用来标记收发双方(网卡的序列号)
2)IP地址:在逻辑上标记一台电脑,用来指引数据包的收发方向(相当于电脑的序列号)
3)网络掩码:用来区分ip地址的网络号和主机号
4)默认网关:当需要发送的数据包的目的ip不在本网段内时,就会发送给默认的一台电脑,称为网关
5)集线器:已过时,用来连接多态电脑,缺点:每次收发数据都进行广播,网络会变的拥堵
6)交换机:集线器的升级版,有学习功能知道需要发送给哪台设备,根据需要进行单播、广播
7)路由器:连接多个不同的网段,让他们之间可以进行收发数据,每次收到数据后,ip不变,但是MAC地址会变化
8)DNS:用来解析出IP(类似电话簿)
9)http服务器:提供浏览器能够访问到的数据
10)网关: 具有转发数据能力的设备
(如192.168.1.2要发数据给192.168.2.2, 但二者不可直接通信,
可通过路由器A(有两块网卡, ip分别是192.168.1.3, 192.168.2.3),
ip192.168.1.2先转给192.168.1.3, 然后192.168.1.3转发给192.168.2.3, 然后192.168.2.3最终转给192.168.2.2)
4. 常见问题
4.1 TCP/IP协议簇粘包拆包问题
https://www.jianshu.com/p/209576915459
参考资源
https://www.jianshu.com/p/9e63767c04e1 (各种协议包结构)
https://www.jianshu.com/p/29868fb82890 (TCP三次握手四次挥手)