二、Java原生网络编程

文章目录

        • 1.网络通信基本常识
          • 1.1 编程中的Socket是什么?
        • 2.BIO详解
          • 2.1 原生JDK网络编程-BIO
        • 3.NIO详解
          • 3.1 BIO和NIO的区别
          • 3.2 什么是NIO?
          • 3.3 NIO之Buffer
        • 4.AIO
        • 5.直接内存和零拷贝
          • 5.1 直接内存比堆内存快在哪里?
          • 5.2 零拷贝
            • 5.2.1 什么是零拷贝?
            • 5.2.2 零拷贝的作用
            • 5.2.3 零拷贝数据传输
            • 5.2.4 Linux之MMap内存映射
            • 5.2.5 Linux之sendFile
            • 5.2.5 Linux之slice

1.网络通信基本常识

1.1 编程中的Socket是什么?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,其实就是一个门面模式。TCP用主机的IP地址加上主机的端口号,作为TCP连接的端点,这种端点就叫做套接字(Socket)。

长连接和短连接
短连接就是两个主机之间完成一次通信后就断开,比如:UDP和Http1.0。
长连接指两个主机之间完成通信后,不立即断开连接,还可以继续使用进行通信,比如:Http1.1、Http2.0和TCP。

关注的三件事

客户端、服务端和通信编程

  1. 连接(客户端连接服务端,服务端接收连接)
  2. 读取网络数据
  3. 写入网络数据

总结:
Socket将应用层以下的传输层、网络层和数据链路层都封装起来了,由操作系统提供,应用层只需要通过Socket就可以完成与下层之间的通信。

2.BIO详解

阻塞IO:Blocking IO (面向流)
客户端在访问服务器时,当服务器没有响应数据,此时服务端就会创建一个线程,并处于阻塞状态,直至等到服务器数据响应。

存在的问题:
当有很多客户端在请求服务器,而服务器都没有数据响应时,就会创建出很多的线程处于阻塞状态,线程的创建伴随着CPU性能的消耗和内存资源的消耗。

2.1 原生JDK网络编程-BIO
  • 服务端
    • ServerSocket负责绑定IP地址,启动监听端口;
    • 在子线程中创建一个Socket(可以通过线程池来避免线程的重复创建和销毁),负责与客户端的Socket发起连接操作;
    • 连接成功后,双方通过输入流和输出流进行同步阻塞式通信
  • 客户端
    • 通过Socket与服务端子线程中创建的Socket建立连接,并通过输入流和输出流进行同步阻塞式通信

3.NIO详解

IO多路复用:No Blocking IO (面向缓冲区)
为了缓解BIO导致的大量线程创建引起的资源浪费问题,出现了NIO机制,通过服务器创建的一个线程来管理多个客户端发送的请求。

3.1 BIO和NIO的区别
  • BIO阻塞IO是面向流的,客户端与服务端是通过流的方式直接进行通信;
    NIO多路复用是面向缓冲区的,客户端和服务端发送消息和接收消息都是通过缓冲区来完成,发送的消息存放在缓冲区,读取的消息也是来自于缓冲区;
  • BIO阻塞IO一个服务端只能服务一个客户端;NIO多路复用IO一个服务端可以服务多个客户端;
3.2 什么是NIO?

三大核心组件及关系

  • Selector
    • 在一个线程中维护着一个或多个Selector,根据客户端的数量决定;
  • Channel
    • 服务端:ServerSocketChannel,相当于BIO中的ServerSocket
    • 客户端:SocketChannel,有几个客户端就有几个SocketChannel
  • Buffer
    • 读取和写入数据都分别对应一个缓冲区,不同的客户端之间通信也对应不同的缓冲区。
3.3 NIO之Buffer

重要属性

  1. capacity 容量
    • Buffer的容量,最多可以存储多少数据
  2. position 位置
    • 记录下一次写入数据的位置
  3. limit 限制
    • 在limit没有限制的情况下,limit等于capacity

Buffer的分配、读写和常用操作

Buffer是一个抽象类,几乎每一个基本数据类型都有一个实现类,由于网络数据的传递方式是二进制的,所以我们使用到的是ByteBuffer实现类。

Buffer的分配:
可以在堆中分配内存,也可以在直接内存中分配内存。

Buffer的读写操作:

  • 应用程序往Buffer中读取/写入数据
    • 应用程序提供的Buffer,比如ByteBuffer中存在put方法,可以将数据写入Buffer缓冲区
    • 应用程序通过get方法从Buffer缓冲区中读取数据
  • SocketChannel往Buffer中读取/写入数据
    • SocketChannel中的read方法其实是往Buffer中写入数据的操作,他的功能是从SocketChannel中读取数据,然后写入Buffer缓冲区中。
    • SocketChannel中的write方法才是从Buffer缓冲区中读取数据,然后写入到SocketChannel中去

从Buffer中读取数据注意事项:
Buffer在写入数据后,变成从Buffer中读取数据时,就需要先调用flip()方法,以保证position变成起始位置,limit停留在读取数据的末端;否则,读取数据时候,position还停留在上一次写入数据的那个位置,所以就无法读取到数据。

