计算机网络的层次结构
OSI 七层模型
层 | 功能 | 常见协议 |
---|---|---|
应用层(Application Layer) | 应用层提供网络服务和用户应用程序的接口。它定义了各种应用协议,如HTTP、FTP、SMTP等,以满足特定应用需求 | HTTP、HTTPS、FTP、SMTP、POP、IMAP、DNS、DHCP、Telnet |
表示层(Presentation Layer) | 表示层负责处理数据的表示和格式转换,以确保在不同系统间的互操作性。它处理数据的压缩、加密、解密和格式转换等任务 | |
会话层(Session Layer) | 会话层管理不同应用程序之间的通信会话,建立、维护和终止会话连接。它提供会话控制和同步功能,支持多个会话和数据交换 | |
传输层(Transport Layer) | 传输层提供可靠的端到端数据传输服务,确保数据的完整性和顺序。它定义了传输协议(如TCP和UDP),并处理数据分段、流量控制和错误恢复 | TCP、UDP |
网络层(Network Layer) | 网络层负责在不同网络之间进行数据包的路由和转发。它为数据包选择最佳路径,并处理跨网络的寻址和拥塞控制 | IP、ICMP |
数据链路层(Data Link Layer) | 数据链路层管理通过物理层建立的点对点链接或局域网,确保可靠的数据传输。它处理帧的组装、错误检测和校正,并提供对物理层的透明访问 | Ethernet、ARP |
物理层(Physical Layer) | 物理层处理物理连接以及数据传输的物理介质,如电缆、光纤等。它负责为传输比特流提供传输媒介和基本的电气和物理特性 |
TCP/IP 四层模型
层 | 功能 |
---|---|
应用层(Application Layer) | 应用层是用户与网络进行交互的接口。它包含众多的协议和应用程序,用于实现各种网络服务和应用需求。常见的应用层协议有HTTP(超文本传输协议)、FTP(文件传输协议)、SMTP(简单邮件传输协议)等 |
传输层(Transport Layer) | 传输层提供端到端的数据传输服务。它定义了两个主要的传输协议:TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)。TCP提供可靠的、面向连接的数据传输,而UDP提供无连接的、不可靠的数据传输。该层还处理数据分段、流量控制和错误恢复等任务 |
网络层(Internet Layer) | 网络层实现了IP协议,负责在不同网络之间进行数据包的路由和转发。它使用IP地址标识主机和网络,并通过路由选择算法确定最佳路径。主要协议包括IP(Internet Protocol)、ICMP(Internet Control Message Protocol)和ARP(Address Resolution Protocol) |
网络接口层(Network Interface Layer) | 也称为数据链路层或物理层。该层处理与物理网络介质的直接交互,例如以太网、Wi-Fi等。它负责将数据包封装为帧,并进行物理传输 |
常见概念
万维网、互联网和以太网的区别
互联网(Internet)是一个概念,是广域网的一种(WAN,Wide Area Network),在全世界被广泛使用。互联网通过 TCP/IP 协议,把全球范围内的局域网连接在一起,可以实现点对点的网络通讯。
以太网(Ethernet)是一种常见的局域网(LAN,Local Area Network)技术,工作在物理层和数据链路层。在物理层,它定义了电气特性、信号传输速率和距离限制等规范。在数据链路层,以太网定义了帧格式、帧起始和结束标记、帧同步和帧检验序列等。
从网络模型来看,互联网是在网络层及之上构建的互联网络,而以太网则是工作在数据链路层及之下的一种技术。互联网是被人普遍认为的网络,而以太网则是被广泛使用的构建局域网的技术。
万维网(World Wide Web)是一种基于英特网的信息系统,通过超文本链接的方式来组织、检索和共享文档和资源。在万维网上,不同的文档和资源通过统一的标识符(URL,统一资源定位符)进行唯一标识。万维网的核心技术是HTTP(Hypertext Transfer Protocol),它定义了在网络上传输超文本资源的规范。
MAC 地址
MAC地址(Media Access Control Address)是网络设备(如网卡、无线适配器)在出厂时固定分配的唯一标识符。它是一个由12个十六进制数(0-9,A-F)组成的字符串,通常以冒号或短横线分隔。MAC地址由两部分组成:前6个十六进制数表示厂商代码(OUI,Organizationally Unique Identifier),用于标识设备制造商;后面的6个十六进制数是设备的序列号,由制造商分配。
MAC地址是在数据链路层上使用的,用于局域网内设备之间的直接通信。当设备在局域网上发送数据时,目标设备的MAC地址用于将数据帧传送到正确的设备。
需要注意的是,MAC地址只在局域网内部有效,不会跨越路由器传输。在互联网上进行通信时,数据包会根据目标IP地址进行路由,而不是MAC地址。
常见网络工具
ping
用于判断网络联通状态。
# ipv4 地址
ping 10.0.10.1
# ipv6 地址
ping6 2001:4860:4860::8888
注意:
- 没有收到消息回复,不一定是网络不通,也有可能是目标设备关闭了 ping 请求相应
telnet
可以远程连接开启 telnet 服务器的目标设备,也可以判断目标设备的某个端口的连通性。
telnet 10.0.10.1 8080
TCP/IP
建立/断开连接
TCP状态转换图
建立连接(三次握手)
- 客户端发送一个带SYN标志的TCP报文到服务器(报文1)
- 服务器端回应客户端(报文2), 这个报文同时带ACK标志和SYN标志. 因此, 它表示对刚才客户端SYN报文的回应, 同时又发送标志SYN给客户端, 询问客户端是否准备好进行数据通讯
- 客户端必须再次回应服务端一个ACK报文(报文3)
FLAGS | 含义 |
---|---|
SYN | 建立连接 |
FIN | 关闭连接 |
ACK | 响应 |
PSH | 有数据传输 |
RST | 重置连接 |
首部字段 | 说明 |
---|---|
seq | 序列号 |
ack | 对方需要发送的下一个报文序号 |
再述三次握手。
- 客户端想要建立连接,SYN 标志置为 1,序列号每次都有,所以将首部的 seq 设置为一个随机值 i
- 服务端确认建立连接,SYN 标志置为 1,作为响应报文,ACK 标志置为 1,同样设置首部的 seq 为一个随机值 j,同时设置 ack 指明对方下一次需要发送的报文序列为 i+1
- 客户端接收到服务端的连接请求,发送确认消息,作为响应报文,ACK 标志置为 1,同时将报文序列seq置为 i+1,请求对方下一次发送序列号为 j+1 的报文,ack 置为 j+1
终止连接(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。当一方完成它的数据发送后发送一个FIN来终止这个方向的连接,另一端收到FIN后,仍可以发送数据。首先进行关闭的一方执行主动关闭,另一方执行被动关闭。
- TCP客户端发送一个FIN, 用来关闭客户端到服务端的数据传送
- 服务端收到FIN, 发回一个ACK, 确认序号为收到的序号加一. 和SYN一样, 一个FIN占用一个序号
- 服务端关闭客户端的连接, 发送一个FIN给客户端
- 客户端发回ACK报文确认, 并将确认序号设置为收到的序号加一
再述四次握手,终止连接。
终止连接,需要双方都发送终止请求,并回复对方。
- 客户端主动关闭,置 FIN 为 1,序列号seq置为u
- 服务端被动关闭,回复客户端的关闭请求,因为是响应报文,置ACK为 1,指明对方下一个报文序列,ack 置为 u+1
- 服务端开始主动关闭,置 FIN 为 1,序列号 seq 置为 w,指明对方下一个报文序列,ack 置为 u + 1
- 客户端被动关闭,恢复服务端的关闭请求,因为是响应报文,置ACK为1,返回对方想要的报文序列,置 seq 为 u+1,指明对方下一个报文序列,ack 置为 w+1
状态
状态 | 说明 | 备注 |
---|---|---|
CLOSED | 初始状态 | |
LISTEN | 监听状态, 可以接受连接 | |
SYN_RCVD | 接受到SYN 报文 | 正常情况下, 这个状态是服务端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态. 在这种状态时, 当收到客户端的ACK报文后, 会进入ESTABLISHED状态 |
SYN_SEND | 客户端已发送SYN报文 | 与SYN_RCVD 呼应, 当客户端SOCKET执行CONNECT连接时, 首先发送SYN报文, 随即进入SYN_SEND状态, 并等待服务端发送三次握手中的第二个报文 |
ESTABLISHED | 连接已建立 | |
FIN_WAIT_1 | 等待对方FIN报文 | FIN_WAIT_1和FIN_WAIT_2都表示等待FIN报文. FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1状态, 而当对方回应ACK报文后,则进入到FIN_WAIT_2状态. |
FIN_WAIT_2 | 等待对方FIN报文 | FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接 |
TIME_WAIT | 收到对方FIN报文, 并发送了ACK报文 | 等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态 |
CLOSING | 表示发送FIN报文后, 没有收到对方的ACK报文, 却收到了对方FIN报文 | 如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报 文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接 |
CLOSE_WAIT | 等待关闭 | 收到对方FIN报文, 并回应了ACK报文, 进入到CLOSE_WAIT状态. 在CLOSE_WAIT状态下,需要完成的事情是等待关闭连接. |
LAST_ACK | 被动关闭一方在发送FIN报文后, 等待对方的ACK报文 | 当收到ACK报文后, 进入CLOSED状态 |
同时关闭/打开连接
问题
为什么建立连接是三次握手, 关闭连接是四次握手?
这是因为,服务端的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
TIME_WAIT后还需要SMSL
TCP协议在关闭连接的四次握手过程中,最终的ACK是由主动关闭连接的一端(后面统称A端)发出的,如果这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,因此A端必须维护状态信息(TIME_WAIT)允许它重发最终的ACK。如果A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
因而,要实现TCP全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的A端必须维持TIME_WAIT状态 。
允许老的重复分节在网络中消逝(实际也就是避免同一端口对应多个套接字). TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。
关闭TCP连接一定需要四次握手吗
不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_WAIT状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT 对于我们来说往往是有利的。
TCP如何保证可靠传输
- 序列号、确认和重传:每条报文指定一个序列号,接收方收到报文会发送确认,如果发送方迟迟未收到确认,就重传
- 数据校验
- 连接管理
- 窗口控制
- 流量控制
- 拥塞控制
参考
Nagle算法
规则
- 如果包长度达到MSS, 则允许发送
- 如果包还有FIN, 则允许发送
- 设置了TCP_NODELAY选项, 则允许发送
- 未设置TCP_CORK选项时, 若所有发出去的小数据包(包长度小于MSS)均被确认, 则允许发送;
- 上述条件都未满足, 但发生了超时(一般为200ms), 则立即发送
其他说明
- Nagle算法只允许一个未被ACK的包存在于网络,它并不管包的大小,因此它事实上就是一个扩展的停-等协议,只不过它是基于包停-等的,而不是基于字节停-等的
- Nagle算法完全由TCP协议的ACK机制决定,这会带来一些问题,比如如果对端ACK回复很快的话,Nagle事实上不会拼接太多的数据包,虽然避免了网络拥塞,网络总体的利用率依然很低
- Nagle算法是silly window syndrome(SWS)预防算法的一个半集。SWS算法预防发送少量的数据,Nagle算法是其在发送方的实现,而接收方要做的是不要通告缓冲空间的很小增长,不通知小窗口,除非缓冲区空间有显著的增长。这里显著的增长定义为完全大小的段(MSS)或增长到大于最大窗口的一半。
- 注意:BSD的实现是允许在空闲链接上发送大的写操作剩下的最后的小段,也就是说,当超过1个MSS数据发送时,内核先依次发送完n个MSS的数据包,然后再发送尾部的小数据包,其间不再延时等待。(假设网络不阻塞且接收窗口足够大)
- 当有一个TCP数据段不足MSS,比如要发送700Byte数据,MSS为1460Byte的情况。nagle算法会延迟这个数据段的发送,等待,直到有足够的数据填充成一个完整数据段
- 为了解决大量的小报文对通信造成的影响,提高传输效率
参考
其他
- ACK: 应答作用
- SYN: 同步作用
I/O
阻塞 I/O
通常I/O操作都是 阻塞I/O,以一次读操作为例,数据先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间,对应数据准备、数据拷贝两个阶段。在数据准备阶段,线程/进程会被挂起,当数据拷贝至应用程序内存空间后,线程/进程被唤醒。
非阻塞 I/O
如果使用阻塞I/O,同时处理1000个请求,则需要1000个线程,虽然大多数线程可能被挂起,但不可避免的是频繁的线程上下文切换,非常占用系统资源,每个线程的时间槽也会非常短,导致系统整体有效负载降低,此外每个线程还会占用 512KB / 1MB 的内存。
此时,引入了 非阻塞I/O 的概念,通过调用 fcntl(POSIX)/ ioctl(Unix)设为非阻塞模式。在此模式下,如果有数据收到,就会返回数据,否则立即返回一个错误。这样虽然不会阻塞线程,但需要线程不断轮询来读取或写入。
I/O 多路复用
为了解决单线程轮询单个文件描述符效率低下的问题,有了 I/O多路复用 的概念,多路是指多个文件描述符(socket连接),复用指的是复用一个线程。
所以,I/O多路复用是指,使用一个线程来检查多个文件描述符的就绪状态,如调用 select / poll 函数,传入多个文件描述符,如果有一个就绪,则返回,否则阻塞至超时。
这样在处理1000个连接时,只需要1个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。
I/O多路复用(多路是指多个文件描述符(fd,file descriptor,socket 是fd的一种),复用指的是复用一个线程),主要技术有 select、poll、epoll。
- | select | poll | epoll |
---|---|---|---|
时间处理复杂度 | O(n) | O(n) | O(1) |
连接数 | 需要通过 FD_SETSIZE 设置最大连接数 | 无限制 | |
事件触发机制 | 轮询 | 轮询 | 回调 |
工作模式 | LT | LT | LT / ET |
fd 拷贝 | 每次调用,每次拷贝 | 每次调用,每次拷贝 | 通过 mmap 内存映射,避免内存拷贝 |
select
select 会将全量fd_set
从用户空间拷贝到内核空间,并注册回调函数, 在内核态空间来判断每个请求是否准备好数据 。select在没有查询到有文件描述符就绪的情况下,将一直阻塞(select是一个阻塞函数)。如果有一个或者多个描述符就绪,那么select将就绪的文件描述符置位,然后select返回。返回后,由程序遍历查看哪个请求有数据。
缺陷
- 牵扯到两次内存拷贝,第一次将 fd_set 从用户空间拷贝至内核空间;第二次是将 fd_set 从内核空间拷贝至用户空间
- 牵扯到两次集合遍历,都是对 fd_set 遍历,第一次发生在内核态,第二次发生在用户态
- select 文件描述符有上限,及 fd_set 的容量,虽然可以通过宏 FD_SETSIZE 来设置
poll
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd
结构代替select的fd_set
(网上讲:类似于位图)结构,其他的本质上都差不多。所以Poll机制突破了Select机制中的文件描述符数量最大为1024的限制。
缺陷
- 同样会有两次遍历和内存拷贝
epoll
epoll 对 select 的三个缺陷进行了修复。
解决内存拷贝:epoll 方式下,fd 是通过mmap共享内存的方式,避免在用户空间和内核空间的拷贝。
解决多次遍历:epoll不像select或poll一样每次都把当前线程轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把当前线程挂一遍(这一遍必不可少),并为每个fd指定一个回调函数。当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。那么当我们调用epoll_wait时,epoll_wait只需要检查链表中是否有存在就绪的fd即可,效率非常可观。
解决fd 数量限制:pollfd 结构代替 fd_set 解决数量限制, 使用双向链表保存就绪状态的fd。
工作模式
epoll 分为两种工作模式,LT(level trigger,水平触发) 和 ET(edge trigger,边缘触发),两种模式的区别是在于对就绪fd的处理。
- LT模式: 默认的工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;事件会被放回到就绪链表中,下次调用epoll_wait时,会再次通知此事件。
- ET模式: 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应并通知此事件。
异步 I/O
进程 / 线程在发起IO操作之后,内核会立刻返回,不会阻塞用户线程/进程。当内核把数据准备完毕后,向用户进程发送一个signal通知。