详解TCP网络协议栈的工作原理

详解,tcp,网络协议,工作,原理 · 浏览次数 : 409

小编点评

# 网络连接与传输 ### 1. 三次握手原理 * 客户端发起SYN,服务端收到SYN后发送SYN和ACK,客户端回复ACK。 * 完成连接的建立。 ### 2. TCP状态转换 * LISTEN状态到SYN_RCVD状态和SYN_SEND状态,如何进入ESTABLISHED状态。 * 四次挥手FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSING直接的转换,CLOSE_WAIT和LAST_ACK的处理等。 ### 3. TCP分包 * 在数据末尾添加分隔符,这会导致解数据可能需要有合包操作;因为分割数据包后,需要记录后一个数据包,用于与该包后面部分数据进行合并。 ### 4. TCP粘包 * 应用层协议头前面添加包长度。 * 分两次接收数据;第一次先接收包的长度,然后根据包的长度一次性读取或循环读取数据。 ### 5. TCP四次挥手断开连接 * 客户端调用close(),同时发送FIN包;接收端接收到FIN包返回ACK包,接收端发送FIN包;发起方接收到FIN包返回ACK包;完成断开。 ### 6. TCP状态转换图 ``` | LISTEN | SYN_RCVD | SYN_SEND | ESTABLISHED | | --- | --- | --- | --- | | CLOSE | FIN_WAIT_1 | FIN_WAIT_2 | FIN_WAIT_1 | | CLOSE | TIME_WAIT | TIME_WAIT | CLOSE | ```

正文

本文分享自华为云社区《网络通信的神奇之旅:解密Linux TCP网络协议栈的工作原理》,作者: Lion Long 。

一、TCP网络开发API

TCP,全称传输控制协议(Transmission Control Protocol),是一种面向连接的、可靠的、基于字节流的传输层通信协议。

1.1、TCP服务器调用的API

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

// 1

int socket(int domain, int type, int protocol);

// 2

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 3

int listen(int sockfd, int backlog);

// 4

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

// 5

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 6

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 7

int close(int fd);

// 8

int shutdown(int sockfd, int how);

1.2、TCP客户端调用的API

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

// 1

int socket(int domain, int type, int protocol);

// 2

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 3

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

// 4

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 5

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

// 6

int close(int fd);

// 7

int shutdown(int sockfd, int how);

1.3、API函数的作用

(1)int socket(int domain, int type, int protocol)

在文件系统中分配一个fd,并创建TCB数据结构。

(2)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

为TCP的socket绑定本地IP地址和端口。

(3)int listen(int sockfd, int backlog)

将TCP置于LISTEN状态。

(4)int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

从全连接队列中取出一个节点,并分配一个fd。

(5)ssize_t recv(int sockfd, void *buf, size_t len, int flags)

在对应fd中,从读缓冲区中拷贝出数据。

(6)ssize_t send(int sockfd, const void *buf, size_t len, int flags)

把fd对应的TCB数据拷贝到写缓冲区中。

(7)int close(int fd)

准备一个FIN包,放到写缓冲区,是否fd。

(8)int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

准备一个SYN包,交给协议栈发送出去,等待三次握手完成后才返回。

二、TCP的三个阶段

2.1 TCP建立连接

TCP连接的建立主要依靠socket()、bind()、listen()、connect()、accept()这几个函数。

2.1.1、TCP的三次握手

示意图:

cke_122.png

三次握手在kernel协议栈中进行,那么三次握手是在哪几个函数中发送的呢?

第一次,由connect()函数触发 发起握手,也就是发送syn包到服务端;

第二次,在listen()之后accept()之前,服务器接收到syn包后发送syn&&ack包到客户端;

第三次,客户端发送ack包到服务端完成连接的建立。

TCP报头:

0 |1 |2 |3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-------------------------------+-------------------------------+

| Source Port | Destination Port |

+---------------------------------------------------------------+

| Sequence Number |

