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。  同时对这个抓包工具的使用也算初步的入了下门吧。

        上图:

    第一部分的帧数据:

        《java源码剖析之socket(四)---结合tcp调试运行》

    主要的核心的内容,都用黑色的字体写在了图中。 

     同时,途中红色的部分尤其是Protocols in frame特别重要。 

第二部分:

    《java源码剖析之socket(四)---结合tcp调试运行》

    这一部分对应的tcp/ip协议族中的数据链路层和物理层(大牛的博客是这样说的,但是我感觉物理层这东西没法对应)。 这里面要注意一下48位的map地址。 还有就是这里注意一下编码与字符的区别,比如这里面的是十六进制的编码,里面存在大量的数字和字母(a-f),这就跟我们平时见到的字符矛盾了,在java中,一个a代表一个char,应该是采用了2个字节保存,同时数字采用了int型,是四个字节。  而有时候我们又能看到a被 ascll编码代号:97给代替了,这里又出现了数字然而它是一种编码。   所以这里又是编码,又是数据类型的,傻傻分不清楚。  这也是为什么数据库概论课上老师所强调的,数据的基本属性其中有一个就是数据类型的原因吧。   从十进制数据类型来说: 97 若为int型,则在java中它表示应该用4×8=32位表示,97若为十进制编码类型,则在ascll中它表示为a,默认用7位表示,第八位补0。97若为十六进制数据类型,它代表了一个数的大小,在java中用32位存储,97若为十六进制编码,则它代表一个8位的二进制串.  总之以后要注意类型吧,把自己也给搞蒙蔽了。 

    继续上图,接下来是ip协议:

    第三部分:

        《java源码剖析之socket(四)---结合tcp调试运行》

    这一部分对应tcp/ip协议簇的网络层。 其实我觉得这层应该跟tcp层放在同等重要的位置,甚至重要性大于tcp也不为过。  但是好像大家都比较强调tcp呢,嘿嘿嘿。 

    ·理论练习实际,可以看到网络是如此奇妙。  等有时间了一定要好好研究一下ip协议。 

   第四部分:

    《java源码剖析之socket(四)---结合tcp调试运行》

《java源码剖析之socket(四)---结合tcp调试运行》

     这一部分自然是这次实验的重点了。 这里就把tcp面向连接的可靠传输彻底弄撑投,免得托自己后腿。 

可以看到第一幅图中,有一个折叠项:Flags。  这个相当于对某次通信中tcp的类别吧。  就像编程中的Flag,标志位。 每位有其特殊含义,且是事先已经设置好的。 发送方与接收方都要遵循这个约定。 这也是协议的由来。 同时也能看到这其中有一个window size,以及它下面的window scale。 这也印证了理论滑动窗口机制,

    接着看看抓包情况的总体分析:

    第一次抓包:

    《java源码剖析之socket(四)---结合tcp调试运行》

    在这一次实验中,客户机向服务器发送一个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读写缓冲区:

 

                    一旦创建了一个套接字实例,操作系统就会其分配缓冲去以存放接收和要发送的数据。 

《java源码剖析之socket(四)---结合tcp调试运行》

    

 向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到了发送缓冲区队列SendQ,就是在Socket的OutputStream上调用 flush()方法,也不能保证数据能够立即发送到网络。真正的数据发送是由操作系统的TCP协议栈模块从缓冲区中取数据发送到网络来完成的。

  当有数据从网络来到时,TCP协议栈模块接收数据并放入接收缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。

 

    真是一语道破天机啊,之前也看过这篇博文,但是没有实际动手去了解这些,体会是万万没有在这么深的。其中我将我认为最重要的内容用高亮的黑体加粗显示了。这下就不纠结了,写与不写(指io流的写与不写,对应的是读与不读),它都在那里,不紧不慢~~~  相当于通信的双方的流的数据存(”存“不是很不恰当,因为是一个缓冲)取是靠系统的TCP协议栈模块实现。  

    不管怎样吧,自己还是进行了第二次抓包演示的:针对第一次的变化,我将客户机写入的数据量加大了很多,模仿一个大数据的传输,看一下它们的读取关系。  因为我们知道tcp协议是一个全双工通信,通信的双方都能够在通信的任意时刻发送数据。 

    《java源码剖析之socket(四)---结合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

 

    原文作者:java源码分析
    原文地址: https://blog.csdn.net/qq_36285943/article/details/79931422
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