TCP

[toc]

TCP

TCP总流程

image-20201116221214607.png

客户端、服务端:调用socket函数,创建套接字描述符

服务端调用bind函数,绑定端口和ip

  • 端口:为了内核收到数据包知道交给那个应用进程

  • ip:机器可能有多个网卡,需要监听哪个网卡

服务端调用listen函数,将套接字转换为被动套接字。不主动连接别人,需要别人联系我们。

服务端阻塞在accept函数,等待客户端连接请求到来

客户端调用connect函数,向服务器发动主动连接,在connect函数内部主动发起三次握手过程。

客户端和服务端进行read和write数据。tcp是全双工通信的,read和write是双向的。

通信完毕后,其中一方调用close函数向对方发送一个分包,表示不再发数据了。对方读到read函数返回0,感知到对方关闭连接请求,随后自己也调用close函数,通知对方我也关闭了。

TCP头部

image-20201116221846648.png

源端口、目的端口:决定发送到哪个应用进程

序列号:解决乱序问题

确认号:每发送一个数据包需要对方的确认包,没有收到就会重新发送

标志位:ACK为1,确认好就行。RST表示连接已经重置。SYN表示发起一个连接请求。FIN表示关闭一个连接。

内核角度看TCP如何维护一个socket

image-20201116222208687.png

linux中一切皆文件,socket也以文件形式存在。

调用socket函数立刻返回一个文件描述符。要在pcb里已打开的文件描述符列表中占有一项,该项的内容就是一个指针指向内核中的文件表,文件表最终找到inode

对网络文件来说,inode并不是真正指向磁盘文件,而指向struct socket类型结构体。

struct socket结构中有一个接受队列和一个发送队列。read函数数据写到发送队列中,从网络接到的TCP包放到接收队列中。从而实现全双工通信。

TCP三次握手流程:

image-20201116222634590.png

TCP协议栈主动向服务端发送SYN包,告诉服务器自己的序列号是 i,客户端进去SYN-SENT状态。

服务端内核协议栈接收到SYN包,给客户端回应ACK,同时发送自己的SYN序列号,服务端进去SYN-RCVD状态。

客户端收到服务端的ACK后,从connect函数返回。

服务端收到客户端的应答包,从accept函数返回。

TCP过程中包的内容

TCP头部的标志位:(1位bit)布尔域控制连接状态,三个重要标志位:SYN 创建连接,FIN 结束连接,ACK 确认接收数据

序列号和确认号:32位,序列号用来跟踪该端发送的数据量,确认号用来通知发送端数据成功接收。初始序列号是随机的,通常使用相对序列号/确认号,跟踪更小的相对序列号/确认号会相对容易一些。

TCP建立到发送信息间包的内容。

  1. 客户端主动发起连接请求。TCP会话的每一端的序列号(相对)和序列号都从0开始。此时通话未开始,不显示确认号。

  2. 服务端响应客户端的请求。响应中附带序列号0(服务端的第一个包),相对确认号1(表示收到了客户端的SYN包)。Note:尽管客户端没有发送任何有效数据,确认号还是被加1,这是因为接收的包中包含SYN或FIN标志位。含有SYN或FIN标志位的包并不携带有效数据。

  3. 客户端回应。用确认号1响应服务端的序列号0,同时发送自己的序列号。由于服务端发送的包确认收到客户端的SYN包,客户端的序列号由0变为1。完成TCP三次握手

  4. 客户端发第一个携带有效数据的包,HTTP请求。因为没有发送数据,序列号依然为1。因为没有接收数据,确认号也保持1不变。Note:准确来说是,客户端发送的HTTP请求。包中有效数据的长度为725字节。

  5. 服务器收到HTTP请求,回应ACK包。服务端发送ACK包来确认客户端在包4中发来的数据。服务端的序列号保持为1不变,确认号的值增加了725(包4的有效长度)变为726。告知客户端,当前总共收到了726字节的数据。

  6. 服务端返回HTTP响应的开始。由于服务端在该包之前返回的包中都不带有有效数据,序列号依然为1。该包带有1448字节的有效数据。

  7. 客户端回应。由于上个数据包的发送,客户端的序列号增长至726。从服务端接收了1448字节的数据,客户端的确认号由1增长至1449。

  8. 重复6、7。客户端的序列号一直是726(客户端除了最初的725字节数据没有再向服务端发送数据)。服务端的序列号持续增长(不断的发送HTTP响应)。Note:序列号为当前端成功发送的数据位数,确认号为当前端成功接收的数据位数,SYN标志位和FIN标志位也要占1位

