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类进行内存分配的。

六、更多

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

十二条Linux运维面试必备经典笔试/面试题,来挑战一下!

又到了一年一度的秋招,作为运维方向,看了一些面经,收集了一些笔试面试题,总结了一下,贴出来仅供参考,有错误的地方还请指出. 1.Linux设置环境变量 暂时的:...

4789
来自专栏琯琯博客

优化 Laravel 网站打开速度

1.关闭debug 打开.env文件,把debug设置为false. APP_ENV=local APP_DEBUG=false APP_KEY=base64...

41711
来自专栏耕耘实录

CentOS7中firewalld的安装与使用详解

1、firewalld提供了支持网络/防火墙区域(zone)定义网络链接以及接口安全等级的动态防火墙管理工具。它支持 IPv4, IPv6 防火墙设置以及以太网...

742
来自专栏Brian

Linux 系统优化

概述 在Linux 学习笔记一大体介绍了一些简单的Linux知识和一些简单的优化。下面我们来学习一下Linux和Linux一些安全知识(Linux是基于内核为...

4346
来自专栏前端萌媛的成长之路

一波webpack

1864
来自专栏Python

nginx优化 突破十万并发

文章转载于:http://9388751.blog.51cto.com/9378751/1676821

4200
来自专栏SDNLAB

脱坑神器,让你一步了解ODL控制器集群

一、控制器集群基本知识 1.1 Consensus一致性 Consensus一致性是指多个服务器在状态达成一致,但是在一个分布式系统中,因为各种意外可能,有的...

4477
来自专栏运维小白

12.22 php-fpm慢执行日志

php-fpm慢执行日志目录概要 vim /usr/local/php-fpm/etc/php-fpm.d/www.conf//加入如下内容 request_s...

3527
来自专栏xcywt

TCP头部分析与确认号的理解

1、TCP的特点: 基于字节流 面向连接 可靠传输 缓冲传输 全双工 流量控制 2、头部格式和说明 图源百度。如下图示,就是TCP包的头部结构。可以看到这个头部...

32110
来自专栏龙首琴剑庐

基于复杂方案OWSAP CsrfGuard的CSRF安全解决方案(适配nginx + DWR)

1、什么是CSRF? 已经有很多博文讲解其过程和攻击手段,在此就不重复了。 O(∩_∩)O 不清楚的同学,请自行搜索或按链接去了解: http://blog...

4627

扫码关注云+社区

领取腾讯云代金券