网络编程-从TCP连接的建立说起 超、凢脫俗 2021-12-14 23:15 209阅读 0赞 来源:公众号【编程珠玑】 作者:守望先生 ## 前言 ## 网络编程几乎是每一门编程语言都会涉及的内容,虽然各种语言调用的方式可能不一样,但它们背后的原理支持都是一样的。因此本文将从TCP的连接的建立说起。在此之前,假设你已经对计算机网络有了最基本的认识。 ## 网络编程做什么 ## 当下网络应用数不胜数,如微信,可以让你通过网络与远在异国他乡的朋友交流沟通;如在线视频,让你通过网络就可以观看你喜欢的视频,而这一切的背后,都有网络编程技术的支持。通俗来讲,可以认为网络编程是**两台或者多台主机(应用)之间进行数据交换或传输**。 ## TCP:传输控制协议 ## 而数据交换需要按照一定的规则,而这种规则就是**协议**。只有按照约定的规则,双方之间才能正确地进行数据交换。而TCP就是这些协议的一种,**它提供一种面向连接的,可靠的字节流服务**。 ## 为什么要理解TCP ## 事实上不理解TCP背后的基本原理,仍然可以写出代码,但是当你遇到一些奇奇怪怪的而通过API的说明又无法解决的问题时,你就会庆幸自己花了点时间去学习TCP了。 ## TCP连接的建立 ## 关于TCP连接的建立,你可能早已耳熟能详,其流程倒背如流。但我觉得还是有必要再理一理。TCP连接的建立,也就是三次握手的流程如下: ![640?wx\_fmt=png][640_wx_fmt_png] TCP三次握手 我们再试着描述一下三次握手的过程: 至此三次握手完成。需要注意的是,这是正常流程下的三次握手。而前面所说的这些状态可以通过[netstat命令][netstat]或者[ss命令][ss]查看到,当然有些状态的存在时间比较短,可能无法观察到。 好了,那么问题来了: 如果以上所有问题你都能轻而易举的回答出来,那么本文后面的内容你可以跳过了。 ## 为什么要三次握手 ## 这几乎是面试中必问的一个问题。一个TCP连接是全双工的,即数据在两个方向上能同时传输。因此,建立连接的过程也就必须确认双方的收发能力都是正常的。 四次握手是否可以呢?完全可以!**但是没有必要**!在服务端收到SYN之后,它可以先回ACK,再发送SYN,但是这两个信息可以一起发送出去,**因此没有必要**。 两次握手是否可以呢?想象这样一种情况,客户端发起了一个连接请求在网络中滞留了很长时间,以至于在连接建立好且断开连接后,它才到达服务端,此时如果采用两次握手,那么服务端就会认为这个报文是新的连接请求,于是建立连接,等待客户端发送数据,但是实际上客户端根本没有发出建立请求,也不会理睬服务端,因此导致服务端空等而浪费资源。 为什么服务器会认为这个迟到的报文是新的连接请求?因为如果采用两次握手机制,那么服务端无法通过SYN来判断这是一个迟到或者重复的报文,还是正常到达的报文,但是对于三次握手,即便出现这样的情况,也不会在服务端建立起真正的连接。 ## 一个正常的连接三次握手 ## 我们利用tcpdump命令和[nc命令][nc]来观察一个正常的tcp连接建立过程。首先在终端1准备抓包: $ tcpdump port 1234 -i any -v -n 在终端2启动监听1234端口: $ nc -l 1234 在终端3连接: $ nc 127.0.0.1 1234.0.1 1234 在终端1得到以下输出内容: tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes21:00:50.794424 IP (tos 0x0, ttl 64, id 50542, offset 0, flags [DF], proto TCP (6), length 60) 127.0.0.1.45848 > 127.0.0.1.1234: Flags [S], cksum 0xfe30 (incorrect -> 0x3163), seq 1310563628, win 43690, options [mss 65495,sackOK,TS val 3721786049 ecr 0,nop,wscale 7], length 021:00:50.794437 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) 127.0.0.1.1234 > 127.0.0.1.45848: Flags [S.], cksum 0xfe30 (incorrect -> 0xef35), seq 1685196050, ack 1310563629, win 43690, options [mss 65495,sackOK,TS val 3721786049 ecr 3721786049,nop,wscale 7], length 021:00:50.794449 IP (tos 0x0, ttl 64, id 50543, offset 0, flags [DF], proto TCP (6), length 52) 127.0.0.1.45848 > 127.0.0.1.1234: Flags [.], cksum 0xfe28 (incorrect -> 0xc17a), ack 1, win 342, options [nop,nop,TS val 3721786049 ecr 3721786049], length 0listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes 21:00:50.794424 IP (tos 0x0, ttl 64, id 50542, offset 0, flags [DF], proto TCP (6), length 60) 127.0.0.1.45848 > 127.0.0.1.1234: Flags [S], cksum 0xfe30 (incorrect -> 0x3163), seq 1310563628, win 43690, options [mss 65495,sackOK,TS val 3721786049 ecr 0,nop,wscale 7], length 0 21:00:50.794437 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) 127.0.0.1.1234 > 127.0.0.1.45848: Flags [S.], cksum 0xfe30 (incorrect -> 0xef35), seq 1685196050, ack 1310563629, win 43690, options [mss 65495,sackOK,TS val 3721786049 ecr 3721786049,nop,wscale 7], length 0 21:00:50.794449 IP (tos 0x0, ttl 64, id 50543, offset 0, flags [DF], proto TCP (6), length 52) 127.0.0.1.45848 > 127.0.0.1.1234: Flags [.], cksum 0xfe28 (incorrect -> 0xc17a), ack 1, win 342, options [nop,nop,TS val 3721786049 ecr 3721786049], length 0 从上面抓包内容可以看到,总共有三个报文,分别是客户端发送到服务端的SYN,服务端回应给客户端的SYN和ACK,以及客户端回应给服务端的ACK。 ## 连接到一个不存在的端口 ## 如果要连接的服务器端口不存在会出现什么情况呢?我们利用nc命令来抓包观察。 在一个终端窗口使用管理员权限执行下面的命令进行抓包,并打印相关信息: $ tcpdump port 1234 -i any -v -n 在另外一个终端使用nc命令尝试连接到本地的1234端口 $ nc 127.0.0.1 1234 -vnc: connect to 127.0.0.1 port 1234 (tcp) failed: Connection refusednc: connect to 127.0.0.1 port 1234 (tcp) failed: Connection refused TCP抓包内容如下: tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes21:06:15.295407 IP (tos 0x0, ttl 64, id 29112, offset 0, flags [DF], proto TCP (6), length 60) 127.0.0.1.46108 > 127.0.0.1.1234: Flags [S], cksum 0xfe30 (incorrect -> 0x7fef), seq 1175796450, win 43690, options [mss 65495,sackOK,TS val 2076405654 ecr 0,nop,wscale 7], length 021:06:15.295462 IP (tos 0x0, ttl 64, id 58706, offset 0, flags [DF], proto TCP (6), length 40) 127.0.0.1.1234 > 127.0.0.1.46108: Flags [R.], cksum 0x77e7 (correct), seq 0, ack 1175796451, win 0, length 0listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes 21:06:15.295407 IP (tos 0x0, ttl 64, id 29112, offset 0, flags [DF], proto TCP (6), length 60) 127.0.0.1.46108 > 127.0.0.1.1234: Flags [S], cksum 0xfe30 (incorrect -> 0x7fef), seq 1175796450, win 43690, options [mss 65495,sackOK,TS val 2076405654 ecr 0,nop,wscale 7], length 0 21:06:15.295462 IP (tos 0x0, ttl 64, id 58706, offset 0, flags [DF], proto TCP (6), length 40) 127.0.0.1.1234 > 127.0.0.1.46108: Flags [R.], cksum 0x77e7 (correct), seq 0, ack 1175796451, win 0, length 0 从抓包内容中可以看到,首先nc客户端发送一个SYN(Flags为S),seq为1175796450。而后收到一个RST(Flags为R),seq为1175796451。 也就是说,如果连接到一个不存在的端口,服务端所在的系统会**响应一个RST**(复位),直接终止连接。 Flags字段含义如下: ## 连接到一个不存在的服务器 ## 同样是利用nc和tcpdump命令。 $ tcpdump port 1234 -i any -v -n 在另外一个窗口使用nc命令连接到一个不存在的或者无法连接的服务器地址: $ nc 121.11.12.31 1234 -vnc: connect to 121.11.12.31 port 1234 (tcp) failed: Connection timed out12.31 1234 -v nc: connect to 121.11.12.31 port 1234 (tcp) failed: Connection timed out tcpdump输出内容如下: tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes21:13:04.259752 IP (tos 0x0, ttl 64, id 33411, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xcdc0 (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75888078 ecr 0,nop,wscale 7], length 021:13:05.269438 IP (tos 0x0, ttl 64, id 33412, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xc9ce (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75889088 ecr 0,nop,wscale 7], length 021:13:07.285415 IP (tos 0x0, ttl 64, id 33413, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xc1ee (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75891104 ecr 0,nop,wscale 7], length 021:13:11.445491 IP (tos 0x0, ttl 64, id 33414, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xb1ae (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75895264 ecr 0,nop,wscale 7], length 021:13:19.637403 IP (tos 0x0, ttl 64, id 33415, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0x91ae (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75903456 ecr 0,nop,wscale 7], length 021:13:35.765417 IP (tos 0x0, ttl 64, id 33416, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0x52ae (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75919584 ecr 0,nop,wscale 7], length 021:14:09.045497 IP (tos 0x0, ttl 64, id 33417, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xd0ad (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75952864 ecr 0,nop,wscale 7], length 0listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes 21:13:04.259752 IP (tos 0x0, ttl 64, id 33411, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xcdc0 (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75888078 ecr 0,nop,wscale 7], length 0 21:13:05.269438 IP (tos 0x0, ttl 64, id 33412, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xc9ce (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75889088 ecr 0,nop,wscale 7], length 0 21:13:07.285415 IP (tos 0x0, ttl 64, id 33413, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xc1ee (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75891104 ecr 0,nop,wscale 7], length 0 21:13:11.445491 IP (tos 0x0, ttl 64, id 33414, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xb1ae (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75895264 ecr 0,nop,wscale 7], length 0 21:13:19.637403 IP (tos 0x0, ttl 64, id 33415, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0x91ae (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75903456 ecr 0,nop,wscale 7], length 0 21:13:35.765417 IP (tos 0x0, ttl 64, id 33416, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0x52ae (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75919584 ecr 0,nop,wscale 7], length 0 21:14:09.045497 IP (tos 0x0, ttl 64, id 33417, offset 0, flags [DF], proto TCP (6), length 60) 192.168.0.103.52402 > 121.11.12.31.1234: Flags [S], cksum 0xd0ad (correct), seq 2648987704, win 29200, options [mss 1460,sackOK,TS val 75952864 ecr 0,nop,wscale 7], length 0 通过实际操作可以发现,当发送第一个SYN没有响应时,客户端会再次发送;如果还是没有响应,再隔更长一段时间,继续发送SYN,最终连接超时。从观察情况来看,**默认会进行5次重发**,5次的重试时间间隔分别为1s, 2s, 4s, 8s, 16s。 ## 初始序列号是如何变化的 ## 通过前面的两次抓包可以看到,发送第一个SYN请求的初始序列号seq并不是固定的。实际上,不同的系统它的生成方法可能不同,但是可以知道的是,它在一定时间内,生成seq值肯定不同,否则服务端无法区分这到底是同一个seq的重发还是这个报文在网络中滞留一段时间后又重新到达。RFC 793指出初始序列号可以可看成一个32位的计数器,每隔4ms加1(但不同系统实际实现又可能不太一样,为了安全起见会处理成随机值),因此当它重新回到开始的时候,已经过了够长时间,使得网络中延迟的报文早已消失。 ## 半连接队列 ## 在服务器收到客户端的连接请求,并发送ACK之后,服务端处于SYN\_RECV状态,此时的连接成为半连接,服务器会将半连接放到一个名为半连接队列的地方。 ## SYN攻击 ## 正因如此,如果有人恶意地向服务器发送大量的SYN包,并且由于客户端IP是伪造的,导致服务器收不到ACK,不断重发ACK,以至于半连接队列容易占满,导致无法处理正常的连接请求,并且可能导致服务器资源耗尽。 如何处理SYN攻击又是另外一个话题。 ## 总结 ## TCP三次握手的正常场景我们很容易描述出来,但是涉及更多细节以及异常场景的时候,我们可能不是那么熟悉,通过本文可以简单地了解TCP连接的建立,为后面的网络编程打下基础。但是更多内容本文没有涉及。 本文难免有不足之处,欢迎各位提出建议或意见。本文原文地址[网络编程-从TCP连接的建立说起][-_TCP] 推荐阅读: [netstat的替代者-ss命令实例详解][ss] [网络工具中的“瑞士军刀”了解一下?][nc] [不可不知的网络命令-netstat][netstat] 关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源 ![640?wx\_fmt=jpeg][640_wx_fmt_jpeg] 更多内容阅读原文或访问: [640_wx_fmt_png]: /images/20211214/9d57993f96e1483994c53bb0e7a0b1c3.png [netstat]: http://mp.weixin.qq.com/s?__biz=MzI2OTA3NTk3Ng==&mid=2649284748&idx=1&sn=4779d84db824ffd9032b058186396bdc&chksm=f2f993ebc58e1afd82c735c2f217e6805ef1ed9e8803146fb15d7fbbf4ed8d1687253b43afbf&scene=21#wechat_redirect [ss]: http://mp.weixin.qq.com/s?__biz=MzI2OTA3NTk3Ng==&mid=2649284776&idx=1&sn=17774bdfe5f8e7296f107c2af60e2b31&chksm=f2f993cfc58e1ad9ba32071d1f3e5060400da41d4ed59346de676dee48a918a04466b27a31f2&scene=21#wechat_redirect [nc]: http://mp.weixin.qq.com/s?__biz=MzI2OTA3NTk3Ng==&mid=2649284756&idx=1&sn=fddd9930b8fad80dfd848b233cb66184&chksm=f2f993f3c58e1ae5a04915516ee90083f87cb35c343b5169294bfd319812e3f5803949c97c80&scene=21#wechat_redirect [-_TCP]: https://www.yanbinghu.com/2019/07/02/52476.html [640_wx_fmt_jpeg]: /images/20211214/abebedeedb374f95b0ce968657042631.png
还没有评论,来说两句吧...