Buffer的继承关系:

  • Buffer
    • ByteBuffer
      • MappedByteBuffer
    • CharBuffer
    • ShortBuffer
    • IntBuffer
    • LongBuffer
    • FloatBuffer
    • DoubleBuffer

4.AIO

异步IO
因为读取网络数据和写入网络数据,都需要通过系统调用来完成上下文的切换,为了解决这个问题出现了AIO,异步IO.

Windows系统实现了AIO,而Linux是采用的NIO来模拟实现AIO,但是并非是真正实现了AIO。

5.直接内存和零拷贝

5.1 直接内存比堆内存快在哪里?

TCP缓冲区,每个TCP的Socket的操作系统内核中都有一个发送缓冲区(SO_SEDBUF)和一个接收缓冲区(SO_RECVBUFF).
在进行网络通信的过程中,尽可能的申请直接内存。

直接内存相比于堆内存,避免了2次拷贝。因为在堆中申请的内存,需要拷贝到直接内存,然后再拷贝到套接字发送缓冲区中,最终发送给网络;如果在直接内存中申请内存,就直接拷贝到套接字缓冲去中,最终发送给网络。
之所以在堆中申请的内存不能直接跨过直接内存,是因为堆中申请的内存存在GC回收的可能,所以需要拷贝到直接内存中,避免GC回收。

5.2 零拷贝
5.2.1 什么是零拷贝?

零拷贝(Zero-copy)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时,节省CPU周期和内存带宽。

5.2.2 零拷贝的作用

零拷贝技术可以减少数据拷贝和共享线程操作的次数,消除传输数据在存储器之间不需要的中间拷贝次数,从而有效的提高数据传输效率。
零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销。

5.2.3 零拷贝数据传输

传统数据传输:
buffer = File.read;
Socket.write(buffer);

第一行代码执行的操作:

首先从内核空间中的磁盘中,通过DMA拷贝将数据拷贝到文件读取缓冲区;然后通过CPU拷贝,将数据拷贝到用户进程的缓存区。

第二行代码执行的操作:

通过CPU拷贝,将数据从用户进程中的缓冲区中拷贝到套接字发送缓冲区(SO_SNDBUF);然后通过DMA拷贝,将套接字发送缓冲区中的数据拷贝到网络设备缓冲区中。

期间总共涉及到4次拷贝和4次上下文切换。

  • DMA两次拷贝,CPU两次拷贝;
  • 从用户空间上下文切换到内核空间的磁盘1次上下文切换,然后再经过文件读取缓冲区到用户空间应用进程缓冲区1次上下文切换,接着从应用进程缓冲区到内核空间的套接字发送缓冲区1次上下文切换,最终经过内核空间中网络设备缓冲区到达用户1次上下文切换,总共是4次上下文切换。

内核缓冲区和网络设备缓冲区都属于内核空间,文件读取缓冲区和套接字发送缓冲区同样属于内核空间;只有应用进程缓冲区才属于用户空间。

5.2.4 Linux之MMap内存映射

3次拷贝,4次上下文切换。

内核空间中的磁盘上的数据通过DMA拷贝到用户进程中的缓冲区,通过MMap内存映射,实现内核空间中的内存数据与应用进程缓冲区的内存数据关联起来;然后再通过CPU拷贝,将用户空间中的应用进程中缓冲区的数据拷贝至套接字缓冲区;最终通过DMA拷贝,将套接字缓冲区中的数据拷贝到网络设备缓冲区中。

内存映射MMap,避免了从磁盘中将数据拷贝至文件缓冲区这个操作,直接将数据从磁盘缓冲区中拷贝到了应用进程缓冲区中;因为经过文件缓冲区,数据是没有做任何更改的,所以为了避免这个操作,直接通过DMA拷贝,从内核空间中的磁盘中将数据拷贝至用户空间中的应用进程中的缓冲区去。
然后磁盘缓冲区和应用进程缓冲区中的数据通过MMap内存映射关联起来,磁盘中的内存中的数据会通过切片,一片内存映射应用进程中内存中的一片内存数据,每一片内存数据都有一个起始地址和结束地址,用于标记这一块内存数据。

5.2.5 Linux之sendFile

3次或2次拷贝,取决于硬件设备是否支持;2次上下文切换。

直接避开用户空间,直接在内核空间中,通过DMA拷贝,从磁盘拷贝数据到文件缓冲区。如果硬件不支持,还需要通过CPU拷贝,将文件缓冲区中的数据拷贝到套接字缓冲区。如果硬件支持,文件缓冲区的数据不需要拷贝至套接字缓冲区,直接将套接字缓冲区中的数据通过DMA拷贝至网络设备缓冲区。

5.2.5 Linux之slice

2次拷贝,2次上下文切换

同样是避开用户空间,直接在内核空间中,使得文件缓冲区和套接字发送缓冲区公用同一块内存,之间通过管道通信(PIPE);只需要通过两次DMA拷贝,即可完成数据的传输,通过DMA拷贝将磁盘中数据拷贝到文件缓冲区,由于文件缓冲区与套接字缓冲区是通过管道通信的,所以再通过DMA将套接字缓冲区中的数据拷贝到网络设备缓冲区中。

你可能感兴趣的:(计算机网络,网络编程)