Java网络编程基础篇

一、前言

网络通讯在系统交互中是必不可少的一部分,无论是面试还是工作中都是绕不过去的一部分,本节我们来谈谈Java网络编程中的一些知识,本chat内容如下:

  • 网络通讯基础知识,剖析网络通讯的本质和需要注意的点
  • 使用Java BIO阻塞套接字 实现简单TCP网络通讯
  • 使用Java NIO 非阻塞套接字实现简单非阻塞TCP网络通讯
  • JavaIO模型与Java NIO中ByteBuffer

二、 网络通讯基础知识

网络通讯的本质用一句话来说是处于两个主机上的两个进程之间进行通讯,如下图:


image.png

如上图主机A和B上面有好多进程,比如QQ进程,手淘进程,微信进程,浏览器进程等等。
这里假如进程1为微信进程,在应用层微信肯定自己约定了自己的应用成层协议(比如约定协议包为协议头+消息内容)。

那么当主机A上的微信用户给主机B上的微信用户发送消息时候,发送的消息内容要首先经过程序把要发送的数据转换为自己的应用层协议格式的数据,把消息转换为应用层包是在用户程序代码里面做的。做完这些后网卡驱动程序会接着把应用层包转换为运输层的tcp包或者udp包,在运输层会把应用层包作为数据,然后在数据包前添加协议头组成运输层包,协议头里面会包含目的地址的网络端口号。

然后运输层的包会被作为数据包的数据部分,然后在数据部分前面添加ip层的头部部分,头部里面会含有当前主机的ip 和目的地址的ip组成ip层的包

ip层的包最后会被转换为数据链路层的数据包帧,在帧的头部会新增当前主机网卡mac地址和下一跳的主机的Mac地址,注意这里不是目的主机的mac地址,因为在源主机和目的主机之间很可能有好多路由器,这时候下一跳的mac地址就是当前主机连接的路由器的Mac地址。

image.png

最后数据链路层的数据帧会被转换会在物理层通过二进制流通过网络传递到网络上,网络流经过路由器时候路由器会首先把二进制流转换为数据链路层的数据帧,然后转换为网络层的ip数据包,然后读取目的地址的ip,然后查找路由表进行路由选择,然后把ip数据包重新转换为数据链路层的帧,这时候数据帧里面的目的Mac地址是路由选择的主机的Mac地址,然后把数据帧通过物理层透明的把二进制流传递到下一站,如果下一站就是目的ip所在主机,则网卡驱动会吧二进制流依次转换为 数据链路层数据帧、ip包、传输层tcp包或者udp包,最后交给应用程序进程进行处理,应用程序转换包为具体数据然后进行处理。

这里需要注意的是网络层只是能确定目的主机,还记得ip包里记录了目的主机的ip,但是一个主机上可能会有多个进程,那么具体把数据交给那个进程进行处理那?这个就是运输层的作用,运输层包里面记录的端口号,运输层会把数据交给具体端口号的进程。即网络通讯的socket地址实际是 ip+端口号。

另外整个通讯过程中传输的是二进制流,没有业务数据包的概念(比如我发送了一个业务请求包),当发送方发了多个业务包后,接受方的运输层并不知道每个包的边界,它只是把接受到的数据传输给应用层,所以应用层要自己根据约定好的协议解析二进制流为业务所需要的包,即半包粘包问题,可以参考(https://gitbook.cn/gitchat/activity/5b13e6a675742e21d6d14ea4)。

另外由于网络传输的都是二进制流,所以在发送方进程需要把要发送的数据(比如本文字符串)进行序列化为二进制流,而接受方应用层需要根据对应的反序列化把二进制流转换为具体的数据(比如文本字符串),即序列化与反序列化问题。

另外路由器只有网络层,数据链路层,物理层三层结构。

一次网络通讯看此很复杂,中间需要做好多协议包的转换,但是好在除了应用层协议部分是需要应用程序自己来做,其他下层都是由网卡驱动程序来完成的,这些对应用程序是透明的。
...

五、 Java IO模型与Java NIO中ByteBuffer

5.1 Java IO模型

image.png

如上图当网络应用进程向socket写入数据时候,首先需要在应用程序内申请一个写buffer,然后把数据写入到写buffer,然后应用程序的执行会用用户态切换到核心态,核心态程序把应用程序写buffer里面的数据拷贝到操作系统层面的写缓存里面。当应用程序读取数据时候是需要把操作系统层面的读buffer里面的数据拷贝到应用程序层面的读buffer里面。一般情况下应用程序层面的buffer都是从堆空间里面申请的,这就需要在用户态和核心态之间数据传输时候进行一次数据copy。这是因为核心态是不能直接应用程序堆内存的,必须转换为直接内存。

如果用户态申请的堆外内存(直接内存)那么就会省去中间的拷贝操作,操作系统层面会直接使用用户态申请的堆外内存(直接内存)里面的数据。

5.2 使用ByteBuffer分配堆内存与堆外内存

Java NIO中提供了一个ByteBuffer用来分配发送和接受缓存用的,其分为两种模式的内存,一个是我们常用的堆内存,一个是堆外内存(直接内存)。

  • 当我们调用ByteBuffer的allocate方法时候,实际分配的是堆内存,其代码如下:
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
    HeapByteBuffer(int cap, int lim) {       
        super(-1, 0, lim, cap, new byte[cap], 0);
    }

可知这里是new了一个byte数组,所以分配的是堆内存。

    ByteBuffer(int mark, int pos, int lim, int cap,  
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

可知其内部是通过hb这个指针来指向了分配的堆内存。

  • 当调用ByteBuffer的allocateDirect方法时候,分配的就是堆外内存:
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

    protected static final Unsafe unsafe = Bits.unsafe();

 DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);
        //1 使用Unsafe分配堆外内存
        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
       //2 对分配的堆外内存进行初始化
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
       //3.创建堆外内存回收器
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }

可知堆外内存是使用UNSAFE类进行内存分配的。

六、更多

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,159评论 11 349
  • 一、什么是TCP/IP 网络和协议 1. TCP/IP是一类协议系统,它是一套支持网络通信的协议集合。网络是计算机...
    karlon的马甲阅读 6,499评论 1 24
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,076评论 1 32
  • 背景某种情况下,某些UI需要重复使用,或者这个UI比较复杂,想要单独编辑。这时候可以创建一个component,并...
    SuperCoderMan阅读 891评论 0 0
  • 姓名:黄礼龙 公司:余姚大发化纤有限公司 2017.6.16-18上海盛和塾264期 《六项精进》学员 组号:乐观...
    黄礼龙阅读 115评论 0 0