+---------------------------------------------------------------+

| Acknowledgment Number |

+-------+-----------+-+-+-+-+-+-+-------------------------------+

| Header| Reserve |U|A|P|R|S|F| Window |

| Length| |R|C|S|S|Y|I| |

| | |G|K|H|T|N|N| |

+-------------------------------+-------------------------------+

| Checksum | Urgent Pointer |

+---------------------------------------------------------------+

| Option |

+---------------------------------------------------------------+

| Data |

| ... |

+---------------------------------------------------------------+
  • SYN:即synchronous,同步。
  • ACK:即acknowledgement,确认。
  • PSH:即push,推送。
  • FIN :即finish,结束。
  • RST:即reset,重置。
  • URG:即urgent,紧急。
  • Sequence Number:是数据包本身第一个字节的序列号。
  • Acknowledge Number:是期望对方继续发送的那个确认数据包的序列号其值一般为接收到的Sequence Number加1。

从报文中可以看出,SYN包最重要的是将SYN位设为1,设置Sequence Number;ACK包最重要的是将ACK位设为1,设置Acknowledgment Number。

半连接队列和全连接队列:

在三次握手中,Linux kener 协议栈会维护两个队列:半连接队列和全连接队列。

半连接队列(也叫SYN队列): 半连接队列在第一握手中,当客户端发送SYN包到服务端时,服务端的半连接队列会加入一个节点,表示此连接处于半连接状态。

全连接队列(也叫ACCEPT队列): 全连接队列在第三握手中,当客户端发送ACK包到服务端时,服务端会检查半连接队列中是否存在此连接节点(通过五元组进行查找),如果存在就将此连接节点加入全连接队列中;否则将抛弃此连接。

accpt()函数在三次握手完成后,从全连接队列中取出连接节点,为节点分配socket fd,返回到用户态。

那么,accept()函数如何知道全连接队列中有节点呢?

当三次握手完成后,全连接队列创建节点的同时会释放一个有连接接入的信号(single或信号量),这个信号决定了accept()函数是否可以从全连接队列中取节点;也决定epoll等IO多路复用器能不能检查这个连接fd是否可读。

在阻塞模式下,accept()函数一直等待信号,直到全连接队列中有节点才返回。

在非阻塞模式下,全连接队列为空accept()函数就返回-1,否则返回socket fd。

在listen()函数有,有一个backlog参数,这个参数表示的是全连接队列的大小还是半连接队列的大小呢?

随着TCP协议的不断迭代,backlog参数在不同的版本中代表的含义也不相同;它可以是半连接队列大小,也可以是全连接队列大小,也可以是半连接队列+全连接队列的大小总和。不过,效果不会有太大差异。目前版本中主要表示全连接队列的大小。

DDOS攻击:

根据三次握手原理,产生一种对服务器的攻击方式:DDOS攻击。所谓DDOS攻击,就是客户端伪造一些不存在的IP,一直发送SYN包,使服务器的半连接队列不断增大,当半连接队列的大小达到极限时,造成网络阻塞就会导致服务器无法再接受连接,从而使服务器奔溃。

2.1.2、TCP状态转换

TCP状态转换图:

cke_123.png

(1)从状态转换图看出,LISTEN状态可以通过发送SYN和数据转换到SYN_SEND状态;也就是LISTEN状态可以发送数据。

(2)SYN_SEND状态可以收到SYN,并发送SYN和ACK转换到SYN_RECV状态;也就是两个设备可以互发SYN包,建立连接。

2.2 TCP传输数据

TCP传输数据主要依靠send()和recv()两个函数。

使用send()函数发送数据时,返回正数不一定代表发送成功。因为send()函数仅仅只是将数据拷贝到协议栈的写缓冲区,由协议栈发送;发送过程中会经过N个网关,可能存在丢包或链路断开导致未能发送到目的地。如果要知道数据是否发送成功,需要加上确认机制(ACK)。

2.2.1、传输控制块TCB

