老司机 - TCP

新人拜访

“您好,我是 TCP 服务的实习生,刚培训完,请问您就是 TCP 老司机吗?”

正坐在办公室悠闲喝着咖啡的我,差点一口喷出来,“哦哦,行,你终于来啦,看来几天前的资源申请通过了啊,老司机?”

“浏览器老大这么叫你的,说你厉害,靠谱的很。”

“好吧,既然来了,赶紧准备准备!这你的工作台。” 我放下手中的咖啡,走到了一个靠窗的工作台,指了指。

“好嘞。” 说着,刚来的小伙子走了过来。

工作台

“好多工具啊,这个通道是什么,这儿还有个钟?都怎么用呀?” 小伙子满脸好奇的问道。

“这样吧,从左到右,我一一给你说说。”

“首先,最左边呢,是个通道,这里会蹦出应用层需要我们发送的数据。最常见的就是 HTTP 那小子的报文了。”

“了解,我们的工作就是发送应用层的数据。原来不需要我们自己去拿啊!”

“旁边呢就是扫描机,你需要根据实际情况把数据进行扫描,扫描机会把数据分块,按序号,放在旁边这个盒子里。”

“嗯嗯,这个我清楚,数据块大小需要根据实时的网络情况确定。”

“不错。这小伙子脑子还挺灵光。看来培训的不错” 我心中暗暗赞到。

“再往右,你看到这又是一个通道,这个通道就是我们和服务器进行沟通的通道了。通道旁是两个计数器,分别用来记录我们的序号和服务器序号,这个会自动加,你不用操作。就叫 1 号计数器和 2 号计数器吧,等下会用到。”

“好的,1 号计数器其实就是报文中的序号?”

“嗯嗯,不错,用的时候看一眼就行,在计数器旁边有几个按钮,我给你说说。这里最好记一下。”

“嗯嗯” 说着小伙子拿出了笔记本开始记录。

任务降临

我指了指标着 SYN 的按钮,说道:“这个是请求连接按钮,也就是通知服务器,我们想发数据给他。”

说着,最左侧的通道突然一闪,蹦出了一个 HTTP 服务打包好的包裹,刚来的小兄弟显的有点慌乱,咽了一口气。

“刚好,那我就那这给给你做一下演示吧。” 说着,我站到了工作台正中。

“嗯。” 小兄弟紧张的说不出话来。

“首先呢,按一下 SYN 按钮,请求发送数据。” 1 号计数器跳了一下,由原本的 0 跳到了 1。“按下这个按钮工作台会发送一个请求连接报文,相信报文的内容你应该清楚吧?” 我问道。

SYN1,序号为 0。” 看了一眼 1 号计数器,小伙子自信的答道。

“嗯,不错!” 不一会计数器旁的通道蹦出了一段报文,2 号计数器直接由 0 跳到了 11

“让我们来看看,都有什么,你看这里。” 我指了指报文中的 ACK,小伙子也靠了过来。

ACK 等于 1,说明服务器接受了我们的连接请求,对吧?” 小伙子说道。

“是的,那我们建立连接?” 我露出了狡猾的笑容。

“还需要核对 确认序号 吧?” 小伙子有点困惑。

“为什么?” 我反问道。

“确保连接的准确性。老师说过,网络是一个复杂的环境,会出现报文滞留的情况,因此 TCP 连接两端规定,在得到请求连接时,需要将 确认序号 置为请求序号加 1。那么现在我们应该还要核对 确认序号1 号计数器的值!” 小伙子转眼看向 1 号计数器,“咦,怎么一样?应该是差 1 的啊?”

“你忘了,在你按下 SYN 按钮时,1 号计数器已经跳了一下吗?”

“哦 ~” 小伙子恍然大悟,“那是应该相等,这个也太好用了吧!得记一下!”

“唰唰唰” 一旁发出铅笔摩擦纸面的声响,“有我刚来的样子” 我不经感叹道。

“接着,你看 SYN 字段为 1,说明服务器请求连接,TCP 是一个稳定的连接,双向数据的通道,需要双方都确认连接状态,这点我不需要特殊说明吧?”

“嗯嗯,在学校老师说过。”

“那好,为了确保连接的正确性,我们需要和服务器做相同的操作,把序号加 1 后,放在确认序号内,服务器返回的序号是 10 我们返回 11 就行。但这些不需要你进行实际的报文生成,在你确定可以建立连接的情况下,按一下标着 ACK 按钮就行。” 说着我按下了 ACK 按钮。“现在三次握手结束,连接通道成功生成。我们开始传输数据,剩下还有几个按钮用到的时候在说。”

“那个,我想问一下,我知道 TCP 是个双向数据通道,但如果我们不确认建立连接,直接发送数据应该也没问题吧?我们和服务器都已经发送了一次请求,并且都收到了,为什么还要确认一下呢?” 小伙子若有所思道。

“既然是双向数据通道,那么通道的两端都应该清楚自己和对方的发送和接收的能力,对吧?”

“当然!不然通道是不稳定的。”

“那你想想,在前两次通信过程中,双方都知道了哪些情况?”

