各位亲爱的读者们大家好,在上一篇文章中我们着重对计算机底层对cpu原理进行了相关分析,那么这篇文章中我们主要会对计算机模块对网络进行一次深入挖掘。
什么是TCP
简单来说就是一种面向连接的稳定,可靠的协议。
实战理解
先从一段实战操作来看连接:
nc指令的安装:(可以用于建立连接)
yum install -y nc 复制代码
从一台linux服务器上发起一次和百度的连接:
nc www.baidu.com 80 复制代码
然后新起一个会话窗口,查看连接的详情:
这里面你会看到nc指令正在和百度建立连接,这就是一次tcp的连接信息。
[root@idea-server~]# netstat -natpActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1055/sshd tcp 0 64 172.18.34.129:22 117.136.79.136:21544 ESTABLISHED 1275/sshd: root@pts tcp 0 0 172.18.34.129:59170 100.100.30.25:80 ESTABLISHED 12816/AliYunDun tcp 0 0 172.18.34.129:39812 14.215.177.38:80 ESTABLISHED 2792/nc tcp 0 0 172.18.34.129:22 117.136.79.136:21545 ESTABLISHED 2682/sshd: root@pts 复制代码
在这里面我们可以发现,通过netstat指令能够看到建立连接的双方的ip和端口信息。
四元组
源IP地址、目的IP地址、源端口、目的端口
nc指令的功能很强大,除了能够建立场景的http连接之外,还可以和一些服务端程序 如redis建立连接,这里我贴出一段小案例:
//在自己的mac上边连接了一台redis服务器【idea @ Mac】>>>>>>nc localhost 6379//连接成功之后会有个堵塞的窗口状态,此时你可以输入一些指令,这里我输入了keys *keys *//此时redis内部是空的,所以返回0 表示没有key*0//存入一个keyset key1 idea +OKkeys *//key的总数是1*1//第一个key的长度是4个字节$4//第一个key的名称,往下类同key1set key2 idea2+OKset key3 idea3+OKkeys **3$4key3$4key1$4key2 复制代码
通过这些案例我们可以从宏观层面来看出tcp连接的一些现象,但是内部实际上还是隐含了比较多的细节点值得我们去学习研究,下边让我们来一层层地突破
三次握手
为什么要进行三次握手?
保证双方都具有收发数据的能力
三次握手的细节点
这里我用了一张图来解释三次握手的相关细节点
注意:
在三次握手的时候要想建立一次可靠的连接,那么seq和ack必须保证配对的关系。如果出现了客户端只发送seq数据包给到服务端,收到了服务端返回ack数据包之后不做任何处理,那么就会对服务端造成资源消耗。当这类恶性行为增加的时候就会出现syn的“泛洪攻击”情况。
服务端和客户端在接收到对方的数据包之后都做了什么?
在建立了握手关系之后,客户端和服务端都会建立属于各自的socket对象,通信队列等信息,用于后续的通信处理。
思考:
假设有一台8080端口的tomcat应用部署在了机器A上边,有一台反向代理的服务器(类似于nginx)部署在了机器B上边。假设理想情况下,机器B和机器A之间最多能够建立起多少个连接?
这类型的问题经常会在做一些高并发系统的性能压测中遇到。我们回顾到上边讲到的netsta和nc的实战中来进行深入理解。
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 172.18.34.129:39812 14.215.177.38:80 ESTABLISHED 2792/nc 复制代码
在建立一次连接的时候,会看到连接的四元组信息,这四个信息只要能够保证唯一性,那么通信的双方就能够建立连接。(不考虑网络屏蔽,机器内存不足的情况,单纯理想状态下)。
客户端一般按照系统内核设置可以分配有65535个端口号数目(可以手动设置内核参数调整)
例如下方指令,查看到某台服务器的实际分配端口范围在32768 - 60999 之间
[root@izwz9ic9ggky8kub9x1ptuz ~]# cat /proc/sys/net/ipv4/ip_local_port_range32768 60999 复制代码
在Linxu系统中,端口的数值范围为无符号short类型,值范围为1 ~ 65535。一般来讲1 ~ 1023范围默认只有Root用户有权限使用,普通用户可以使用区间范围1025 ~ 65535,约6万。
但你要考虑这中间很多的端口可能被已运行的程序占用,不妨打个折降低预期范围,留有5万左右的可用数值,以作缓冲。
所以,上边提到的那个应用场景中,我们可以分析出:
一台server(假设只有一个网卡,其网卡只有一个ipA)和tomcat之间可以建立的连接情况应该是:
//假设从01开始分配端口server's ipA:01 --> tomcat's ip:80server's ipA:02 --> tomcat's ip:80server's ipA:03 --> tomcat's ip:80server's ipA:04 --> tomcat's ip:80...server's ipA:65535 --> tomcat's ip:80 复制代码
那么理想情况下一共可以建立起6万多个连接。
如果只有一台物理机器,希望建立起10万个连接,可以试试给linux配置多个网卡绑定多个ip地址发送请求。
//假设从01开始分配端口server's ipA:01 --> tomcat's ip:80server's ipB:01 --> tomcat's ip:80server's ipA:02 --> tomcat's ip:80server's ipB:02 --> tomcat's ip:80...server's ipA:65535 --> tomcat's ip:80server's ipB:65535 --> tomcat's ip:80 复制代码
这样就能够保证有双倍的链接数目了。
TCP的长连接和短连接
在客户端和服务端进行了通信连接的建立握手之后,双方就会各自建立好对应的socket信息,队列以及相关的内存资源,然后保持连接。
短连接
客户端和服务端握手之后建立连接,发送完信息之后立马发送断开信号,执行四次挥手操作。
四次挥手的过程:
客户端先发送一个FIN+seq的分手信号包,服务端接收之后返回确认接收到指令的ACK数据包,接着再返回一个分手的数据包(FIN+ACK)通知客户端可以进行断开操作,最后客户端再发送ACK数据包通知服务端进行断开操作。整体的一个流程图如下所示:
实战经验分享:
之前在工作中有遇到过发现机器上边有大量连接处于TIME-WAIT阶段,后来发现是因为一些服务的调用方处理繁忙状态,在连接断开的第四个步骤长时间没有发送最终的分手确认包,导致大量连接处于TIME-WAIT状态。后来经过深入定位最终在调用方发现异常,然后进行修复。
短连接特点
1.通常建立了连接后只会发送一次读写请求,然后就进行断开操作,而且一般都是client端主动断开连接。
2.对于通信双方的资源开销比较小。
长链接
client端和server端建立连接之后发送多次读写消息,重复使用同一条连接,读写数据发送完毕之后不会主动进行连接中断操作。
TCP的保活功能
主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,
设计思路优点类似于各类常见中间件的“心跳检测”功能。
假设有一个client和server建立连接发送完数据包之后,超过1个小时都没有再进行通信了,那么此时server端的保活定时器会发送一个探活数据给到client端验证是否有数据响应:
1.如果有响应就证明客户端依然存活,连接继续保持。
2.如果客户端没有响应,服务端会在一定时间间隔之后重新发送数据包检测,这里面的重试次数会有一个上线阈值,如果始终没有响应,server端会主动关闭连接。
长链接特点
1.可以达到连接复用,减少开销的效果。
2.如果遇到恶意攻击,反而会随着恶意连接的增多,对server端造成过度资源损耗,这种情况下server端可以配置一些策略,当连接数打满的时候主动关闭一些建立了连接但是长时间没有进行通信的通道。
网络数据传输的过程
网络的osi七层模型
相信大部分的小伙伴都有听说过这个网络模型,其结构如下图所示:
但是这种模型的分层过于复杂,所以在实际应用当中也并没有进行落地实现。
tcp/ip 五层模型
市面上比较常见的模型是tcp/ip五层模型,具体分层为:应用层,传输层,网络层,数据链路层。
这里我们通过一个案例实战来深入理解这五个层面:
首先我们用一台linux机器作为client端,计划通过这台机器对baidu发送请求,然后通过tcpdump对发送请求的数据包做过滤分析。
先开启tcpdump监听的指令:
//监听eth0网卡上边的80端口的请求数据[root@idea-test ~]# tcpdump -nn -i eth0 port 80 复制代码
然后对百度首页发送一次请求:
[root@kun-120-79-204-69 ~]# curl www.baidu.com <!DOCTYPE html> <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8> <meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer> <link rel=stylesheet type=text/css href=http://s1.bdstatic.com/ r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu. com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1 > <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu> <span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=s u value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&a mp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html> 复制代码
此时你会看到server端打印出了数据包信息,对应解释看下方备注:
[root@idea-test ~]# tcpdump -nn -i eth0 port 80 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes // |发送方地址ip+端口 |百度机器ip+端口 17:14:14.600384 IP 172.18.34.129.59170 > 100.100.30.25.80: Flags [.], seq 2915433426:2915434858, ack 2359524898, win 1432, length 1432: HTTP 17:14:14.600391 IP 172.18.34.129.59170 > 100.100.30.25.80: Flags [P.], seq 1432:1954, ack 1, win 1432, length 522: HTTP // |S 代表第一次握手发送SYN数据包,.代表接收方返回的ACK确认数据包 // |下边三行包含了三次握手中需要传输的数据包seq,ack信息 17:14:14.602770 IP 172.18.34.129.44922 > 14.215.177.39.80: Flags [S], seq 3229198268, win 29200, options [mss 1460,sackOK,TS val 2464453029 ecr 0,nop,wscale 7], length 0 17:14:14.609684 IP 14.215.177.39.80 > 172.18.34.129.44922: Flags [S.], seq 1786736958, ack 3229198269, win 8192, options [mss 1452,sackOK,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,wscale 5], length 0 17:14:14.609702 IP 172.18.34.129.44922 > 14.215.177.39.80: Flags [.], ack 1, win 229, length 0 //在建立了握手之后,client端机器向百度机器发送了一个数据包 17:14:14.616546 IP 172.18.34.129.44922 > 14.215.177.39.80: Flags [P.], seq 1:78, ack 1, win 229, length 77: HTTP: GET / HTTP/1.1 //百度机器返回了一个确认的数据包,但是此时还没将百度页面的数据返回,只是通知客户端我接收到了你的数据包信息 17:14:14.623603 IP 14.215.177.39.80 > 172.18.34.129.44922: Flags [.], ack 78, win 908, length 0 //接下来百度这边返回了页面的数据信息给到客户端,注意,此时的数据包长度是2781 17:14:14.624543 IP 14.215.177.39.80 > 172.18.34.129.44922: Flags [P.], seq 1:2782, ack 78, win 908, length 2781: HTTP: HTTP/1.1 200 OK //客户端接收到数据之后需要返回一个ack的确认数据包,百度接收到之后也会返回ack确认数据包 17:14:14.624550 IP 172.18.34.129.44922 > 14.215.177.39.80: Flags [.], ack 2782, win 272, length 0 17:14:14.627410 IP 100.100.30.25.80 > 172.18.34.129.59170: Flags [.], ack 1432, win 1893, length 0 17:14:14.627417 IP 100.100.30.25.80 > 172.18.34.129.59170: Flags [.], ack 1954, win 1889, length 0 //开始进行四次挥手的连接分离操作 17:14:14.630397 IP 172.18.34.129.44922 > 14.215.177.39.80: Flags [F.], seq 78, ack 2782, win 272, length 0 17:14:14.637359 IP 14.215.177.39.80 > 172.18.34.129.44922: Flags [.], ack 79, win 908, length 0 17:14:14.637459 IP 14.215.177.39.80 > 172.18.34.129.44922: Flags [F.], seq 2782, ack 79, win 908, length 0 17:14:14.637469 IP 172.18.34.129.44922 > 14.215.177.39.80: Flags [.], ack 2783, win 272, length 0 17:14:16.003074 IP 172.18.34.129.59170 > 100.100.30.25.80: Flags [P.], seq 1954:3312, ack 1, win 1432, length 1358: HTTP 17:14:16.030523 IP 100.100.30.25.80 > 172.18.34.129.59170: Flags [.], ack 3312, win 1893, length 0 ^C 复制代码
通过对上述的保文进行分析之后,我们对于一次请求过程中传输的数据信息有了个较为深入的认识,那么接下来回顾到之前我所提到的网络分层模型进行分析。
应用层
是最靠近用户的OSI层。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务
传输层
处理好三次握手的数据包,然后转交给网络层。主要是计算对应的seq,ack的数据值,定义了一些传输数据的协议和端口号信息等,但是这一层并不会发送该数据包,需要将数据包发送给到下一层使用。
网络层
在计算机的底层有一个叫做路由表的配置信息,可以自行进行查看:
[root@idea-test ~]# route -NKernel IP routing tableDestination Gateway Genmask Flags Metric Ref Use Iface0.0.0.0 172.18.47.253 0.0.0.0 UG 0 0 0 eth0169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0172.18.32.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0 复制代码
从传输层封装好的数据包中已经包含了接收方的ip地址,此时网络层获取到ip之后(假设称之为ipa),ipa需要依次和Genmask做与运算。如果结果和Destination不匹配,则继续进行配对计算。
从现有的路由表配置中我们可以看到和0.0.0.0做与运算,发现和Destination一致,此时将数据包丢给172.18.47.253这个网关(通常就是我们生活中配置的路由器地址)即可。
如果走的是内网地址的数据发送,那么此时匹配到的路由条目应该就是下边两个中的任意一项了,此时匹配的Gateway是0.0.0.0,那么就不需要将数据包发送给到链路层,直接通过eth0发送出去即可了。
链路层
arp协议是链路层的一项协议,在获取到需要发送的下一跳机器ip之后,链路层需要准确查询到对应的物理网卡地址才能进行通信。
因为在链路层中路由器并不知道它的下一跳机子的具体位置在哪里,也就没有办法进行通信了。不管网络层使用的是什么协议,在实际网络的链路上传送数据帧时,最终还是必须使用硬件地址。因此当知道主机的 IP 地址之后,就先要找出其对应的硬件地址。地址解析协议 ARP 可以根据网络层使用的 IP 地址,解析出在数据链路层使用的硬件地址。
查看arp信息:
[root@idea-test network-scripts]# arp -nAddress HWtype HWaddress Flags Mask Iface172.18.47.253 ether ee:ff:ff:ff:ff:ff C eth0 复制代码
ps:可以试试手动删除arp的列表信息,如果删除之后再发送网络请求就会多一步arp广播的操作。
网关接收到数据之后,会将数据发送到ee:ff:ff:ff:ff:ff 这个物理地址上边,然后该机器也是执行相同的运算,找出下一台网关的地址ip,再换算为物理地址。这样一层跳一层,最终将数据发送到目标机器所在的网络集群中,再由目标机器集群的网关进行层层解析,最终将数据接收处理。
这里的一层套一层可以用这张图来理解:
这里从图可以看出链路层的数据传播有些类似于一条链表的形状。
物理层
底层的物理硬件,处理数据发送。将一些物理传输中的比特流接收和发送进行相应的封装,让上边层面无感知地使用。