TCP关闭连接。

  1. 客户端认为通信已完成,决定结束连接,发FIN包。客户端发送设置了FIN标志位的包38,其确认号和之前的包37一样。

  2. 服务端回应客户端期望关闭连接的请求。服务端确认号加1,同时设置当前包的FIN标志位。(分两次发?)

  3. 客户端回应。序列号727,确认号加1的方式确认服务端的FIN包。

包序号以几开始?

image-20201116223000872.png

为了防止网络中被延时的数据包以后又被传送,导致连接一方对他错误解释。所以客户端和服务器之间的包序号不能以 1 号开始变时。

每一个连接都要有不同的起始同步序号

image-20201117144122425.png

listen函数内部建立了未完成三次握手队列,称之为SYN队列。已完成三次握手的队列,ACCRPT队列。

accept函数实现就是阻塞等待,然后从ACCEPT队列取出一个连接,创建出新的socket,将原核目标IP和端口填入socket,然后返回。

connect函数实现就是创建connect队列,然后将当前要发送的信息放入connect队列,服务端收到syn信息时放入SYN队列,同时回复一个SYN和ACK。客户端收到ACK,从connect队列取出,然后connect返回。

服务端收到客户端的ACK的ACK后,从SYN队列删除,插入到ACCEPT队列,阻塞在pthread_cond_wait的accept函数,由于ACCEPT队列有数据而被唤醒,取走队头节点,然后accept函数返回。

补充

  1. 序列号是“随机”的;
  2. ACK +1的问题;

序列号应该不是随机的,而是由双方协商的,协商之前最开始的值由TCP运输层生成。 它可能是上一个该端口的序列号+1 ,原因是为了避免上次已断开的连接,还存在网络中延迟到达的TCP报文段的序号与当前连接中等待报文段的序号相同,以至于认为是这一次连接的包,发生错误。

关于ACK+1,在文中“包2”中如下所述:

“需要注意的是,尽管客户端没有发送任何有效数据,确认号还是被加1,这是因为接收的包中包含SYN或FIN标志位(并不会对有效数据的计数产生影响,因为含有SYN或FIN标志位的包并不携带有效数据)”这实际上是误解,因此误导了很多人。

在停-等协议中,ACK确认的是当前包的序列号Seq=1 ,接收端会传回 ACK=1 。标识接收到了Seq=1的包,这是我们认为理所当然的。

但是在TCP协议中不是这样, 当发生Seq=1的包,接受端会传回ACK=2, 也就是将接受到的Seq+1 , 代表的是期望接受到的下一个包是 Seq=2的包。 (换句话说,和头部的标志位并没有任何关系)

为什么要这样呢?.. 这么做比较违反我们的直觉。

因为TCP是以流水线发出的,比如发送端顺序的发出 Seq=1、Seq=2、Seq=3。

那么如果ACK确认的序号和收到的包的序号一致的话,那么需要发回 ACK=1、ACK=2、ACK=3 共三个包。

但是TCP协议对此进行了优化,只需要发送一个ACK包就能代表说自己已经收到了前面三个包,
那就是发送ACK=4 (期望收到Seq为4的包)。这样节省了ACK确认的数量。

另外TCP是的序号是根据数据流编码的, 假设最开始Seq=0 Len=3, 那么 ACK=4的时候:

第一个意思是想表明期待收到下一个Seq为4的包。

第二个意思实际上是说,想收到的包开始的那个比特位于数据流中的第四个比特。(下次从数据流中的第四个Byte开始发送)

(实际上上面两个意思是一样的 = = ,同时也再次说明这个ACK+1和头部的标志位无关)

为什么要三次握手

image-20201117144828569.png

最重要:为了满足在信道不可靠的情况下,建立双向连接。

其他:为了减少恶意伪造数据包的用户对服务器攻击。减少异常的情况下,服务端资源占用情况。

TCP四次挥手流程

image-20201117145254085.png

主动关闭的一方调用close函数,发送FIN包表示要关闭连接,之后进入FIN_WAIT_1状态。

被动方接受FIN包,内核协议栈插入一个EOF标志,到接受缓冲区的数据之后,同时给主动方发送ACK包,被动方进入CLOSE_WAIT状态。

主动方收到ACK后,进入FIN_WAIT_2状态。

