java源码剖析之socket(四)---结合tcp调试运行
开始之前,贴上大牛的地址,很多的知识都来自于他的博客,非常感谢这种无私奉献,乐于分享的大牛:
https://www.cnblogs.com/rocomp/p/4790340.html
好,开始自己的表演:
先说说实验的环境吧:
采用了java的socket进行tcp模拟练习
为了更加真实的模拟真实环境,我用了两台主机,一台用java的ServerSocket,用作服务器,为linux ubuntu6,另外一台用java的 Socket,用作客户端,为 windows10。 用windows10主机开启一个局域网,局域网网关为:192.168.191.1 ;ubuntu主机连接进局域网,hdfs给给分配的ip地址为 :192.168.137.57,它的网关为 192.168.137.1。 计算机网络不是很好,不太清楚为社么它不以192.168.191.1为网关。 等有时间了研究。
抓包工具采用:wireshark。 同时对这个抓包工具的使用也算初步的入了下门吧。
上图:
第一部分的帧数据:
主要的核心的内容,都用黑色的字体写在了图中。
同时,途中红色的部分尤其是Protocols in frame特别重要。
第二部分:
这一部分对应的tcp/ip协议族中的数据链路层和物理层(大牛的博客是这样说的,但是我感觉物理层这东西没法对应)。 这里面要注意一下48位的map地址。 还有就是这里注意一下编码与字符的区别,比如这里面的是十六进制的编码,里面存在大量的数字和字母(a-f),这就跟我们平时见到的字符矛盾了,在java中,一个a代表一个char,应该是采用了2个字节保存,同时数字采用了int型,是四个字节。 而有时候我们又能看到a被 ascll编码代号:97给代替了,这里又出现了数字然而它是一种编码。 所以这里又是编码,又是数据类型的,傻傻分不清楚。 这也是为什么数据库概论课上老师所强调的,数据的基本属性其中有一个就是数据类型的原因吧。 从十进制数据类型来说: 97 若为int型,则在java中它表示应该用4x8=32位表示,97若为十进制编码类型,则在ascll中它表示为a,默认用7位表示,第八位补0。97若为十六进制数据类型,它代表了一个数的大小,在java中用32位存储,97若为十六进制编码,则它代表一个8位的二进制串. 总之以后要注意类型吧,把自己也给搞蒙蔽了。
继续上图,接下来是ip协议:
第三部分:
这一部分对应tcp/ip协议簇的网络层。 其实我觉得这层应该跟tcp层放在同等重要的位置,甚至重要性大于tcp也不为过。 但是好像大家都比较强调tcp呢,嘿嘿嘿。
·理论练习实际,可以看到网络是如此奇妙。 等有时间了一定要好好研究一下ip协议。
第四部分:
这一部分自然是这次实验的重点了。 这里就把tcp面向连接的可靠传输彻底弄撑投,免得托自己后腿。
可以看到第一幅图中,有一个折叠项:Flags。 这个相当于对某次通信中tcp的类别吧。 就像编程中的Flag,标志位。 每位有其特殊含义,且是事先已经设置好的。 发送方与接收方都要遵循这个约定。 这也是协议的由来。 同时也能看到这其中有一个window size,以及它下面的window scale。 这也印证了理论滑动窗口机制,
接着看看抓包情况的总体分析:
第一次抓包:
在这一次实验中,客户机向服务器发送一个hello from 192.168.191.1:端口号,服务器向客户机发送一个”谢谢连接我form:192.168.137.47:6889“。 可以看到第一个红色线窗中的内容即为tcp的三次握手。
三次握手时,由客户机主动发起,发送的情况如下:
客户机 服务器
Flag(SYN),seq=0 ----->
<------ Flag(SYN,ACK),seq=0,ack=(0+1)
Flag(ACK),seq=1,ack=(0+1) ------>
至此,双端的连接就建立了。 这里比书本上更深的理解就是:ACK,SYN等是一个标志位,而ack,seq是一个数据信息,要携带信息的,所以它们还是有质的区别的。 同时,虽然是三次握手,但是从逻辑上却能看到它达到了四次的功能。 原因是:第一次:客户机主动唤起服务器; 第二次:服务器主动,同时回复第一次的确认;第三次:客户机主动,回复确认信息。 那么问题的关键点就是在第二次,是因为它一次做了逻辑上的两件事情。 因此从逻辑上来看:无论是客户机还是服务器,它们都主动过一次,并且得到了对方的同意。 这里想想能不能运用一下到我们谈恋爱的过程当中呢? 哈哈哈。
对于传输途中而言,看上图可知道:客户机在确认了服务器的连接请求后,立马就向主机发送了信息。同时主机收到了信息并给了确认。(这个seq就是当前会话确认的序号,会话的推进就是依靠seq的推进,我的理解是这样)。然后服务器又接着给客户机发送了一段信息,接着没等客户机说话,立马又给他发了一个连接断开的指令。 不过这是后话。 这里还有一堆问题没解决。
我长期以来也有一个疑惑,那就是当我们在java中调用了socketOutpustreamWriter的write方法之后,什么时候调用TCP将数据传输过去,是我们将所有的write写完之后呢?还是调用一个方法就发一下。况且cpu怎么知道什么时候write写完还是没又写完? 由于这里涉及到一些底层的东西,自己也无法给出猜测解释或者其他的,因此查询网上大牛的资料:
给出的解释如下:
并且大牛的见解也很独到:
在JAVA中,我们用 ServerSocket、Socket类创建一个套接字连接,从套接字得到的结果是一个InputStream以及OutputStream对象,以便将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中,读写IO流会有异常IOException产生。
套接字或插座(socket)是一种软件形 式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。JAVA 有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;Socket,客户端用它初始一次连接。侦听套接字只能接收新的 连接请求,不能接收实际的数据包,即ServerSocket不能接收实际的数据包。
套接字是基于TCP/IP实现的,它是用来提供一个访问TCP的服务接口,或者说套接字socket是TCP的应用编程接口API,通过它应用层就可以访问TCP提供的服务。
在JAVA中,我们用 ServerSocket、Socket类创建一个套接字连接,从套接字得到的结果是一个InputStream以及OutputStream对象,以便 将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中,读写IO流会有异常IOException产生。
套接字底层是基于TCP的,所以socket的超时和TCP超时是相同的。下面先讨论套接字读写缓冲区,接着讨论连接建立超时、读写超时以及JAVA套接字编程的嵌套异常捕获和一个超时例子程序的抓包示例。
1.socket读写缓冲区:
一旦创建了一个套接字实例,操作系统就会其分配缓冲去以存放接收和要发送的数据。
向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到了发送缓冲区队列SendQ,就是在Socket的OutputStream上调用 flush()方法,也不能保证数据能够立即发送到网络。真正的数据发送是由操作系统的TCP协议栈模块从缓冲区中取数据发送到网络来完成的。
当有数据从网络来到时,TCP协议栈模块接收数据并放入接收缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。
真是一语道破天机啊,之前也看过这篇博文,但是没有实际动手去了解这些,体会是万万没有在这么深的。其中我将我认为最重要的内容用高亮的黑体加粗显示了。这下就不纠结了,写与不写(指io流的写与不写,对应的是读与不读),它都在那里,不紧不慢~~~ 相当于通信的双方的流的数据存(”存“不是很不恰当,因为是一个缓冲)取是靠系统的TCP协议栈模块实现。
不管怎样吧,自己还是进行了第二次抓包演示的:针对第一次的变化,我将客户机写入的数据量加大了很多,模仿一个大数据的传输,看一下它们的读取关系。 因为我们知道tcp协议是一个全双工通信,通信的双方都能够在通信的任意时刻发送数据。
发现它发送了两个数据报文,同时却只收到了一个ack确认报文,但是这个确认报文的确认序号是包含了两个报文数据信息的确认。 由于前面已经看了大牛的博文,所有自己之前所纠结的一些问题,猜想也好,试验也罢,都成了徒劳,因此更多的自己的思考的过程就不写了。况且今天也算写的比较多的一天了有点疲。
最后来看一看它的四次挥手:
从这两次实验来看,挥手的主动方都为服务端:
客户机 服务器
<----------Flag(FIN,ACK),seq=x,ack=y
Flag(ACK),seq=y,ack=x+1 ---------->
Flag(FIN,ACK),seq=y,ack=x+1 ---------->
<------------Flag(ACK),seq=x+1,ack=y+1
至此,四次挥手结束,连接结束。
注意这里,并不是连接关闭的请求都由服务器发起,客户机也可以发起。比如教科书上面就是。
四次挥手跟三次握手有些区别,那就是三次握手是物理上的三次,逻辑上的四次。
而四次挥手则是,逻辑上的四次,物理上也是四次的情况。 主要是它将握手中的第二步拆分成了两步。 至于为什么好像是因为如果采用三次挥手,那么有一次的确认就收不到了。至于为什么不用三次挥手,好像是因为有一方可能会在半关闭状态存在还要发送的数据。 FIN发送的时机,好像是一方已经没有数据要发的时候,便会发送这个。 也就是客户机和服务器谁先到没有数据发送的时候谁就主动发起。 哎这个东西实在是有些复杂,越想越想不通。
最后再记录一下它们中常用的seq和ack的关系及意义:
算了不记了,感觉自己要崩了。 赶快去海皮海皮吧,补充精力!!
大牛地址:https://www.cnblogs.com/rocomp/p/4790340.html