为了保证数据能正确分发,TCP使用一种TCB(传输控制块)的数据结构,把发送给不同设备的数据封装起来。这个TCB会存在整个TCP周期,知道断开连接。

一个TCB数据块包含数据发送双方对应的socket信息以及拥有存放数据的缓冲区。建立连接连接发送数据之前,通信双方必须做一个准备工作:分配内存建立TCB数据块。当双方准备好自己的socket和TCB数据结构后,就可以进入“三次握手”建立连接。

2.2.2、TCP分包

TCP分包就是要传输的数据很大,超出发送缓存区剩余空间,将会进行分包;待发送的数据大于最大报文长度,TCP在传输前将进行分包。

分包在应用程序的处理一般是发送循环send(),接收方循环recv()。

2.2.3、TCP粘包及解决方案

TCP粘包就是发送方发送的若干数据包到接收方接收时粘成一个包,从接收缓冲区看就是后数据包的头紧接着前数据包的尾。

常见解决方案:

(1)(推荐)应用层协议头前面添加包长度。分两次接收数据;第一次先接收包的长度,然后根据包的长度一次性读取或循环读取数据。

例如:

// ...

ssize count=0;

ssize size=0;

while(count<tcpHdr->length)

{

size=recv(fd,buffer,buffersize,0);

count+=size;

}

// ...

(2)为每个包添加分隔符。在数据末尾添加分隔符,这会导致解数据可能需要有合包操作;因为分割数据包后,需要记录后一个数据包,用于与该包后面部分数据进行合并。

cke_124.png

2.3 TCP四次挥手

断开连接是比建立连接和传输数据还复杂的一个过程,断开连接主要分为主动关闭和被动关闭两种。

四次挥手示意图:

cke_125.png

需要注意的是,调用close()不是立即完成断开,而是关闭了数据传输,进入了四次挥手阶段,TCB数据结构还没有释放。四次挥手结束才真正把TCB释放。

根据四次挥手流程,可以思考一些问题:

(1)传输数据过程中,网线断了之后立刻连接,TCP如何知道?

网线掉线网卡会停止供电,再次连接后网卡恢复供电,网卡服务重启,网络连接重连。应用程序设计通过心跳包检测。

(2)服务器如何知道客户端是否宕机?

一样需要通过心跳包机制来检测。

(3)服务器如何甄别网络阻塞和宕机?

服务器发送心跳包时,不仅仅发一次,而是要发送多次的;如果是网络阻塞,那么在一定时间内一定有回复信息;如果是宕机,无论多长时间都没有客户端的回复。

(4)如果出现大量的CLOSING状态,如何处理?

出现大量CLOSING状态,基本上业务上要处理的逻辑过多,导致一直在CLOSING状态;可以使用异步,将网络层和业务层分离,单独处理。

(5)四次挥手中,为什么存在TIME_WAIT状态?

防止没有LAST_ACK或LAST_ACK丢失,导致一直重发已经不存在的socket。

总结

需要掌握TCP三次握手和四次挥手的过程,熟悉TCP状态转换。清楚什么是SYN包和ACK包。

(1)三次握手是 由客户端发起SYN,服务端收到SYN后发送SYN和ACK,客户端回复ACK;完成连接的建立。

(2)断开连接主要有主动断开和被动断开。

(3)四次挥手是 由发起方调用close(),同时发送FIN包;接收端接收到FIN包返回ACK包,接收端发送FIN包;发起方接收到FIN包返回ACK包;完成断开。

(4)理解TCP的状态转换图。LISTEN状态到SYN_RCVD状态和SYN_SEND状态,如何进入ESTABLISHED状态;四次挥手FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSING直接的转换,CLOSE_WAIT和LAST_ACK的处理等。

(5)理解API的底层原理,以及全连接队列和半连接队列。

(6)TCP的分包场景以及TCP粘包的处理方式。

TCP通信完整过程:

cke_126.png

 

