java源码剖析之socket(二)

    上午讲到了Inet4Address,这里就继续接着写吧!

    Inet4Address里面我认为的几个重要的函数都已经看过了,之后就该看看Inet6Address类了。  但是它很多都是跟Inet4Address类似的。而且本身也比较复杂,就没怎么细看了。  只看了一个方法:

    《java源码剖析之socket(二)》

    这里也可以看到为什么我们平时Ipv6多用 ” :”进行一个分隔。 但是因果关系来说不是因为这里解析用了”:“我们就说ipv6要用”:“进行分割,而是因为我们平时用了”:“分割,所以这里才有这个方法的具体处理。  这也是一种理论联系实际吧! 像我们以前上的计算机的导论课,如果不亲自实践,总有一种虚而不实的感觉。自然理解的也就不深刻了。 

    同时,经过之后的调试发现InetAddress系列还有用于Ip格式划分的有一个专门的工具类,好像叫做:InetAddressUtil,但是在看源码的时候好像没有注意到这样的一个依赖。 同时,有一点疑惑也产生了,当时看的时候倒没想这么多。  如果说InetAddress是用于主机Ip地址的规范的话,那么端口怎么表示的呢? 要知道tcp协议传输中就必须知道源端口,以及目的端口的。 去看看源码,应该能解开这个迷惑。  

    去socket类的构造方法中再捋一下,瞬间就明晰了。 

《java源码剖析之socket(二)》

        《java源码剖析之socket(二)》

    通过上午的文章我们已经知道,所有的面向连接的socket实际调用的都是它的一个私有构造方法,也就是上面第一张图的方法,仔细一看,它用了两个InetSocketAddress,来支持连接操作的完成。 两个虽说长的还是有区别,但是由于源码比较多,可能当时也没注意。  回过头来再处理,也为时不晚嘿嘿。   可以看到port被作为它的一个int型的私有成员给保存起来,供Socket使用。

    到这里,对于Socket的连接准备差不多有了一个整体的认识。 就是需要IP地址并进行标准化,主机名并进行标准化,提供相应的端口号。  连接操作采用何种机制,满足何种规范。大体的浏览了一下,就像学理论知识一样总有一种不真实的感觉,因此这里将每个具体类的主要的重点的方法都看一下。

    作为Socket的核心依赖类基类:SocketImpl,自然是要仔细看看的,而且它的名字有点怪,会让人误以为Socket为一个接口,它为Socket的实现类,可能是我见的不多,因为一种模块化的程序设计肯定是要符合一定的规范的,包括命名规范,自己也有一种约定俗成的惯性吧。而事实上它为一个抽象类,而且还是抽象程度比较高的。

    《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

    一连上了四张,发现传上来图片变好大。  但是是值得的,因为每个方法的上面都有对于方法的官方描述。 我想这是比任何教科书和老师都要权威的说明了吧。  我认为的这几个方法重要点在于它能对应于我们所学的理论知识。  比如: 监听连接,当队列长度大于所设置的最大值时,连接就会被拒绝;获取一个连接的同时给它一个SocketImpl的实参;创建一个流对象;连接到指定Ip的指定端口;available()方法返回一个Socket连接中没有被阻塞的数据个数;获得这个Socket连接的输入流或者输出流。  但是因为它们都是高度抽象的,所以都给Abstract了。交给子类去实现吧,这种方法真是舒服嘿嘿嘿。

    然后看看它的成员变量,发现它依赖了Socket,以及ServerSocket,上节中提到的环根源也在于此。 

    《java源码剖析之socket(二)》

    途中框起来的还有一个类FileDescriptor。这个类不是很清楚搞什么的,但是它很重要。查看源码时发现它相当于Socket操作中的一个标识符一样,但是它本身的结构又不是很复杂,不知道是怎么实现的。同时也充当了输入输出流的一个状态位的感觉。 第一次看这个类是在FileInputStream和FileOutputStream中,而且它本身也是位于Io包下面。  有一点像那个tcp滑动窗口的感觉嘿嘿,等以后有机会了研究。

    接下来看它的 一级抽象子类:AbstractPlainSocketImpl。

     《java源码剖析之socket(二)》

    该静态块代码是指在该类第一次加载时便通过JNI调用net库,以备后面使用。 至于调用到哪?我像应该时内存中吧。或者说让java能够管理的内存中。 

    《java源码剖析之socket(二)》

    《java源码剖析之socket(二)》

    这两个方法是针对FileDescriptor的,可以看到它记录了某个FileDescriptor的又多少个线程在使用,并且会根据实时情况对这个数值进行动态修改。 

    至于它的父类的一些核心的方法同样是采用了一个abstract,丢给子类实现。然后看看它的成员变量:

    《java源码剖析之socket(二)》

    可以看到这里有一个核心的地方需要注意了,那就是它里面有两个类SocketInputStream和SocketOutputStram。这也就是负责给我们提供输入输出的流对象了。 但其实回想一下我们所了解的java的io流体系,里面并没有一个叫做Socket什么的流。因此我们难免有些疑惑了,流的体系算比较庞大的,类也比较多,依赖关系也是错综复杂的。  那么它在传输时用的哪个具体的对象来作为传输的实现呢?    这里就采用一个自顶向下,面向深度的介绍了(自己取得拿来装逼用。如果是面向切面的话,就是从一种平行的角度去解析,将每一层的内容解析完再去解析下一层)。哎呀妈呀,这不就是深度优先与广度优先的区别嘛~~~

    好,那就去看这两个流。查看源码:

    《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

    分分钟发现它们其实是分别继承了FileInputStream和FileOutputstram,通过前面的学习,我们知道流体系中按照操作方式分为两个大类,基本流和包装流。  而从其本质来看,其实也就基本流一种。  基本流中,负责读写的有两类(FileIxxxxxStram和ArrayxxxxxStram),负责托管的有两类(FilterxxxxxStream(同时针对输出来说:它的下面又具有一个特殊的PrintStream),DataxxxxxStream)。   可以知道对于xxxxxStream来说,我们说它怎么实现读写操作以及实现原理或许不怎么恰当,但是对于FilexxxxxStream来说,它本身已经是作为了一个基本类,所有方法都是已经具备运行的条件。所以不用再去思考Socket传输时用的哪个流之类的问题了。   现在应该时可以充满底气的拍着胸脯说:Socket的传输是通过FilexxxxxStream实现的。  从这里是不是又能考虑联系一下我们的理论知识呢?记得原来看操作系统书籍的时候,操作系统将所有的硬件都识别为一个文件,即一切皆文件。  那么自然的网络也可以视为一个文件了。   因此这里采用FilexxxxxStream进行操作也就可以理解了。  还有一点就是在操作方式上来说,我们更多时候使用了包装流。  Reader和Writer,其实也就是通过InputStreamReader或者OutputStreamWriter下面的,FileInputStreamReader和FileOutputWriter。  这里顺便复习一下它们的关系,OutputStreamWriter和InputStreamReader是托管给的抽象类:xxxxxStream,而FileInputStreamReader和FileOutputWriter分别是托管给的具体类FileInputStream和FileOutputStream。因此不管它怎么变化都是换汤不换药的。     

    同时注意一下SocketxxxxxStream覆盖了具体的方法,实现了它自己的相应方法:

        《java源码剖析之socket(二)》

    《java源码剖析之socket(二)》

    回想一下FilexxxxxStream,算了回想不起来了,去看一眼。