被动方的应用进程读到一个结束标志后,调用close函数发送FIN包给主动方。被动方进入LAST_ACK状态。

主动方收到FIN包发起ACK,主动方进入TIME_WAIT状态。

被动方收到ACK后进入CLOSED状态。

主动方在2倍MSL后也进去CLOSED状态。linux中MSL时间为30s,所以TIME_WAIT时间为60s。

为什么需要TIME_WAIT状态?为什么要是2倍MSL?

如果没有TIME_WAIT,主动方的端口就可以用于新连接。这时如果被动方没有收到ACK,会重发FIN包,导致新连接被 关闭。

2倍MSL:允许最后的ACK包丢失一次。如果丢失,重发的FIN包会在第二个MSL内到达。

image-20201117150158293.png

无论主动方还是被动方都需要一个FIN包,ACK包,所以关闭需要四次挥手。

为什么建立连接是三次,端口连接是四次呢?

TCP不允许处于半打开状态,单向传输数据。所以服务端把回应给客户端的ACK和SYN包一起发送给客户端。

但是TCP允许在半关闭的状态下单向发送数据。

TCP总结

image-20201117150637103.png

TCP真的可靠吗?

tcp服务端宕机了怎么办?

  • TCP是一个可靠的协议,怎么保证可靠的?

  • TCP并不能保证他发送的数据能够准确的发送到最大的应用进程。

TCP如何保证可靠

差错:发送端发送的数据和接收端接收的数据不一致。TCP会在首部增加一个校验和,可以校验TCP首部和数据,接收到接收数据后发现校验和有错,就丢弃这个数据包,并且不给对端发送应答包。

丢包:发送数据包后会启动一个定时器,如果得不到对方的应答就会超时重传。

失序:TCP是承载于IP包之上的,IP发送时可能走不同路径,导致后发数据包先到达。TCP首部就做了序列号,接收端接收数据可以对它重新排序。

重复发送:根据TCP首部的序列号。

TCP发送过程情况

  1. 发送一次,应答一次。发送一次,应答一次。不能很好利用网络和机器的效率。

  2. 发送端尽可能发送,接收端接收后返回一个应答。

    1. 接收端告诉发送端,接收能力有多强。以免丢包,增加网络开销。

    2. 数据包在网络上多个路由器之间转发,网络转发数据能力。拥塞控制。先发送一个包,可以应答;发2个,应答,发4个。。增加到一定程度线性增长或者网络丢包了,降低网络发送速率。

image-20201117151951256.png

TCP的可靠机制仅仅是端到端的可靠。

应用进程A发送数据到应用进程B。发送端会从协议栈从上往下层层传输,然后经过若干路由器到目标机器,再向上传输到达接收端。

路由器之间是网络设备,并没有TCP,IP是一个不可靠协议。TCP的接收端应用进程B的内核协议栈TCP,能够保证收到的数据是按序的,未受损的。

一个经典问题:

B端的TCP向A发送了确认包,但是此时B宕机了,没有把已确认的数据包读走。那A收到了应答包,就认为B已经收到了。

解决方案:增加应用层的应答包,

image-20201117152821831.png

设计双通道,向外发送数据包的模块,能收到对端应用进程的ACK。

设计思路:发送一个数据包,然后启动一个定时器,在定时器到达前,不发送数据,直到对端应用层的ACK包收到后,才继续发下面的数据。如果超时没有收到对端的应答,就把进程结束掉(或数据重传)。

TCP故障类型:

1、不能收到FIN的故障

  • 网络掉线了或主机崩溃了,若此时刚好阻塞在read函数上,没法恢复。只能设置读超时,通过setsockopt。

  • 若先write再read,这时内核协议栈会不断重传,直到一定次数后,还收不到应答,就标记这个连接是异常的,走到超时错误。

  • 也有可能是阻塞在select或者epoll上,建议做心跳包,定时检测对端应用进程是否存活。

  • 对端操作系统崩溃了,在TCP还没有放弃连接前,重激起来了。这时能收到对端的RST。

2、能收到FIN的故障

  • 对端应用进程崩溃、close、exit,都能收到FIN包。

  • 如果阻塞在read上,read直接返回0。

  • 如果此时在write,第一次write对端的内核协议栈会给一个RST,表示连接已经重置。如果收到RST,还继续发数据包,就会收到管道破裂。

SYN泛洪攻击

SYN cookie

待补充

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容