大家好,我是阿秀
后端,可以说是仅次于算法岗之外竞争最为激烈的岗位,而其中的服务端开发也是很多人会选择在秋招中投递的一个岗位,我想对于很多人来说,走上服务端开发之路的起点就是一个回声服务器了。
今天带大家实战一把,真实体验服务端底层数据交换的点点滴滴,在这过程中可以让你看见 TCP 三次握手四次挥手的具体过程,全程干货,不开玩笑。
客户端:Ubuntu 16.04 ,IP:192.168.78.128 ,简称为客户端A
服务端:Ubuntu 16.04 ,IP:192.168.78.130,简称为服务端B
抓包利器-大白鲨 Wireshark 以及 Linux 下的抓包命令 tcpdump
GCC:5.4.0
因为 Wireshark 的图标就很像一条大鲨鱼的鱼鳍,所以又叫大白鲨,不信你看看
大白鲨
这里简单介绍下 TCP 最经典的三次握手与四次挥手
三次握手四次挥手第一次握手:建立连接。客户端发送连接请求报文段,将 SYN 位置为1,seq(Sequence Number) 为 x;然后,客户端进入 SYN_SEND 状态,等待服务端的确认;
第二次握手:服务端收到 SYN 报文段。服务端收到客户端的 SYN 报文段,需要对这个SYN 报文段进行确认,设置 ack(Acknowledgment Number) 为 x+1 (也就是 seq+1);同时,自己还要发送 SYN 请求信息,将 SYN 位置为1,seq 为 y;服务端将上述所有信息放到一个报文段(即 SYN+ACK 报文段)中,一并发送给客户端,此时服务端进入 SYN_RECV 的状态;
第三次握手:客户端收到服务端的 SYN+ACK 报文段。然后将ack设置为y+1,向服务端发送ACK报文段,这个报文段发送完毕以后,客户端和服务端端都进入 ESTABLISHED 状态,完成TCP三次握手。
成功建立连接后,客户端与服务端就开始进行数据交互。客户端发送数据,服务端回复收到该数据,然后交替进行下去。
当客户端和服务端通过三次握手建立 TCP 连接进行可靠数据传输后,当数据传送完毕,肯定是要断开TCP连接,这里就有了神秘的“四次挥手”。
第一次挥手:客户端设置 seq和ack,向服务端发送一个FIN报文段;此时,客户端进入 FIN_WAIT_1 状态;这表示客户端没有数据要发送给服务端了;
第二次挥手:服务端收到了客户端发送的FIN报文段,向客户端回一个 ACK 报文段,ack 为 seq+1;客户端进入 FIN_WAIT_2 状态;服务端告诉客户端,我“同意”你的关闭请求;
第三次挥手:服务端向客户端发送 FIN 报文段,请求关闭连接,同时服务端进入 LAST_ACK 状态;
第四次挥手:客户端收到服务端发送的 FIN 报文段,向服务端发送 ACK 报文段,然后客户端进入 TIME_WAIT状态;服务端收到客户端的 ACK 报文段以后,就关闭连接;此时,客户端等待 2MSL 后依然没有收到回复,则证明服务端已正常关闭,那好,客户端也可以关闭连接了。
这里主要使用的是尹圣雨[韩]的著作《TCP/IP网络编程》第 4 章中的简易版回声服务器来进行实验。
所谓回声服务端,就像小时候在回声山谷中玩的游戏一样,你朝山谷中大吼一声“啊”,然后山谷也会给你一个“啊”。回声服务端就是你向服务端发送一个“hello world”,回声服务端也向你回复一个“hello world”。
1、将客户端程序 echo_client.c 放在客户端 A 中,将服务端程序 echo_server.c 放在服务端 B 中
2、在客户端 A 中开启一个命令行窗口,使用 tcpdump 命令监控 A、B 之间的网络通信,并将消息保存为 pacp 文件,方便后续进行抓包分析
3、在服务端 B 中编译程序 echo_server.c,开启服务端程序 echo_server,监听指定端口 2333
这里的端口号可以自己指定,在 1025-65535 之间都可以,主要是因为0-1024已经被系统占用了,比如http的80端口,ssh的22端口。而 Linux 下默认端口数在65535个,所以自己可以指定的端口号就在1025-65535之间。
4、在客户端 A 中编译程序 echo_client.c,并且开启客户端程序 echo_client,指定通话 IP 以及端口号,我在这里就是服务端 B 的IP:192.168.78.130以及 2333 端口号了
5、在客户端 A 中发送消息“hello”,然后按 Q 退出即可
6、通信完毕,将 2 中保存的文件转存到 Windows 环境下,使用大白鲨 Wireshark 进行网络数据包分析。
7、分析抓到的数据包文件
说了那么多,终于可以开始开干了!
1、将echo_client.c 、echo_server.c分别放在客户端A:192.168.78.128 以及服务端B:192.168.78.130 中。
客户端A 服务端B2、 在客户端A中新开一个命令行窗口,输入命令:sudo tcpdump -i any tcp and host 192.168.78.130 and port 2333 -w message.pcap
tcpd数据包保存命令1、由于tcpdump命令需要管理员权限,所以需要加上sudo命令进而获取管理员权限。
2、这段命令的大概含义就是监控客户端 A:192.168.78.128,和服务端 B:192.168.78.130 之间在端口号2333 上的基于TCP的数据交换,并且保存为 message.pcap 文件
可以看到,我们在输入该条命令后,需要首先输入 Linux 下的密码获取管理员权限,然后就开始监听客户端 A:192.168.78.128,与服务端 B:192.168.78.130之间在端口号2333上的TCP通信了。
3、接下来,我们进入含有 echo_server.c的文件夹,将服务端B的程序进行编译,编译命令为 gcc echo_server.c -o echo_server,可以看到当前文件夹下出现了 echo_server 程序
编译服务器程序接下来,开始监听我们预先设置好的端口号 2333,命令为:./echo_server 2333,服务端开始正式监听。
运行服务器程序4、服务端 B 设置完毕,我们开始转战客户端 A ,在 2 中使用 tcpdump 命令监听的那个端口号不要关闭,千万不要关闭,我们在客户端 A 中另外新开一个命令行。
跟服务端 B 中类似,首先将客户端 A 中的程序echo_client.c进行编译,编译命令:gcc echo_client.c -o echo_client
编译客户端程序跟服务端 B 中类似,我们在客户端 A 中开启客户端程序echo_client,指定通话IP :192.168.78.130及端口号 2333
命令为./echo_client 192.168.78.130 2333
运行客户端程序可以看到出现 “Connected…..”字样,说明我们已经走完长征两万五千公里,成功会师啦!
客户端 A 与服务端 B 终于成功连接了,这个时候我们再转去看一下服务端 B 的状态。
服务器程序状态在服务端 B 的监听窗口也出现了“Connect client 1”字样,换句话说,在服务端看来,有一个客户端与它成功建立连接了。
5、下一步就可以开始通信了,我们在客户端 A 中发送“hello”字样。
在客户端A发送消息可以看到,我们在客户端 A 中发送了一条消息“hello”, 服务端 B 也给了我们一个消息“hello”,这也就是我们上文中提到的回声服务端了。
接下来,我们按照提示,输入“Q”结束本次通话。
退出客户端A至此,本次通话结束。
6、最后我们在 2 中开启 tcpdump 命令监控的界面中,按下 ctrl+ c ,结束监听。
保存抓包文件可以看到,提示我们一共成功捕获了10 个packets,没有数据包丢失。接下来,我们将捕获文件 message.pacp传输到 Windows 下开始进行抓包分析。
可以看出一共 10 个数据包,也对应了上文中我们在 Linux 下通过tcpdump命令抓到的数据包个数。其中序号 1-3 为三次握手的数据包,序号 4-7 为两次数据交换的数据包,8-10 为三次挥手的数据包。
抓到的10个数据包问题1:4-7 为什么是两次数据交换呢?
回答1:我们的回声服务端就是你发送什么数据过去,服务端发送什么数据回来,所以第一次数据交换:客户端A发送数据”hello“到服务端B,B回复 确认收到。这也对应着4、5数据包;第二次数据交换:服务端B发送数据”hello“到客户端A,A回复 确认收到。这也对应着6、7数据包。
问题2:说好的四次挥手呢?这里怎么只有三次了?
回答2: 因为服务端收到客户端的 FIN 后,服务端也可以同时关闭连接,这样就可以把 ACK 和 FIN 两个包合并到一起发送,这样可以节省一个网络包,“四次挥手”变成了“三次挥手”。这样可以节省网络资源,省时又省力。而通常情况下,服务端收到客户端的 FIN 后,很可能还没发送完数据,所以就会先回复客户端一个ACK 包,完成所有数据包的发送后,才会发送 FIN 包,也就是“四次挥手“了。
第一次握手,序号为1,客户端A:192.168.78.128 向服务端B:192.168.78.130 发送SYN请求包,seq为1796975076。
第二次握手,序号为2,服务端 B:192.168.78.130 向客户端A:192.168.78.128 发送 SYN、ACK 请求回复包,seq为1222412335,ack为1796975077,也就是第一次握手中的 seq+1。
第三次握手,序号为3,客户端A:192.168.78.128 向服务端B:192.168.78.130 发送 ACK 确认包,seq为1796975077,ack为为1222412336,也就是第二次握手中的 seq+1。
第一次数据交互:
第一次数据交互序号为4,客户端A:192.168.78.128 向服务端B:192.168.78.130 发送 push 消息包,可以看到下方的数据解析为“hello”,并且数据长度 len = 6。
可能有些小伙伴问“hello不是一共5个字符吗?长度应该为5啊。”len = 6是因为“hello“长度为5,再加上结尾的‘\0’,加起来一共就是6了
序号为5,服务端B:192.168.78.130 向客户端A:192.168.78.128 发送 ACK 确认包,表示已经收到该消息。
第二次数据交互:
第二次数据交互序号为6,服务端B:192.168.78.130 客户端A:192.168.78.128发送push消息包,可以看到下方的数据解析为“hello”,并且 len = 6。
序号为7,客户端A:192.168.78.128 向服务端B:192.168.78.130 发送ACK确认包,表示已经收到该消息。
正式的四次挥手如下图所示:
标准的四次挥手我们所抓到的三次挥手如下所示:
我们抓包抓到的三次挥手第一次挥手,序号为8:客户端A:192.168.78.128 向服务端B:192.168.78.130 发送 FIN 请求断开连接包,表示主动请求断开链接。
第二三次挥手,序号为9:服务端B:192.168.78.130 向 客户端A:192.168.78.128发送 FIN、ACK 确认并请求断开消息包,表示收到上次断开连接的请求,并请求断开服务端到客户端的链接。
可以看出,我们所抓的包中,将第二次挥手和第三次挥手合并为一个数据包了,也就是192.168.78.130->192.168.78.128的包中既有FIN也有ACK,所以这也是三次挥手而不是四次挥手的原因。
第四次挥手,序号为 10:客户端A:192.168.78.128 向服务端B:192.168.78.130 发送 ACK 确认包,表示收到服务端发送过来的请求断开连接消息,并给予回复。
授人以鱼不如授人以渔,本次实战的全部文件已经打包分享出来了,公众号后台回复【回声服务器】就可以获取,你也可以自己动手实践一下。毕竟“纸上得来终觉浅,绝知此事要躬行“。
如果大家觉得这种实战类型的文章还可以的话,还请留言告诉我,以后我多为大家带来这种实战类型的文章。
对了欢迎抽奖,后台直接回复【抽奖】即可,公众号抽奖的人数不多,中奖概率还是挺大的,溜了溜了。
https://my.oschina.net/u/658658/blog/417739
《TCP/IP网络编程》- 尹圣雨[韩]
《Wireshark网络分析就这么简单》- 林沛满
《Wireshark网络分析的艺术》- 林沛满
《计算机网络-自顶向下方法》- ames F. Kurose、Keith W. Ross
《TCP/IP详解 卷1:协议》- kevin R.Fall W.Richard Stevens
双非渣硕的秋招之路总结(已拿抖音研发岗SP)
一份百投百中的计算机校招简历是如何迭代足足26版的?
个人C++学习路线大公开
— END —
Hi,小伙伴你好,我是阿秀,一枚从底层慢慢爬到互联网大厂的程序员,公众号上每一篇原创文章都是我精心创作、慢慢打磨出来的,如果你觉得本文对你有所帮助,麻烦点亮一下「赞」和「在看」,也可以「分享」给需要的小伙伴,阿秀真的很需要你的点亮,十分感谢!
“你只管努力,剩下的交给时间就好,我就是活生生的例子~”