《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

    嗯,它们的差别在哪我想应该一目了然了吧。   那就是SocketxxxxxStream的每个操作都要指定FileDescriptor。  由此可见FileDescriptor的重要性。

    按照深度优先遍历的说法,现在已经把这个两个子节点处理完了,回到根节点去。

    那么我们就该看看SocketImpl的第三层后代,它们皆为实现类了。 

    首先看看逻辑上最大的那个:PlainSocketImpl

    《java源码剖析之socket(二)》

    通览一遍类,发现它的作用类似于跳板。   按照设计模式的话可能分个适配器模式给它吧。  典型的就是它将所有的核心方法都托管给了它的父类,同时通过上图中的方法跳到具体的实现类去。  将核心方法托管给父类的到现在还是见了很多了,如io体系中的FilterxxxxxStream,以及DataxxxxxStream,BufferReader,BufferWriter等。

    接着往前走,看看DualStackSocketImpl。

    《java源码剖析之socket(二)》

    红线框中表示,这个是Windows系统版本相关的,因为版本所支持的TCP/IP栈的程度不同。

    《java源码剖析之socket(二)》

    可以看到这里它大致是会根据取得到数据来判断是否阻塞,并且更新阻塞状态。

    《java源码剖析之socket(二)》

    上面这个图中的方法可以说是io体系中的核心与精髓了吧。  因为所有的类都是为了完成这些操作,所有的类都是为了完成这些操作而服务。可以看到所有的方法都被贴上了一个native标签,这让我对其中的具体实现不能看个究竟,于是网上资料一搜,说openjdk可以看,于是:

    《java源码剖析之socket(二)》

《java源码剖析之socket(二)》

    以上就是dualStackSocketImpl的c的实现。自己到现在还是没有那种觉悟,就是高等语言与低级语言都只是一种语言(这里不是指java和c的关系),我们应该真正关注的是内存,寄存器,数据结构,算法等知识。  因为说实话到了现在,自己asm(汇编语言)也了解过和看过甚至写过一些,c语言也看多,c++也看过,脚本语言如javaScript,php,python也接触过,java更是一直在学,c#也用过一点点。再比如现在比较热门一点的前端框架,Angular,ReactJs,aui  其实语言就那样,如果想要了解一下它的用法,只需要知道哪些是它语言内部的,哪些是给我们提供的,同时了解一些它们的生命周期,注意事项啥的基本就完了)。但是如果说想要去了解一下它的构成,则必须去花一番功夫去刻苦专研一下。  了解一下它的存储体系,它的一些精彩的算法等等。  过多的纠结语言种类并没有什么益处,什么语言适合做什么事情通常都已经是一个烂大街的话题,因此我们作为一个小小的程序员,我觉得不应该依赖于某一种语言而存活。  事实上是只要我们喜欢,甚至可以自己定义自己的语言,因为语言的差别并不是本质差别。  说了这么多的体会,可惜的是自己并没有这种觉悟,哎!

    回过头来,发现一个搞笑的事情。  看源码里面,引入了好几个头文件。

    而实际上这个connect方法的核心也就是第二图的红线框部分,而通览全文,发现并没有该方法。 因此断定这个核心方法位于头文件中,这个头文件的位置并没有通过相对路径给找到。 因此猜测这是属于windows系统的头文件用于支持网络连接的。   而且我们知道windows系统本身也是用C系语言写的,因此自己调用自己也就不足为奇。 同时windows的程序同时也是运行在c系语言下的,因此调用这么一个头文件更是不足为奇。   那么这个connect方法做了哪些事情,大致猜测一下。  我们知道tcp/ip协议有一个三次握手,建立连接的时候,我想这个connect方法就是用来支持Tcp/ip协议栈操作的吧!

    把这个问题处理了,再返回到根路径去。大致看下另外一个TwoStackSocketImpl,因为它们有很高的相似性:

    《java源码剖析之socket(二)》

    这也能说明它们是按照windows版本去分别的。   一看进度条发现内容已经很多了,就再开一篇接着写。

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