分层后的互联网世界
application
7 +---------------------------------------------------------------------------------------------+
| NameSystem: Host Network File E-Mail&News: WWW&Gopher: Inter-active: |
6 | >DNS Config: Mgmt: Transfer: >RFC822 / MIME >HTTP >Telnet |
| >BOOTP >SNMP >FTP >SMTP >Gopher >"r" Commands |
5 | FileSharing: >DHCP >RMON >TFTP >POP / IMAP >IRC |
| >NFS >NNTP |
+---------------------------------------------------------------------------------------------+
Transport(作为主机内的进程寻址,和其他功能)
4 +---------------------------------------------------------------------------------------------+
| User Datagram Protocol Transmission Control Protocol |
+---------------------------------------------------------------------------------------------+
Internet
3 +---------------------------------------------------------------------------------------------+
| +----------------------------+ +-----------------+ +-----------------+ |
| |InternetProtocol +--------+ | |IP Support | |IP Routing | |
| | (IP/IPv4,IPv6) | IP NAT | | |Protocols | |Protocols | |
| | +--------+ | | +-------------+ | | +-------------+ | |
| | +--------+ | | |ICMP/ICMPv4, | | | |RIP,OSPF, | | |
| | | IP Sec | | | |ICMPv6 | | | |GGP,HELLO, | | |
| | +--------+ | | +-------------+ | | |IGRP,EIGRP, | | |
| | +--------+ | | |Neighbor | | | |BGP,EGP | | |
| | |MobileIP| | | |Discovery(ND)| | | | | | |
| | +--------+ | | +-------------+ | | +-------------+ | |
| +----------------------------+ +-----------------+ +-----------------+ |
+---------------------------------------------------------------------------------------------+
+---------------------------+ +-----------------------------------+
|Address Resolution Protocol| |Reverse Address Resolution Protocol|
+---------------------------+ +-----------------------------------+
Network Interface
4 +---------------------------------------------------------------------------------------------+
| Serial Line Interface Protocol Point-to-Point Protocol (LAN/WLAN/WAN Hardware Driver) |
+---------------------------------------------------------------------------------------------+
1 TCP报文格式
1.1 TCP的作用
在Web端发出一个GET请求,它的流程是怎样的呢 ?
- 客户端发出一个GET请求,我们假定这是一种HTTP Request。
- 数据链路层,将这个HTTP Request发送到路由器上。
- 根据IP层来选定最短路径
- TCP层,把不定长度的HTTP Request切分成TCP认为合适的Segment段,然后发送到目的Server上。 在中间的任意一个节点中,报文都有可能丢掉,而且路由也有可能发生变换,所以,TCP必须保证, 每个Segment都能到达目的Server。当目的Server接收到所有的Segment之后,由于TCP层都是由 操作系统内核来实现的,所以Kernel会按照相同的顺序把HTTP Request扔给了上层的Nginx。 Nginx会生成一个Web页面,然后使用相同的路径,返回给客户端。
- 注意,报文会在TCP层被分隔为多个Segment段,但是Segment段的组合,却不是在TCP层中实现的。
1.2 TCP的定义
面向连接的、可靠的、基于字节流的传输层通信协议
-
面向连接的:一对一才能连接,所以TCP协议不能像UDP协议那样,一个主机同时向多个主机发送同一条消息 如何标识一个连接?
-
使用TCP四元组(源地址,源端口,目的地址,目的端口)来唯一标识一个连接:
- 对于IPv4地址,单主机最大TCP连接数为2^(32+16+32+16)
-
在QUIC(HTTP3)协议中,使用
Connection ID
来唯一标识一个连接的。 一旦生成完Connection ID
之后,哪怕TCP四元组发生了变化,我们仍然可以复用同一个连接。+----------------------+-------------------------------+ | Public Flags(8 bits) | Connection ID (64 bits) | 9bytes +----------------------+-------------------------------+ | QUIC version (32 bit) | 4bytes | (Optional) | +------------------------------------------------------+ | Diversification nonce (32 bytes) | 32bytes | (Optional) | +------------------------------------------------------+ | Packet Number(8, 16, 32 or 48) | 6bytes | (Variable length) | (max) +------------------------------------------------------+
-
-
可靠的:无论链路发生了怎样的变化,TCP都能保证一个报文,一定能最终到达接收端
-
基于字节流:表示我们的消息其实是没有边界的,所以无论我们的消息有多大,都可以进行传输。 基于字节流,它是有序的,当前一个字节接收端没有接收到的时候,即使它收到了后一个字节, 也不能扔给应用层去处理。
1.3 TCP的报文格式
- UDP头部
Byte |0 |1 |2 |3 |
Offset +--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|---
0| | | ^
| Source Port | Destination Port | |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--| 8 Bytes
4| | | |
| Length | Checksum | V
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|---
Bit |0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 20 1 2 3 4 5 6 7 8 9 30 1
-----------------Checksum----------------------- --------------------RFC 768---------------------
Checksum of entire UDP segment and pseudo Please refer to RFC 768 for the complete User
header (parts of IP header) Datagram Protocol(UDP) Specification
- TCP头部
Byte |0 |1 |2 |3 |
Offset +--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|-----
0| | | ^ ^
| Source Port | Destination Port | | |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--| | |
4| | | |
| Sequence Number | 20 |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--| | |
8| | | |
| Acknowledgment Number | | Offset
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--| | |
12| | | TCP Flags | | | |
| Offset | Reserved | C E U A P R S F| Window | | |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--| | |
16| | | | |
| Checksum | Urgent Pointer | V |
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|-- |
20| | |
| TCP Option(variable length, optional) | V
+--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|--|-----
|0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 20 1 2 3 4 5 6 7 8 9 30 1
-------TCP Flags(2)--- --Congestion Notification-- -------TCP Options---------- -------Offset(1)--------
C E U A P R S F ECN(Explicit Congestion 0 End of Options List Number of 32-bit words in
Congestion Window Notification).See RFC3168 1 No Operation(NOP,Pad) TCP header,minimum value of
C 0x80 Reduced(CWR) for full details. valid 2 Maximum segment size 5. Multiply by 4 to get
E 0x40 ECN Echo(ECE) states below. 3 Window Scale byte count
U 0x20 Urgent PacketState DSB ECNbits 4 Selective ACK ok
A 0x10 Ack Syn 00 11 8 Timestamp ------RFC 793-----------
P 0x08 Push Syn-Ack 00 01 Please refer to RFC 793 for
R 0x40 Reset Ack 01 00 ------Checksum--------------- the complete Transmission
S 0x20 Syn No-Congestion 01 00 Checksum of entire TCP segment Control Protocol(TCP)
F 0x01 Fin No-Congestion 10 00 and pseudo header(parts of IP Specification.
Congestion 11 00 header)
Receiver Response 11 01
Sender Response 11 11
常用选项
类型 | 总长度(字节) | 数据 | 描述 |
---|---|---|---|
0 | - | - | 选项列表末尾标识 |
1 | - | - | 无意义,用于32位对齐使用 |
2 | 4 | MSS值 | 握手时发送端告知可以接收的最大报文段大小 |
3 | 3 | 窗口移位 | 指明最大窗口扩展后的大小 |
4 | 2 | - | 表明支持SACK选择性确认中间报文段功能 |
5 | 可变 | 确认报文段 | 选择性确认窗口中间的Segments报文段 |
8 | 10 | Timestamps时间戳 | 用于更精确的计算RTT,及解决PAWS问题 |
14 | 3 | 校验和算法 | 双方认可后,可使用新的校验和算法 |
15 | 可变 | 校验和 | 当16位标准校验和放不下时,放置在这里 |
34 | 可变 | FOC | TFO中Cookie |
2 三次握手
|
|
4 状态变迁
* 这写状态该怎么查看呢?在linux中可以使用ss命令,在mac中可以使用lsof命令。
* TCB: Transmission Control Block,保存连接使用的源端口、目的端口、目的ip、序号、应答序号、
对方窗口大小、己方窗口大小、tcp状态、tcp输入/输出队列、应用层输出队列、tcp的重传有关变量等。
+------------------------------+
+-----V----+ |
| CLOSED |-----------+ |
+-----V----+ | |
+---PassiveOpen----+ | |
| SetUp TCB | |
+------V-----+ Active Open |
| LISTEN | Set Up TCB |
+------------+ Send SYN |
| | |
Receive SYN V |
Send SYN+ACK | |
+------V-----+ SimultaneousOpen +------V-----+ |
|SYN-RECEIVED|<----Receive SYN------|SYN-SENT | |
+------------+ Send ACK +------------+ |
| | |
Receive ACK Receive SYN-ACK |
| Send ACK |
+--------------+ +-------------+ |
| | |
Open-Responder V V Open-Initiator |
Sequence +--------------+ Sequence |
| ESTABLISHED | |
Close-Initiator +--------------+ Close-Responder |
Sequence Close | | Sequence |
Send FIN---+ +---Receive FIN-+ |
| Send ACK | |
+-----V----+ +----V-------+ |
|FIN-WAIT-1| |CLOSE-WAIT | |
+----------+ SimultaneousClose +------------+ |
| | | |
+------+ +-Receive---+ WaitForApplication |
ReceiveACK for FIN,SendACK| Close,Send FIN |
FIN | | |
| | | |
+-----V-----+ +------------+ +-------V----+ |
| FIN-WAIT-2| | CLOSING | | LAST-ACK | |
+-----------+ +------------+ +------------+ |
| | | |
ReceiveFIN Receive ACK ReceiveACKforFIN |
Send ACK for FIN +------>-+
| +--------V---+ ^
+----------->| TIME-WAIT |-->----TimerExpiration---->-+
+------------+ 2*MSL(Maximum Segment Life)
* 关闭连接的目的:防止数据丢失,处理与应用层的交互
4.3 三次握手中的性能优化与安全
服务器三次握手流程实例
|
|
首先客户端发来SYN分组,到达了我们的服务器,首先在我们的服务器内核中,也就是对TCP层的一个实现,那么会把这个SYN分组插入到我们一个叫做SYN队列中,同时呢,同时发出一个SYN/ACK,所以此时如果使用netstat去查看端口的状态的时候,我们会看到SYN-RECEIVED状态。 什么时候由SYN-RECEIVED状态进入ESTABLISHED状态呢。会在客户端再次发来"ACK网络分组"以后,就会进入ESTABLISHED状态了,但是实际上,在我们的Kernel,会把这样的一个SYN从SYN队列中移除,并放进ACCEPT队列中。 当我们的应用程序,比如说Nginx,它在调用accept方法的时候,就从我们这个ACCEPT队列中,把刚刚的那个连接拿出来,给相关的方法去使用了。
所以,在这个过程中呢,我们可以看到,这个SYN队列长度和这个ACCEPT队列的长度,跟我们负载都是相关的,如果我们这是一个面向着每秒钟服务几十万请求这样一个高负载的机器,对SYN队列,ACCEPT队列,都是需要延长的。
那么怎么样进行延长呢?
超时时间与缓冲队列
- 应用层connect超时时间调整
- 操作系统内核限制调整
- 服务器端
SYN_RCV
状态net.ipv4.tcp_max_syn_backlog: SYN_RCVD
状态连接的最大个数net.ipv4.tcp_synack_retries
: 被动建立连接时,发SYN/ACK的重试次数
- 客户端
SYN_SENT
状态net.ipv4.tcp_syn_retries=6
主动建立连接时,发SYN的重试次数net.ipv4.tcp_local_range=32768 60999
建立连接时的本地端口可用范围
- ACCEPT队列设置
- 服务器端
Linux上打开TCP Fask Open
net.ipv4.tcp_fastopen
: 系统开启TFO功能- 0: 关闭
- 1: 作为客户端时可以使用TFO
- 2: 作为服务器时可以使用TFO
-
- 无论作为客户端还是服务器,都可以使用TFO
Fast Open降低时延
Regular Fast Open
Client Server | Client Server
1st Req |-------SYN-------->| | 1st Req |-------SYN---------->|
|<---SYN+ACK--------| | |<---SYN+ACK+Cookie---|
2*RTT |--ACK+HTTP+GET---->| | 2*RTT |--ACK+HTTP+GET------>|
|<-----Data---------| | |<-----Data-----------|
| | | | |
... ... | ... ...
| | | | |
2st Req |-------SYN-------->| | 2st Req |-SYN+Cookie+HTTP GET>|
|<---SYN+ACK--------| | 1*RTT |<---SYN+ACK+Data-----|
2*RTT |--ACK+HTTP+GET---->| | | |
|<-----Data---------| | | |
如何应对SYN攻击 攻击者段时间伪造不同IP地址的SYN报文,快速占满backlog队列,使服务器不能为正常用户服务。
net.core.netdev_max_backlog
- 接收自网卡、但未被内核协议栈处理的报文队列长度
net.ipv4.tcp_syn_backlog
SYN_RCVD
状态连接的最大个数
net.ipv4.tcp_abort_on_overflow
- 超出处理能力时,对新来的SYN直接回包RST,丢弃连接
net.ipv4.tcp_syncookies=1
- 当SYN队列满后,新的SYN不进入队列,计算出cookie再以SYN+ACK中的序列号返回客户端,
正常客户端发报文时,服务器根据报文中携带的cookie重新恢复连接
- 由于cookie占用序列号空间,导致此时所有TCP可选功能失效,例如扩充窗口、时间戳等。
- 当SYN队列满后,新的SYN不进入队列,计算出cookie再以SYN+ACK中的序列号返回客户端,
正常客户端发报文时,服务器根据报文中携带的cookie重新恢复连接
recvSYN sendSYN+ACK 正常流程
| ^
v /
{SYN Queue} Application
\recvACK []
\ ^ accept()
V /
{AcceptQueue}
recvSYN 应用程序过慢
|X
v
{SYN Queue} Application
\recvACK []
\X ^ accept()
V /
{AcceptQueue} 造成AcceptQueue队列满了
recvSYN sendSYNcookie SYN队列满,启用cookie
| ^
v /
{SYN Queue} Application
\ validate []
\recvACK ^ accept()
V /
{AcceptQueue}
造成SYN队列满了
TCP_DEFER_ACCEPT
当服务器收到了ACK网路分组之后呢,其实它已经把连接(也就SYN序列号)放在了ACCEPT队列中了,此时,内核究竟要不要激活应用程序呢?如果这个时候,并不激活应用程序,直到我们收到了data分组,实际消息报文以后,我们再激活我们的应用程序,使得我们的应用程序呢,效率更高。 这个功能呢,就叫做TCP_DEFER_ACCEPT
4.4 优化关闭连接时的TIME-WAIT
在上节课中,介绍到,主动关闭的一方,最后会进入到TIME-WAIT状态,这个TIME-WAIT状态呢,通常会保持2分钟左右,也就是说有两分钟的时间这个端口是被占用的,这对于同时处理大量TCP连接的服务器来说,是一个非常大的负担。所以我们总是在试图去减少TIME-WAIT状态所持续的时间。那么这节课中呢,我们将介绍应当怎么样去优化TIME-WAIT状态中的端口的数量。
TIME-WAIT状态过短或者不存在会怎么样?
-
MSL(Maximum Segment Lifetime)
- 报文最大生存时间
-
持续2MSL时长的TIME-WAIT状态
- 保证至少一次报文的往返时间内 端口是不可复用的。
比如上面这张图中,我们现在发送了三个报文,1和2都正常被接收到了,但是3丢掉了。 那么这个时候呢,如果我们右边这端主动发送了FIN包,进入了FIN-WAIT-1;然后呢, 我们客户端呢,也发送了FIN,ACK,那么正常的关闭。如果没有TIME-WAIT状态,或者说, TIME-WAIT状态非常短的时候,就有可能发生下面的事情: 比如,我们同时又复用了这样的端口,因为我们的一个连接是通过端口+IP这样的一个四元组来标识的,我们前面介绍过,所以,当我们复用这个端口的时候,意味着相当于之前的那个连接。那么这时候,我们建立好三次握手以后,我们正在发送数据的时候,假定,这个报文其实不是丢失了,它只是在网络中被延迟了,现在它突然收到了,那么就会对这个接收方造成一个数据错乱的一个影响。所以我们TIME-WAIT状态是有保护的一个效果的,它保护着不让延迟发过来的数据,来扰乱我的新连接,那么我们的TIME-WAIT状态应该保存多长时间呢,我们通常认为它是2MSL。
什么叫做MSL呢,就是Maximum Segment Lifetime,就是一个报文在网络中存活的一个最长的时间,为什么是2倍的MSL呢,因为我们必须保证一次的报文它的往返时间内,它的端口都是不可复用的。
linux下TIME_WAIT优化: tcp_tw_reuse
net.ipv4.tcp_reuse = 1
- 开启后,作为客户端时新连接可以使用仍然处于
TIME_WAIT
状态的端口 - 由于timestamp的存在,操作系统可以决绝迟到的报文
net.ipv4.tcp_timestamps = 1
- 开启后,作为客户端时新连接可以使用仍然处于
-| |
|\ |
ESTABLISHED |\*--------------->|
| *--------------->|-
| /|
-|<-FIN-----------* | FIN-WAIT-1
|\ |
| *--------FIN,ACK>|-
| /|
| X<-----* | TIME-WAIT
LASK-ACK | |-
| /|
|<------------SYN* |
|\ |
| *--------FIN,ACK>| SYN-SENT
| /|
-|<-RST-----------*/|
|<------------SYN* |
ESTABLISHED |\ |-
|\*--->----SYN,ACK>|
-|\*--->----------->|ESTABLISHED
| *--->----------->|-
由于linux服务器,它同时处理了大量的TCP连接,所以TIME-WAIT状态对它的影响比较大,那么这时候Linux下有一个选项叫做tcp_tw_reuse
。使用它的危险性比较小,因为当把它指向1,也就是开启这个功能以后呢,它仅仅意味着,当我们作为客户端的时候,我们建立一个新的连接时,可以使用仍然处于TIME-WAIT状态的端口。我们前边曾经介绍过,TCP的timestamp这样的一个功能,那么由于有这样的一个OPTIONS选项的一个存在呢,所以我们就可以拒绝迟到的报文,因为迟到的报文它有timestamp,我知道它是率属于上一个连接,而不是我当前的这个连接,我就可以把它拒绝掉。当然我要启用的时候呢,必须把net.ipv4.tcp_timestamps
置为1。
TIME_WAIT优化
net.ipv4.tcp_tw_recycle=0
- 开启后,同时作为客户端和服务器都可以使用TIME-WAIT状态的端口
- 不安全,无法避免报文延迟、重复等给新连接造成混乱
net.ipv4.tcp_max_tw_buckets=262144
time_wait
状态连接的最大数量- 超出后直接关闭连接
RST复位报文 当然出现一些异常情况的时候,我们可以发送RST复位报文,直接关闭连接,绕开4次握手。 比如我们的进程突然关掉了,或者遇到了一些非常严重的错误,我们都会把RST置位1,来表示复位报文。对于复位报文,在wireshark中实际上是用红色来表示的。
5. 其他
5.1 keepalive、校验和、带外数据。
TCP的Keep-Alive功能
- Linux的tcp keepalive
- 发送心跳周期
- Linux:
net.ipv4.tcp_keepalive_time=7200
- Linux:
- 探测包发送间隔
net.ipv4.tcp_keepalive_intvl=75
- 探测包重试次数
net.ipv4.tcp_keepalive_probes=9
- 发送心跳周期
比如说我们现在有一个长连接,那么这个长连接呢,长时间没有传递任何的数据,但它却是在占用着我们两端的资源,比如说我们需要为每一个连接分配一个TCB,那么这个TCB占用着我们的内存。
而keep-alive呢,就是想对于这种长时间没有发送任何数据的连接,我们把它关掉,那么tcp keepalive,怎样把这个连接关掉呢。它采用这样的一个方式,首先我们要先设置一个周期,比如,默认在linux下,有一个tcp_keepalive_time
,默认是7200秒,也就是2小时,在两小时没有收发任何数据的情况下,我们将开启keep-alive检测功能。那么keep-alive检测功能呢,将会发送多个探测包,这些探测包呢,将会一个一个的发送,那么我们发送完一个探测包,如果收到对方的应答以后呢,那么我们这个TCP连接仍然是活跃的,那么keep-alive将进入下一个7200秒的一个潜伏状态;如果我们没有收到对方发来的应答的话,我们将间隔75在发第二个。那么我们最多发送多少个报文,而且都没有收到对方的应答以后,我们将关闭TCP连接。
那么应该是多少个呢,默认是9个。
违反分层规则的校验和
- 对关键头部数据(12字节)+TCP数据执行校验和计算
- 计算中假定checksum为0
0 4 8 12 16 20 24 28 32
+--------+---------+---------+---------+---------+---------+---------+---------+
| Source Address |
| (from IP Header) |
+------------------------------------------------------------------------------+
| Destination Address |
| (from IP Header) |
+------------------+-------------------+---------------------------------------+
| Reserved | Protocol | TCP Segment Length |
| | (from IP Header) | (computed) |
+------------------+-------------------+---------------------------------------+
+-------+-------+--------+ +-------------------+------+------------------------+
|Source |Dest |Reserved| | |Check-| |
|Address|Address|Protocol| | TCP Header | sum | TCP Data |
| | | TCP | | +------+ |
| | | Length | | | |
+-------+-------+--------+ +--------------------------+------------------------+
.. Pseudo-Header TCP Segment ..
+--------------------------------------|---------------------------------------+
Checksum
我们在TCP Header的字段中,可以看到有一个16位2个字节的校验和字段,那么这个校验和对那些内容进行校验呢,它不光针对我们TCP上承载的数据,也对我们TCP的头部进行校验,实际上它还对IP头部中的一部分进行校验,从这个层面来说,它已经违反了分层原则了。因为它同时对IP层做一些校验。ip层哪些东西做校验了呢,通常是对源IP地址和目的IP地址,还有我们承载的协议,等等,这些信息,为什么要对IP地址做些校验呢,因为这时候如果我们发现这个TCP Segment其实不是发给自己的,那么我们就可以更快的发现这样的一个问题。那我们的校验和呢,默认采用的是一个累加和这样的一个方式,但是我们在前面介绍TCP Options的时候,也说过,我们可以改变这样的一个校验和算法,我们可以把校验和这两个字节不够放的话,我们也可以放在Option中。
应用调整TCP发送数据的时机 我们再来看一下,我们在TCP中有一个PSH这样的一个flag,它可以调整我们应用发送数据的一个时机,比如我们现在应用调用了一个Write方法,这个Write方法,假设发了10M的数据,那么这个10M的数据其实会被拆分成很多个Segment,每个Segment不能大于MSS。那么最后一个报文呢,就会自动在它的TCP头部的PSH中,置位1,那么表示如果收到这个报文以后,请接收方尽快的把缓冲区中的所有内容交给应用进程开始处理,而不要去等待必须缓冲区达到了多少字节再处理。因为通常我们发送端调完一个Write方法,都表示完整的一个消息,所以接收方应该在接收到最后一个Segment段的时候,尽快的让应用进程去处理。
紧急处理数据 我们其实还有一个flag标志位,URG,它的含义是紧急标志位,通常我们会称它为带外数据,其实它并不是一个"带外数据",我们可以考虑一下这样的一个场景。比如说telnet中,我们正在敲击很多个字符,那么这些字符在网络中呢可能传输出现了拥塞,这个时候我们突然按了Ctrl-C,我们想终止这个Telnet连接,那么其实这个终止的这样的一个行为,我们就可以在这样的一个报文中,把URG置为1。这样,我们的Server,如果接收到以后呢,不管我的缓冲区是不是含有数据还没有处理,我将优先的去处理这个URG标志位。或者说,我现在有一个ftp,我们正在下载文件,那么我刚开始准备下载一个大文件的时候呢,我突然又把这个文件取消了,那么在取消的这个操作中呢,我们也应该在URG中置为1。
5.2 面向字节流的TCP连接如何多路复用
TCP是一个面向不定长的字节流的协议,因此对它进行多路复用是较为困难的,这节课我将从编程层面上介绍如何对多个TCP连接实现多路复用。
Multiplexing多路复用
- 在一个信道上传输多路信号或数据流的过程和技术 FDMA: 我们在使用对讲机的时候,所使用到的频分多值技术,就是在一个很宽的信道上我们把频率划分成许多个用户,也就是分给我们的对讲机用户使用,在同一段电磁波中,我们就传输了许多路信号,这就是一种多路复用。
TDMA: 在2G时代呢,我们使用TDMA,时分多值技术,那么这也是一种多路复用。我们在同一时间只传输一个用户的信号,但是我们把信号传输的时间片切得非常的短,在一段时间内呢,我们每过一个时间点,我们就传输另外一个不同用户的信号,我们叫做时分多值。
CDMA: 到了3G时代以及3G之后的时代,我们都使用码分多值,我们采用不同的编码来同时传输多个用户的数据,那么这个也叫多路复用。
HTTP2: TCP连接之上的多路复用 实际上在第三部分的课程中,我们曾经介绍过一个TCP连接上,我们跑了很多个stream,当然每个stream上都是一个HTTP的Message,那么这也叫多路复用,这个多路复用针对的是我们HTTP的请求。
非阻塞socket: 同时处理多个TCP连接
Nonblocking I/O Model
application kernel
+ read ------system call----> no data ready +
| <---EWOULD BLOCK------ |
process | read ------system call----> |wait
repeatedly | <---EWOULD BLOCK------ |for data
calls read | read ------system call----> |
waiting for | data ready +
an OK | copy data +
(polling) | | |copy data
| V |from kernel
| process <----return OK-------- copy complete +to user
+ data
而对于我们的TCP连接呢,这里的多路复用我们其实是对编程而言的, 我们进程同一时刻,只处理一个TCP连接是非常容易的,如果在同一时刻我想处理许多个TCP连接,这个时候我首先要借助一个工具,叫做非阻塞socket。那么什么叫非阻塞socket呢。当我们基于非阻塞socket去读取一块数据的时候呢,我们不需要等待我们内核中的读缓冲区里一定有数据,或者说等待某个超时时间,我们的read函数才会返回。而是一旦没有数据,我立刻就返回。如果有数据,我马上就拷贝给你。那么对于我们的write写函数,也是一样的,如果我们写缓冲区或者说我们的可用的发送窗口,那都是为0的时候,我们的write立刻就返回,告诉我一个字节也没有写进去。
如果我有可用的写缓冲区时,我就能写多少就写多少。那么这些都叫做非阻塞socket上的read和write。
那么基于非阻塞socket呢,我就有可能在进程中同一时刻处理许多个TCP连接,但我们还需要借助一个工具,这个工具叫做epoll。
epoll+非阻塞socket
- epoll出现:linux2.5.44
- 进程内同一时刻找出缓冲区或者连接状态变化的所有TCP连接,然后 返回给我们的进程,这样我们的进程就可以基于所有的非阻塞socket, 那么快速的处理所有的tcp连接。
- 3个API
epoll_create
epoll_ctl
epoll_wait
+------------+
+-------+ |epoll_create|
|socket | +------------+
+-------+ |
|------->-注册------> +------V-----+
+---V---+ \|epoll_ctl |<-+
|bind | +------------+ |
+-------+ | |
| | |
| +------V----+ |
+---V---+ |events | |
|listen | +----^------+ |
+-------+ / |
| .---------------------* |
| / +---------+ |
+---V------+ /| accept |--------+
|epoll_wait|---------* +---------+
+----------+\ \ +---------+
\ *----->| send |
\ +---------+
\ +---------+
*----->|receive |
+---------+
epoll为什么高效?
-
活跃连接只占总连接的一小部分 epoll为什么会如此高效呢,其实是基于一个理念,也许我同一时间处理了100万个连接, 但是同一时刻它的活跃连接可能只有几千个,只占总连接数的一小部分,所以呢, epoll在它的实现中,它有两个核心的数据结构,第一个是一个红黑树,这个红黑树 中呢,存放了所有的连接,但是当我们的读写缓冲区发生了变化,或者是我们TCP连接 发生了变化,当然发生变化是由于有事件的触发,那么对于发生变化的这样的一些TCP 连接,我们就把它放在一个队列中,调用
epoll_wait
的时候,只返回这个很小的队列中 的一些TCP连接,我们整体的epoll就会非常的高效。当然有了epoll,有了非阻塞socket,我们的编程其实是叫做异步编程,相对是比较麻烦的。
|2.epoll_ctl ^3.epoll_wait
| |
+--------|---------------------|----+
| | | |
| +------V-----+ +----+ |
| | () | | () | |
| | / \ | | | | |
1.epoll_create| | () () |-事件触发-->| () | |
------------->| | / \ | | | | |
| | () () | | () | |
| +------------+ +----+ |
| |
+-----------------------------------+
非阻塞+epoll+同步编程=协程
/MainThread /Conroutine1 /Conroutine2
local client = redis:new() | | |
|---1:spawn()->[|] |
client:set_timeout(30000) | [|] |
.----|--->--------->[|] |
local ok,err = client:connect(ip,port) --* [|]<-2:switch0--[|] |
[|] | |
if not ok then <----------<-----------. [|]--------->--3:switch()---->---[|]
\ | | [|]
ngx.say("failed:", err) . | [|]<--4:switch()-[|]
*----|---<----------[|] |
return | [|]--5:switch()->[|]
| | [|]
end | | [|]
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。