点击关注,第一时间了解华为云新鲜技术~

与详解TCP网络协议栈的工作原理相似的内容:

详解TCP网络协议栈的工作原理

深入探索Linux TCP网络协议栈的内部机制,揭开其背后的神秘面纱。

[转帖]【翻译】理解 TCP/IP 网络栈

https://cizixs.com/2017/07/27/understand-tcp-ip-network-stack/ TL;DR [TOC] 译者注:很久没有翻译文章了,最近在网络看到这篇介绍网络栈的文章非常详细,正好最近在看这方面的内容,索性翻译过来。因为很多文章比较长,而且很多内容比较专

网络协议之:redis protocol 详解

简介 redis是一个非常优秀的软件,它可以用作内存数据库或者缓存。因为他的优秀性能,redis被应用在很多场合中。 redis是一个客户端和服务器端的模式,客户端和服务器端是通过TCP协议进行连接的,客户端将请求数据发送到服务器端,服务器端将请求返回给客户端。这样一个请求流程就完成了。 当然在最开

网络协议的重要性与应用:理解进程间通信和网络分层结构(上)

学习网络协议的关键是了解其分层结构。在计算机网络中,我们使用的是OSI标准模型和TCP/IP网络模型。这些模型将网络通信划分为多个层级,每个层级都有不同的功能和作用。在本章节中,我们主要讲解了TCP/IP网络模型的前三层:应用层、传输层和网络层。后面的数据链路层和物理层将在下一篇文章中进行详细讲解

[转帖]TCP三次握手详解,滑动窗口,拥塞窗口,网络包路由过程,全连接队列,半连接队列

众所周知,网络分层有传统的OSI七层模型和后来的基于TCP/IP的四层模型: 那么在一次网络的传输过程中具体的流程是怎么样的,我们先从一个数据包的传输说起(以TCP为例): TCP协议根据上层应用提供的信息生成TCP报文 TCP报文在交由下面的IP层(网络层)进行处理,委托IP模块将TCP报文封装成

[转帖]Linux中iptraf命令详解

https://www.sohu.com/a/217620611_610671 iptraf是一个基于ncurses开发的IP局域网监控工具,它可以实时地监视网卡流量,可以生成各种网络统计数据,包括TCP信息、UDP统计、ICMP和OSPF信息、以太网负载信息、节点统计、IP校验和错误和其它一些信息

[转帖]ping、arp、tracert、route这四大命令的详细用法,用途很广

https://zhuanlan.zhihu.com/p/460719455 在网络中ping是一个十分强大的TCP/IP工具。它的作用主要为: 1、用来检测网络的连通情况和分析网络速度 2、根据域名得到服务器IP 3、根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。 我

iperf 工具使用总结

转载请注明出处: iperf是一个用于测量网络带宽的工具,可以通过客户端和服务器之间的数据传输来评估网络性能。下面详细介绍iperf的使用方法、常用命令和参数以及注意事项,并提供一些示例说明。在iperf中,流量测试通常使用TCP或UDP协议。 1. 使用方法 首先,需要在一台计算机上运行iperf

[转帖]计算机网络【TCP的序号 确认号详解 TCP三次握手 和 四次挥手】

文章目录 初始TCP三次握手--建立连接再聊TCP的序号和确认号TCP建立连接--三次握手为什么需要三次握手,二次握手为什么不行?假如第三次握手失败,是如何处理的?TCP释放连接--四次挥手为什么断开连接需要4次挥手TCP释放连接--状态解读 初始TCP三次握手–建立连接 在发送方和接收方方收发TC

[转帖]TCP之Nagle、Cork、Delay ACK(延迟确认)

https://www.jianshu.com/p/167ba81206fb 参考资料 TCP协议中的Nagle算法 TCP中的Nagle算法 Linux下TCP延迟确认(Delayed Ack)机制导致的时延问题分析 TCP-IP详解:Delay ACK 1. Nagle 算法 1.1. 原理 N