“第一次我们发报文,第二次服务器返回报文我们接收到,那我们就知道了我们有能力发送和接收,服务器也有能力接收和发送。”

“那服务器呢?”

“服务器的话,应该知道了他自己有能力接收,知道我们有能力发送,他自己却不清楚他能不能发送,也不知道我们是否能接收!因为我们还没回复他。”

“因此呢,三次通信(握手)让双方都知道了双方都有发送与接收的能力,这对于一个双向数据通道来说至关重要。” 我解释道。

“嗯嗯,我记一下。” 又是一阵 “唰唰唰” 的声音。

发送报文

“已经建立了通道,那接下来就简单了。” 我拿起 HTTP 扔过来的数据,说道:“我们看看我们一次能发送的数据包大小,在这儿。” 我指了指工作台最右侧的屏幕,屏幕上显示着一连串的信息,“这些就是系统参数,这个就是我们网络所能承受的最大发送量。”

“嗯,一次可以发送 1100 字节。” 小伙子边看边说道。

“嗯,不错。那我们就将数据按 1000 来切割吧,剩一定空间个报文头部使用。” 说着我启动一遍的扫描机,设定为 1000,丢进 HTTP 的数据,一旁的盒子出现排好序的数据块。

“好神奇啊,学校里都我们自己切的。” 小伙子双眼中冒着金光。

“接下来发生报文就好了。”

“好的,我来拼接数据,源端口:5800,目标端口:80,序号:1,数据偏移:20,标志位:无特殊情况,窗口大小:3,校验和:...” 小伙子突然叽里咕噜一堆,吓了我一跳。

“不需要这么麻烦,源端口,目标端口,在工作台接收到应用层数据后,就能生成,至于序号和偏移量,你也不用关心啦,我来演示一遍,看好。”

我瞧了一眼服务器先前返回的报文,得知窗口为 3,拿起盒子里的数据就往按钮旁的通道里丢,一连丢了 3 个。1 号计数器也连续跳了 3 下,由 1 跳到了 1001,在由 1001 跳到了 2001,最终定格在 3001

“好了,接下来就等服务器响应了。”

“这就好了?” 小伙子明显有点不敢相信。

“对啊,好了!等服务器确认就行。” 我有点得意。“工作台会帮我们把数据打包生成报文,这个工作台可是我的专利!”

“哇塞,老司机就是厉害!比学校里高级多了。” 小伙子双眼中再次亮起金光。

单向关闭

没过多久,2 号计数器连跳 3 次,哒哒哒,通道里蹦出 3 个服务器返回的报文,内容都为:请求完成,需要下一份数据。

我拿起盒子里的下 3 块,往通道里扔。1 号计数器哒哒哒,又连着跳了 3 次。如此循环往复了十几次,数据终于发完。

“今天的网不错诶,没有重发的情况出现。现在我们发完了,只需要等待服务器发送数据就行了。”

大概过了 10ms,通道内陆陆续续开始蹦出报文,我刚想上手,一旁小伙子一把把报文揽走,“这个我熟,我来吧!”

获取报文数据,按照序号排好,一顿操作还挺溜。

哒哒哒,2 号计数器不断的跳动,定格在 50001 上。

“好现在数据也接收完毕了,需要进行单向关闭。如何进行关闭?” 我想考考小伙子的能力。

“发送报文,将 FIN 置为 1,序号为 43001。” 小伙子瞧了一眼 1 号计数器说道:“然后等服务器返回确认就好了,哦,对为了应付网络滞留情况出现,需要核对 确认序号。”

“不错,但在这...” 我话还没说完,小伙子兴奋的说道:“是不是按那个 FIN 按钮就好了?”

“嗯,对!我们只需要核对 确认序号 就好了。你来操作吧 ~” 看着一脸兴奋的小伙子,我停止了手中的操作。

“好的。” 说着,小伙子按下了 FIN 按钮。1 号计数器跳了一下,定格在 43002。不一会通道里蹦出了报文,确认序号与 1 好计数器一致,都为 43002

“现在单向关闭成功,等待服务器请求关闭。” 小伙子一副训练有素的样子。

“嗯,不错,接下来就等待服务器最终确定关闭了。” 我拿起一旁的咖啡,慢悠悠的说道:“那你知道为什么这是单向关闭?”

“因为只有我们这边确认把数据都已经发送完毕啦 ~ 没准服务器还有数据给我们呢 ~ ”

“嗯,不错。” 我拿起咖啡喝了一口,不经感慨道:“后继有人了啊!”

“老大,服务器发出关闭请求了,你看。” 小伙子拿起一个报文,对我说道。

“我想你应该知道怎么做了吧?”

“嗯嗯,按 ACK 按钮就好了,这个和开启连接确认是同一个操作,按您的智慧,应该是这样。” 说着按下了 ACK 按钮。

“嗯,这个彩虹屁真香,哦不,你真聪明!”

“哈哈” 我们同时发出笑声,“我和你说,那个 HTTP 服务部可不咋的,经常砸我,这不数据都好了,砸 TM 的。”

duang ~” 通道一头发出一阵巨响,“TCP 你们没完了是不?”