Tcp是传输控制层协议,协议就是一个约束,约定双方进行沟通的方式,该层有两层协议:UDP、TCP,走到这一层的时,使用哪种协议进行传输就已经定了,在这我们只讨论TCP。
什么是TCP协议:面向连接的、可靠的协议,面向连接,什么是连接,这个“连接”是个什么东西,虚渺,怎么理解它,接着往下走。
想知道什么是连接,首先那么我们必须要理解TCP的三次握手、四次分手。
上面都是用户空间操作的,用户空间层,在忙着完成http协议的,而怎么通信的呢,只要告诉它要跟谁通信,那用户空间的应用层会调用内核层的所有环节,所以它于是两个环节。
简易图如下:
syn:synchronous
ack:acknowledge 告知已收到的意思
为什么要三次呢,前两次客户端已经明确了,能去服务端,而且服务端还能基于请求进行返回,那为啥还要第3次呢:其实最后一次的握手就要站在服务端一面,服务端能收到别人发来的,那么客户端面向我服务端的输入是通的,虽然我给客户端回了一个数据包,但是服务端不知道它收没收到啊,所以服务端还要等客户端的一个确认,所以,3次握手之后,这样双方才能确认输入输出都是通的。
1.客户端发送请求至服务端,在这一步,服务端收到了客户端的请求,也只能表
示客户端与服务端的单向通信是连通的。
2.服务端收到客户端的请求,并响应。服务端能收到请求,但是客户端不知道你
有没有收到,所以你得告诉客户端我收到了,发送一个ack至客户端。而且,同样
的,你发我我能收到,那我发给你,你能收到吗?所以,发送syn+ack
3.客户端返回ack给服务端,客户端收到服务端的ack,知道了我跟服务端是通
的,然后再返给服务端一个ack,服务端收到ack之后,只能知道,喔,我发出去
的,你也能收到
当时我在理解这个的时候,因为没有对ack的正确认知,导致一直不理解、想不通:其实ACK只是确认收到你的数据包,并不代表任何意思,并不是说给了你ACK响应了,就表示我同意了,并不是的。它只是确认收到了你的数据包而已。意思要主动说出来,你想要干嘛,自己主动说出来,所以要3次握手。
至此,3次握手完成,其实这3次握手,就是在确认双向连接是否能连通,在3次握手之后,两端会有资源的开辟,代表为对方服务,来完成后续的服务,当完成资源的开辟之后,上面所谓的“连接”就有了,这就是所谓面向的连接,这个连接不是物理的,是两端对称的资源。(此前一直以为,面向连接就是两根网线能连通, ̄□ ̄||)
可靠的传输:三次握手建立了连接,三次握手中的ack确认机制保证了可靠的传输
三次握手需要上图中的内核空间的4层来完成,具体怎么传、传到那,是网络、链路…他们干的事,软件是工程学,是分层的,层与层之间是依赖的,不能一层全部活给干了,不便于扩展及维护,如同开发中的分层似的。
理解了三次握手,那么四次分手,自然一说就能明白。
三次握手,是为了双方开辟资源,抛出线程,可靠传输,响应对方,而分手为什么要四次呢,因为分手是释放资源,必需要双方都确定都不发送或接收数据了,这个时候才能释放,简易图:
Fin:finish,就是要表达我想要结束的意思。
与三次握手的区别在于多了第三步,需要双方都确认finish了,才能断开,这也再啰嗦一句:ACK只是确认你的数据包,并不代表任何意思,意思要主动说出来,你想要干嘛,自己说出来。
抓包看证据,抓包命令,如果没有,需要安装一下:
tcpdump -nn -i eth0 prot 80 回车之后,开始监听(需要有root权限)
-nn 是把抓到数据包中的ip、端口号全都变成数值显示
-i 是抓那个接口。抓eth0网卡,抓数据包中端口号为80 的。
如果电脑有多个网卡,需要确认你测试的时发出去的访问连接是通过你监听的那块网卡走出去的。或者你就把多余的网卡down掉,只保留你监听的那个网卡,通过命令down掉:ifconfig eth1 down。
down掉之后,你还要确定有没有网关能让你的数据包出去,如果没有,则添加一个网关:
如果设置虚拟机,没有设置root用户,但是每台机器都会有root用户,那么密码你的用户的密码
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 65535 bytes
19:06:02.569336 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [S], seq 676009814, win 14600, options [mss 1460,sackOK,TS val 15141229 ecr 0,nop,wscale 6], length 0
(我们本地的一个随机端口号192.168.111.131.46015,访问百度的80端口180.101.49.11.80,想要建立连接,数据包长度length 为0,现在还没发送数据,只是在三次握手)
19:06:02.648857 IP 180.101.49.11.80 > 192.168.111.131.46015: Flags [S.], seq 115756544, ack 676009815, win 64240, options [mss 1460], length 0
百度回了一个ack:S. +后面的ack,数据包长度依然为0
19:06:02.648958 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [.], ack 1, win 14600, length 0
客户端给百度回了一个ack,现在三次握手结束,建立连接(两端开辟资源)
是传输控制层控制的,所以数据包长度都为0
19:06:02.649284 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [P.], seq 1:167, ack 1, win 14600, length 166
本地发送数据给百度,数据长度为166,那个P 的意思是告诉百度,你要立刻处理,不要停留,因为我已经没有什么可发的了
19:06:02.649717 IP 180.101.49.11.80 > 192.168.111.131.46015: Flags [.], ack 167, win 64240, length 0
百度回一个ack
19:06:02.723199 IP 180.101.49.11.80 > 192.168.111.131.46015: Flags [P.], seq 1:1441, ack 167, win 64240, length 1440
百度处理完上面的请求,给客户端返回一个长度为1440的包
19:06:02.723245 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [.], ack 1441, win 17280, length 0
客户端给百度一个大小为0的ack
19:06:02.725712 IP 180.101.49.11.80 > 192.168.111.131.46015: Flags [P.], seq 1441:2782, ack 167, win 64240, length 1341
然后百度又给返回一个1341的数据包
19:06:02.725761 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [.], ack 2782, win 20160, length 0
客户端依然回了百度一个ack
一个发生了4步,分别是两次交互,1440+1341就是整个页面的大小字节数,为啥要分两次传输呢,是因为数据包大小是受到限制的,切割成包的,一个包的大小1460个字节,分几波发送。
19:06:02.726178 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [F.], seq 167, ack 2782, win 20160, length 0
然后,客户端发送给百度,我想结束了,想断开了
19:06:02.726689 IP 180.101.49.11.80 > 192.168.111.131.46015: Flags [.], ack 168, win 64239, length 0
百度回个ack,我知道了,但是我想不想断开,我再想想
19:06:02.776867 IP 180.101.49.11.80 > 192.168.111.131.46015: Flags [FP.], seq 2782, ack 168, win 64239, length 0
然后,百度想了想,你个负心人,断开就断开,拜拜
19:06:02.776928 IP 192.168.111.131.46015 > 180.101.49.11.80: Flags [.], ack 2783, win 20160, length 0
最后,客户端回给百度一个ack
每次交互,都会有一次ack,再啰嗦一句。。。ack只是表示我收到了,没有任何意思
如果想看报文的具体信息,那么就在tcpdump命令中加个参数,可以看到每次发送报文的内容:
tcpdump -nn -X -i eth0 port 80,以下内容可忽略:
[root@bogon test]# tcpdump -nn -X -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 65535 bytes
19:43:31.180092 IP 192.168.111.131.47860 > 180.101.49.12.80: Flags [S], seq 2597667236, win 14600, options [mss 1460,sackOK,TS val 17389839 ecr 0,nop,wscale 6], length 0
0x0000: 4500 003c 18ab 4000 4006 0c74 c0a8 6f83 E..<..@[email protected].
0x0010: b465 310c baf4 0050 9ad5 41a4 0000 0000 .e1....P..A.....
0x0020: a002 3908 0785 0000 0204 05b4 0402 080a ..9.............
0x0030: 0109 590f 0000 0000 0103 0306 ..Y.........
19:43:31.226570 IP 180.101.49.12.80 > 192.168.111.131.47860: Flags [S.], seq 7361780, ack 2597667237, win 64240, options [mss 1460], length 0
0x0000: 4500 002c 00f5 0000 8006 243a b465 310c E..,......$:.e1.
0x0010: c0a8 6f83 0050 baf4 0070 54f4 9ad5 41a5 ..o..P...pT...A.
0x0020: 6012 faf0 9a64 0000 0204 05b4 0000 `....d........
19:43:31.226605 IP 192.168.111.131.47860 > 180.101.49.12.80: Flags [.], ack 1, win 14600, length 0
0x0000: 4500 0028 18ac 4000 4006 0c87 c0a8 6f83 E..(..@[email protected].
0x0010: b465 310c baf4 0050 9ad5 41a5 0070 54f5 .e1....P..A..pT.
0x0020: 5010 3908 740a 0000 P.9.t...
19:43:31.226847 IP 192.168.111.131.47860 > 180.101.49.12.80: Flags [P.], seq 1:167, ack 1, win 14600, length 166
0x0000: 4500 00ce 18ad 4000 4006 0be0 c0a8 6f83 E.....@[email protected].
0x0010: b465 310c baf4 0050 9ad5 41a5 0070 54f5 .e1....P..A..pT.
0x0020: 5018 3908 e604 0000 4745 5420 2f20 4854 P.9.....GET./.HT
0x0030: 5450 2f31 2e31 0d0a 5573 6572 2d41 6765 TP/1.1..User-Age
0x0040: 6e74 3a20 6375 726c 2f37 2e31 392e 3720 nt:.curl/7.19.7.
0x0050: 2869 3338 362d 7265 6468 6174 2d6c 696e (i386-redhat-lin
0x0060: 7578 2d67 6e75 2920 6c69 6263 7572 6c2f ux-gnu).libcurl/
0x0070: 372e 3139 2e37 204e 5353 2f33 2e31 342e 7.19.7.NSS/3.14.
0x0080: 302e 3020 7a6c 6962 2f31 2e32 2e33 206c 0.0.zlib/1.2.3.l
0x0090: 6962 6964 6e2f 312e 3138 206c 6962 7373 ibidn/1.18.libss
0x00a0: 6832 2f31 2e34 2e32 0d0a 486f 7374 3a20 h2/1.4.2..Host:.
0x00b0: 7777 772e 6261 6964 752e 636f 6d0d 0a41 www.baidu.com..A
0x00c0: 6363 6570 743a 202a 2f2a 0d0a 0d0a ccept:.*/*....
19:43:31.227220 IP 180.101.49.12.80 > 192.168.111.131.47860: Flags [.], ack 167, win 64240, length 0
0x0000: 4500 0028 00f6 0000 8006 243d b465 310c E..(......$=.e1.
0x0010: c0a8 6f83 0050 baf4 0070 54f5 9ad5 424b ..o..P...pT...BK
0x0020: 5010 faf0 b17b 0000 0000 0000 0000 P....{........
19:43:31.273168 IP 180.101.49.12.80 > 192.168.111.131.47860: Flags [P.], seq 1:1441, ack 167, win 64240, length 1440
0x0000: 4500 05c8 00f7 0000 8006 1e9c b465 310c E............e1.
0x0010: c0a8 6f83 0050 baf4 0070 54f5 9ad5 424b ..o..P...pT...BK
0x0020: 5018 faf0 9b3f 0000 4854 5450 2f31 2e31 P....?..HTTP/1.1
0x0030: 2032 3030 204f 4b0d 0a41 6363 6570 742d .200.OK..Accept-
0x0040: 5261 6e67 6573 3a20 6279 7465 730d 0a43 Ranges:.bytes..C
0x0050: 6163 6865 2d43 6f6e 7472 6f6c 3a20 7072 ache-Control:.pr
0x0060: 6976 6174 652c 206e 6f2d 6361 6368 652c ivate,.no-cache,
0x0070: 206e 6f2d 7374 6f72 652c 2070 726f 7879 .no-store,.proxy
0x0080: 2d72 6576 616c 6964 6174 652c 206e 6f2d -revalidate,.no-
0x0090: 7472 616e 7366 6f72 6d0d 0a43 6f6e 6e65 transform..Conne
0x00a0: 6374 696f 6e3a 206b 6565 702d 616c 6976 ction:.keep-aliv
0x00b0: 650d 0a43 6f6e 7465 6e74 2d4c 656e 6774 e..Content-Lengt
0x00c0: 683a 2032 3338 310d 0a43 6f6e 7465 6e74 h:.2381..Content
0x00d0: 2d54 7970 653a 2074 6578 742f 6874 6d6c -Type:.text/html
0x00e0: 0d0a 4461 7465 3a20 5468 752c 2031 3520 ..Date:.Thu,.15.
0x00f0: 4f63 7420 3230 3230 2030 323a 3433 3a33 Oct.2020.02:43:3
0x0100: 3220 474d 540d 0a45 7461 673a 2022 3538 2.GMT..Etag:."58
0x0110: 3836 3034 6338 2d39 3464 220d 0a4c 6173 8604c8-94d"..Las
0x0120: 742d 4d6f 6469 6669 6564 3a20 4d6f 6e2c t-Modified:.Mon,
0x0130: 2032 3320 4a61 6e20 3230 3137 2031 333a .23.Jan.2017.13:
0x0140: 3237 3a33 3620 474d 540d 0a50 7261 676d 27:36.GMT..Pragm
0x0150: 613a 206e 6f2d 6361 6368 650d 0a53 6572 a:.no-cache..Ser
0x0160: 7665 723a 2062 6665 2f31 2e30 2e38 2e31 ver:.bfe/1.0.8.1
0x0170: 380d 0a53 6574 2d43 6f6f 6b69 653a 2042 8..Set-Cookie:.B
0x0180: 444f 525a 3d32 3733 3135 3b20 6d61 782d DORZ=27315;.max-
0x0190: 6167 653d 3836 3430 303b 2064 6f6d 6169 age=86400;.domai
0x01a0: 6e3d 2e62 6169 6475 2e63 6f6d 3b20 7061 n=.baidu.com;.pa
0x01b0: 7468 3d2f 0d0a 0d0a 3c21 444f 4354 5950 th=/.......
0x01e0: 3c68 6561 643e 3c6d 6574 6120 6874 7470 ..
0x02e0: bee5 baa6 e4b8 80e4 b88b efbc 8ce4 bda0 ................
0x02f0: e5b0 b1e7 9fa5 e981 933c 2f74 6974 6c65 ......... ........<
0x0390: 696d 6720 6869 6465 666f 6375 733d 7472 img.hidefocus=tr
0x03a0: 7565 2073 7263 3d2f 2f77 7777 2e62 6169 ue.src=//www.bai
0x03b0: 6475 2e63 6f6d 2f69 6d67 2f62 645f 6c6f du.com/img/bd_lo
0x03c0: 676f 312e 706e 6720 7769 6474 683d 3237 go1.png.width=27
0x03d0: 3020 6865 6967 6874 3d31 3239 3e20 3c2f 0.height=129>.
0x03e0: 6469 763e 203c 666f 726d 2069 643d 666f div>...
0x0470: 203c 696e 7075 7420 7479 7065 3d68 6964 .... 180.101.49.12.80: Flags [.], ack 1441, win 17280, length 0
0x0000: 4500 0028 18ae 4000 4006 0c85 c0a8 6f83 E..(..@[email protected].
0x0010: b465 310c baf4 0050 9ad5 424b 0070 5a95 .e1....P..BK.pZ.
0x0020: 5010 4380 634c 0000 P.C.cL..
19:43:31.273472 IP 180.101.49.12.80 > 192.168.111.131.47860: Flags [P.], seq 1441:2782, ack 167, win 64240, length 1341
0x0000: 4500 0565 00f8 0000 8006 1efe b465 310c E..e.........e1.
0x0010: c0a8 6f83 0050 baf4 0070 5a95 9ad5 424b ..o..P...pZ...BK
0x0020: 5018 faf0 47ac 0000 3d22 6267 2073 5f62 P...G...="bg.s_b
0x0030: 746e 223e 3c2f 7370 616e 3e20 3c2f 666f tn">.. .
通过负载均衡器这个场景我们可以想一下,
第一次发给了s1服务器,第二次发的包仍然只能发给s1,不能发给s2,如果发生错发情况,s1没有后面的步骤,s2没有前面的步骤,那么最终结果就是s1、s2都会失败,那么对应的就是c端也会失败,双方都开辟不了资源,交互不了,连接失败。
说socket之前,先了解下linux的“一切皆文件”。
exec 8<> /dev/tcp/www.baidu.com/80
路径必须是/dev/tcp
<>表示输入输出,这个8指向了后面的socket链接,/dev/tcp/www.baidu.com/80看似是一个文件路径,其实是linux一切皆文件的特性,linux内核会将路径转换成与百度的一个tcp socket链接,用8指向了这个连接,通过操作这个文件可以从连接中可以输入流输出流。
这个8的术语就叫 文件描述符 file descriptor,如同java中的变量
echo -e "GET / HTTP/1.0\n" 1>& 8
-e是为了解析\n,1表示标准输出,在linux中,“>”是将结果到文件中,而“>&”是到另一个文件中
cat 0<& 8
0表示标准输入
其实说的上面那么多,就为了表达一个意思:一个socket连接,可以用一个文件描述符来表示
ip+port:ip+port
通过netstat -natp 查看目前的连接,同一个客户端访问服务端一个端口,套接字中,一定有一个维度来区分不同的连接,所以socket套接字,就是ip+port套上ip+port,一个随机端口号访问一个ip上的固定端口号
ip:是互联网中主机定位用的,也就是可以通过ip找到这个主机
port:找到主机之后,而这个主机中有很多进程,那你要把这个数据包交给那个进程,通过这个端口号来找到程序的进程或线程。
服务端的端口号是固定的,比如tomcat的8080,客户端的端口号是随机的,最多65535个,但是不要认为一台机器总共只能随机65535个,你要明白套接字,他们是一组,不能只看一半,也就是说如果再连另外一台机器,依然能建立65535个链接。即使端口号重复了,但是整个socket是不重复的。
上面是基于客户端,那么服务端呢,建立了65535个链接,那服务端消耗了几个端口号呢:没错,只会消耗1个,如图所示。
那么他们怎么通信的呢,上面也说了,三次握手之后,建立连接(两端资源的开辟),在建立连接的时候,同时也会fock出一个进程或者线程,将socket与这个进程或线程进行绑定,那持有是怎样的一个形式呢,你别忘了,我们上面建立的一个socket,以一个文件描述符8来体现,那么这个线程或者进程只对这个文件描述符8进行读写就可以了,也就是说在fock线程或者进程的时候,socket被描述成了文件描述符,然后把这个文件描述符与fock出的线程或者进程绑定起来,然后这个线程或进程只对这个文件描述符8进行操作就行了。
通过上图,我们知道了现在是一个线程或者进程对应一个socket,那多个socket能不能对应一个进程或线程呢??—这个知识点就是多路复用器 epoll,意思也就是说一个进程或线程对应多个客户端,避免浪费资源
就是你应用层组装数据,然后递给下一层–传输控制层,下层知道了你想要连接,然后就帮你趟路,进行握手,看路通不通、好不好走,然后准备好第一次握手包之后就停住了,因为这个包不是传输层发出去的,这个包怎么发出去,怎么找到对方主机,不是我控制层做的,所以就开始调用网络层、…层,通过网关 mac地址找到对方主机,每一层都有协议,最后就是,各层各司其职,上面的层都是阻塞的。