java-nio网络编程

Posted 野生java研究僧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java-nio网络编程相关的知识,希望对你有一定的参考价值。

1.java nio知识体系概述

Java NIO(New IO 或 Non Blocking IO)是从 Java 1.4 版本开始引入的一个新的IO API,可以替代标准的 Java IO API。NIO 支持面向缓冲区的、基于通道的 IO 操作。NIO 将以更加高效的方式进行文件的读写操作。

  • 阻塞IO:

通常在进行同步 I/O 操作时,如果读取数据,代码会阻塞到有读取的数据为止,同样,写入调用将会阻塞至有数据能够写入。传统的 Server/Client 模式会基于 TPR(Thread per Request)服务端会为每个请求建立一个线程,由于该线程单独负责处理一个客户端请求,这种模式带来的问题就是线程的数量会急剧的增加,大量的线程会增大服务器的开销,大多数时候我们为了避免这个问题,都采用了 线程池模型 ,并设置线程池的最大数量,这又带来了新的问题,加锁线程中有100个线程,而有100个客户端都在同时下载文件,那么会导致第101个客户端的请求无法进行及时处理,即便这个第101个客户端请求只请求几KB大小的页面,也无法请求成功。传统的Server/Cilent 工作模式如下图所示:

  • 非阻塞(NIO):

NIO中的非阻塞I/O采用了基于 Reactor 模式的工作方式,I/O调用不会被阻塞,相反是注册刚兴趣的特定I/O事件,如可读数据到达,新的套接字连接等等,在发生特定事件时,系统在通知我们,NIO中实现非阻塞I/O的核心对象就是Selector,Selector 就是注册各种I/O的事件的地方,而我们所注册的事件发生时,就是这个对象告诉我们,如下图如示:


从图中可以看出,当有读或写等任何注册的事件发生时,可以从中获取到对应的 SelectionKey ,同时从 SelectionKey 中找到发生的事件和该事件发生的具体SelectableChannel,可获取到客户端发生过来的数据。

非阻塞指的是IO事件本身不阻塞,但是在获取IO事件的select()方法是需要阻塞等待的,区别是在于阻塞的IO会阻塞在IO操作上NIO阻塞在事件获取上,没有事件就没有IO,从高层次上看IO就没有阻塞了,也就是说IO已经发生我们才评估IO是否阻塞,但是select()阻塞的时候IO还没有发生,还有有发生那么久说不上IO阻塞了,NIO的本质是延迟IO的操作,到真正发生IO的时候才发生阻塞,而不是像之前一样,IO流一打开久一直等待IO操作。

java nio 由着几个核心部分组成:Channel(通道),Buffer(缓冲区),Selector(选择器)

  • Channel:

Channel 可以翻译为通道的意思,Channel和IO中的Stream(流)是差不多一个等级的,只不过Stream是单向的,而Channel是双向的。

例如:之前需要用 InputStream(输入流),OutputStream(输出流)才能完成文件的读写,但是现在只需要Channel即可,Channel是双向通道,既可读,也可写。

nio中的Channel主要的实现类有:

  • Buffer:
  • NIO中关键Buffer实现有:
  • Selector:

Selector运行单线程,处理多个Channel,如果你的应用打开了多个通道,每个连接流量都很低,使用Selector就会很方便,例如在一个聊天服务器中,要使用Selector,得向Selector注册Channel,然后调用他的select()方法,这个犯法会一直阻塞到某个注册的通道有事件就绪,一旦这个方法返回,线程就可以处理这些事情,事件的例子如:有新的连接进来,数据接收等等。

2.Channel

Channel是一个通道,它可以读取和写入数据 ,它就像是水管,网络数据可以通过Channel进行读写,通道流和流的不同之处就是在于,流是单线的,通道流是双向的,流只能在一个方向上移动(一个必须是InputStream或OutputStream的子类),而且Channel可以同时读写,因为Channel是全双工的,它可以比流更好的映射底层操作系统的API。

NIO中通过Channel封装对数据的操作,通过Channel 我们可以操作数据源,但又不必关心数据源的物理结构,这个数据源是多种的,比如说:文件,也可以是网络Socket。在大多数应用中,Channel与文件描述符合或者Socket一 一 对应的,Channel 用在字节缓冲区和位于通道的另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。

Channel接口源码:


package java.nio.channels;

import java.io.IOException;
import java.io.Closeable;


/**
 * A nexus for I/O operations.
 *
 * <p> A channel represents an open connection to an entity such as a hardware
 * device, a file, a network socket, or a program component that is capable of
 * performing one or more distinct I/O operations, for example reading or
 * writing.
 *
 * <p> A channel is either open or closed.  A channel is open upon creation,
 * and once closed it remains closed.  Once a channel is closed, any attempt to
 * invoke an I/O operation upon it will cause a {@link ClosedChannelException}
 * to be thrown.  Whether or not a channel is open may be tested by invoking
 * its {@link #isOpen isOpen} method.
 *
 * <p> Channels are, in general, intended to be safe for multithreaded access
 * as described in the specifications of the interfaces and classes that extend
 * and implement this interface.
 *
 *
 * @author Mark Reinhold
 * @author JSR-51 Expert Group
 * @since 1.4
 */

public interface Channel extends Closeable {

    /**
     * Tells whether or not this channel is open.
     *
     * @return <tt>true</tt> if, and only if, this channel is open
     */
    public boolean isOpen();

    /**
     * Closes this channel.
     *
     * <p> After a channel is closed, any further attempt to invoke I/O
     * operations upon it will cause a {@link ClosedChannelException} to be
     * thrown.
     *
     * <p> If this channel is already closed then invoking this method has no
     * effect.
     *
     * <p> This method may be invoked at any time.  If some other thread has
     * already invoked it, however, then another invocation will block until
     * the first invocation is complete, after which it will return without
     * effect. </p>
     *
     * @throws  IOException  If an I/O error occurs
     */
    public void close() throws IOException;

}

与缓冲区不同,通道API主要由接口指定,不同的操作系统通道实现(Channel Implementation)会有根本上的差异,所以通道API 仅仅描述了可以做什么,因此很自然地,通道实现经常使用操作系统的本地代码,通道接口允许你以一种受控且可移植的方式访问底层的I/O服务。

Channel是一个对象,可以通过它读写数据,拿NIO和IO做个对比,通道像是流,所有的数据都通过Buffer对象来处理,你不能将字节直接写入通道中,也不能直接从通道中读取字节。你应该把数据写入字节缓冲区,然后再写入到通道流中去。

  1. FileChannel 从文件中读写数据。
  2. DatagramChannel 能通过 UDP 读写网络中的数据。
  3. SocketChannel 能通过 TCP 读写网络中的数据。
  4. ServerSocketChannel 可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel。正如你所看到的,这些通道涵盖了 UDP 和 TCP 网络 IO,以及文件 IO

2.1 FileChannel 介绍和示例

FileChannel 类可以实现常用的 read,write 以及 scatter/gather 操作,同时它也提
供了很多专用于文件的新方法。这些方法中的许多都是我们所熟悉的文件操作。

abstract voidforce(boolean metaData) 强制将此通道文件的任何更新写入包含该通道的存储设备。
FileLocklock() 获取此通道文件的排他锁。
abstract FileLocklock(long position, long size, boolean shared)获取此通道文件的给定区域的锁定。
abstract MappedByteBuffermap(FileChannel.MapMode mode, long position, long size) 将此频道文件的区域直接映射到内存中。
static FileChannelopen(Path path, OpenOption... options) 打开或创建文件,返回文件通道以访问该文件。
static FileChannelopen(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) 打开或创建文件,返回文件通道以访问该文件。
abstract longposition() 返回此频道的文件位置。
abstract FileChannelposition(long newPosition) 设置此通道的文件位置。
abstract intread(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
longread(ByteBuffer[] dsts) 从该通道读取到给定缓冲区的字节序列。
abstract longread(ByteBuffer[] dsts, int offset, int length)` 从该通道读取字节序列到给定缓冲区的子序列中。
abstract intread(ByteBuffer dst, long position) 从给定的文件位置开始,从该通道读取一个字节序列到给定的缓冲区。
abstract longsize() 返回此通道文件的当前大小。
abstract longtransferFrom(ReadableByteChannel src, long position, long count)从给定的可读字节通道将字节传输到该通道的文件中。
abstract longtransferTo(long position, long count, WritableByteChannel target)< 将该通道文件的字节传输到给定的可写字节通道。
abstract FileChanneltruncate(long size) 将此频道的文件截断为给定大小。
FileLocktryLock() 尝试获取此频道文件的排他锁。
abstract FileLocktryLock(long position, long size, boolean shared) 尝试获取此通道文件的给定区域的锁定。
abstract intwrite(ByteBuffer src) 从给定的缓冲区向该通道写入一个字节序列。
longwrite(ByteBuffer[] srcs)从给定的缓冲区向该通道写入一系列字节。
abstract longwrite(ByteBuffer[] srcs, int offset, int length)` 从给定缓冲区的子序列将一个字节序列写入该通道。
abstract intwrite(ByteBuffer src, long position) 从给定的缓冲区向给定的文件位置开始,向该通道写入一个字节序列。

使用FileChannel完成文件的读操作:

Buffer 通常的操作

  1. 将数据写入缓冲区
  2. 调用 buffer.flip() 反转读写模式
  3. 从缓冲区读取数据
  4. 调用 buffer.clear() 或 buffer.compact() 清除缓冲区内容
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-03-15:10
 * @Version:1.0
 * @Description: FileChannel io流通道完成文件的读取
 */
public class FileChannelRead {
    public static void main(String[] args) throws Exception {
        //获取到 FileChannel 对象
        RandomAccessFile aFile = new RandomAccessFile("web/nio/FileChannelDemo.txt", "rw");
        FileChannel channel = aFile.getChannel();
        // 得到缓冲数组
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 开始读取
        int res = channel.read(buf);
        while (res!=-1){
            System.out.println("读取: " + res);
            buf.flip();
            // 判断是否还有未读取的内容
            while (buf.hasRemaining()){
                System.out.print((char) buf.get());

            }
            // 清楚缓冲区的内容
            buf.clear();
            res=channel.read(buf);
        }
        aFile.close();
        System.out.println("  read finish");
    }
}

1.打开 FileChannel
在使用 FileChannel 之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个 InputStream、OutputStream RandomAccessFile 然后通过 getChannel() 方法来获取一个 FileChannel 实例。下面是通过 RandomAccessFile

打开 FileChannel 的示例:

 //获取到 FileChannel 对象
RandomAccessFile aFile = new RandomAccessFile("web/nio/FileChannelDemo.txt", "rw");
 FileChannel channel = aFile.getChannel();

2.从 FileChannel 读取数据

首先,分配一个 Buffer。从 FileChannel 中读取的数据将被读到 Buffer 中。然后,调用FileChannel.read()方法。该方法将数据从 FileChannel 读取到 Buffer 中。read()方法返回的 int 值表示了有多少字节被读到了 Buffer 中。如果返回-1,表示到了文件末尾

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

3.向 FileChannel 写数据

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-03-15:43
 * @Version:1.0
 * @Description: FileChannel io流通道完成文件的写入
 * 将从文件开头第5个字节后的内容删除掉: channel.truncate(4);
 *
 */
public class FileChannelWrite {
    public static void main(String[] args) throws Exception{
        RandomAccessFile aFile = new RandomAccessFile("web/nio/FileChannelDemo.txt", "rw");
        FileChannel channel = aFile.getChannel();
        // 获取到文件字节数,然后从最大长度开始写入,达到文件追加的方式进行写入
        long size = channel.size();
        channel.position(size);
        byte[] data = size==0?"hello world".getBytes():"\\nhello world".getBytes();
        ByteBuffer buf = ByteBuffer.allocate(1024);
        buf.clear();
        buf.put(data);
        buf.flip();

      while (buf.hasRemaining()){
          channel.write(buf);
      }
      // 将通道中的数据信息强制写入到磁盘中,true表示连同文件权限信息也写入
      channel.force(true);
      channel.close();

    }
}

注意 FileChannel.write()是在 while 循环中调用的。因为无法保证 write()方法一次能向 FileChannel 写入多少字节,因此需要重复调用 write()方法,直到 Buffer 中已经没有尚未写入通道的字节

4.关闭 FileChannel

用完 FileChannel 后必须将其关闭。如:

 fileChannel.close();

5.FileChannel 的 position 方法

有时可能需要在 FileChannel 的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取 FileChannel 的当前位置也可以通过调用 position(long pos)方法设置 FileChannel 的当前位置
这里有两个例子:

long pos = channel.position();
channel.position(pos +12);

如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1(文件结束标志)。

如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并
写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。

6.FileChannel 的 size 方法
FileChannel 实例的 size()方法将返回该实例所关联文件的大小。如:
long fileSize = channel.size();

7.FileChannel 的 truncate 方法
可以使用 FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。
如:channel.truncate(1024); 这个例子截取文件的前 1024 个字节。

8.FileChannel 的 force 方

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到 FileChannel 里的数据一定会即时写到磁盘上。要保证这一点,需要调用 force()方法。force()方法有一个 boolean 类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。

9.FileChannel 的 transferTo 和 transferFrom 方法
通道之间的数据传输:如果两个通道中有一个是 FileChannel,那你可以直接将数据从一个 channel 传输到另外一个 channel。

  • transferFrom()方法
    FileChannel 的 transferFrom() 方法可以将数据从源通道传输到 FileChannel中(译者注:这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个 FileChannel 完成文件间的复制的例子:
/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-03-15:43
 * @Version:1.0
 * @Description: 使用transferFrom完成文件复制  将copy1.txt文件中的内容复制到copy2.txt
 *
 *
 */
public class FileChannelWrite2 {
    public static void main(String[] args) throws Exception{

        RandomAccessFile aFile1 = new RandomAccessFile("web/nio/copy1.txt", "rw");
        FileChannel channel1 = aFile1.getChannel();

        RandomAccessFile aFile2 = new RandomAccessFile("web/nio/copy2.txt", "rw");
        FileChannel channel2 = aFile2.getChannel();

              long position=0;
              long fileSize = channel1.size();
           
              channel2.transferFrom(channel1,0,fileSize);
              aFile1.close();
              aFile2.close();
              System.out.println("copy over");

    }
}

方法的输入参数 position 表示从 position 处开始向目标文件写入数据,count 表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。此外要注意,在 SoketChannel 的实现中,SocketChannel 只会传输此刻准备好的数据(可能不足 count 字节)。因此,SocketChannel 可能不会将请求的所有数据(count 个字节)全部传输到 FileChannel 中。

transferTo()方法
transferTo()方法将数据从 FileChannel 传输到其他的 channel 中。
下面是一个 transferTo()方法的例子:

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-10-03-15:43
 * @Version:1.0
 * @Description: 使用transferFrom完成文件复制  将copy1.txt文件中的内容复制到copy2.txt
 *
 *
 */
public class FileChannelWrite2 {
    public static void main(String[] args) throws Exception{

        RandomAccessFile aFile1 = new RandomAccessFile("web/nio/copy1.txt", "rw");
        FileChannel channel1 = aFile1.getChannel();

        RandomAccessFile aFile2 = new RandomAccessFile("web/nio/copy2.txt", "rw");
        FileChannel channel2 = aFile2.getChannel();

              long position=0;
              long fileSize = channel1.size();
             
              channel1.transferTo(0,fileSize,channel2);
     
              aFile1.close();
              aFile2.close();
              System.out.println("copy over");

    }
}

2.2 Socket 通道

Modifier and TypeMethod and Description
voidbind(SocketAddress bindpoint) 将套接字绑定到本地地址。
voidclose() 关闭此套接字。
voidconnect(SocketAddress endpoint) 将此套接字连接到服务器。
voidconnect(SocketAddress endpoint, int timeout) 将此套接字连接到具有指定超时值的服务器。
SocketChannelgetChannel() 返回与此套接字相关联的唯一的SocketChannel对象(如果有)。
InetAddressgetInetAddress() 返回套接字所连接的地址。
InputStreamgetInputStream() 返回此套接字的输入流。
booleangetKeepAlive() 测试是否启用了 SO_KEEPALIVE
InetAddressgetLocalAddress() 获取套接字所绑定的本地地址。
intgetLocalPort() 返回此套接字绑定到的本地端口号。
SocketAddressgetLocalSocketAddress() 返回此套接字绑定到的端点的地址。
booleangetOOBInline() 测试是否启用了 SO_OOBINLINE
OutputStreamgetOutputStream() 返回此套接字的输出流。
intgetPort() 返回此套接字连接到的远程端口号。
intgetReceiveBufferSize() 获取这个 SocketSO_RCVBUF选项的值,即平台在此 Socket上输入的缓冲区大小。
SocketAddressgetRemoteSocketAddress() 返回此套接字连接,或端点的地址 null如果是未连接。
booleangetReuseAddress() 测试是否启用了 SO_REUSEADDR
intgetSendBufferSize() 获取此 SocketSO_SNDBUF选项的值,即该平台在此 Socket上输出使用的缓冲区大小。
intgetSoLinger() SO_LINGER退货设置。
intgetSoTimeout() SO_TIMEOUT退货设置。
booleangetTcpNoDelay() 测试是否启用了 TCP_NODELAY
intgetTrafficClass() 在从此Socket发送的数据包的IP头中获取流量类或服务类型
booleanisBound() 返回套接字的绑定状态。
booleanisClosed() 返回套接字的关闭状态。
booleanisConnected() 返回套接字的连接状态。
booleanisInputShutdown() 返回套接字连接的一半是否关闭。
booleanisOutputShutdown() 返回套接字连接的写半是否关闭。
voidsendUrgentData(int data) 在套接字上发送一个字节的紧急数据。
voidsetKeepAlive(boolean on) 启用/禁用 SO_KEEPALIVE
voidsetOOBInline(boolean on) 启用/禁用 SO_OOBINLINE (接收TCP紧急数据)默认情况下,此选项被禁用,并且在套接字上接收的TCP紧急数据被静默地丢弃。
voidsetPerformancePreferences(int connectionTime, int latency, int bandwidth) 设置此套接字的性能首选项。
voidsetReceiveBufferSize(int size) 设置 SO_RCVBUF选项为这个指定的值 Socket
voidsetReuseAddress(boolean on) 启用/禁用 SO_REUSEADDR套接字选项。
voidsetSendBufferSize(int size) 设置 SO_SNDBUF选项为这个指定的值 Socket
static voidsetSocketImplFactory(SocketImplFactory fac) 设置应用程序的客户端套接字实现工厂。
voidsetSoLinger(boolean on, int linger) 启用/禁用 SO_LINGER ,具有指定的逗留时间(以秒为单位)。
voidsetSoTimeout(int timeout) 启用/禁用 指定超时的 SO_TIMEOUT(以毫秒为单位)。
voidsetTcpNoDelay(boolean on) 启用/禁用 TCP_NODELAY (禁用/启用Nagle的算法)。
voidsetTrafficClass(int tc) 在从此Socket发送的数据包的IP头中设置流量类或服务类型字节。
voidshutdownInput() 将此套接字的输入流放置在“流的末尾”。
voidshutdownOutput() 禁用此套接字的输出流。
StringtoString() 将此套接字转换为 String
  • 新的 socket 通道类可以运行非阻塞模式并且是可选择的,可以激活大程序(如网络服务器和中间件组件)巨大的可伸缩性和灵活性。本节中我们会看到,再也没有为每个 socket连接使用一个线程的必要了,也避免了管理大量线程所需的上下文交换开销。借助新的 NIO 类,一个或几个线程就可以管理成百上千的活动 socket 连接了并且只有很少甚至可能没有性能损失。所有的 socket 通道类(DatagramChannel、SocketChannel ServerSocketChannel)都继承了 java.nio.channels.spi 包中的 AbstractSelectableChannel这意味着我们可以用一个 Selector 对象来执行socket 通道的就绪选择(readiness selection)。

  • 请注意 DatagramChannel SocketChannel 实现定义读和写功能的接口而ServerSocketChannel 不实现。ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel 对象,它本身从不传输数据。

  • 在我们具体讨论每一种 socket 通道前,你应该了解 socket 和 socket 通道之间
    的关系
    。通道是一个连接 I/O 服务导管并提供与该服务交互的方法。就某个 socket 而言,它不会再次实现与之对应的 socket 通道类中的 socket 协议 API,而 java.net 中已经存在的 socket 通道都可以被大多数协议操作重复使用。全部 socket 通道类(DatagramChannel、SocketChannel 和ServerSocketChannel)在被实例化时都会创建一个对等 socket 对象。这些是我们所熟悉的来自 java.net 的类(Socket、ServerSocketDatagramSocket)它们已经被更新以识别通道。对等 socket 可以通过调用 socket( )方法从一个通道上获取。此外,这三个 java.net 类现在都有 getChannel( )方法。

  • 要把一个 socket 通道置于非阻塞模式,我们要依靠所有 socket 通道类的公有超级类:SelectableChannel。就绪选择(readiness selection)是一种可以用来查询通道的机制,该查询可以判断通道是否准备好执行一个目标操作,如读或写。非阻塞 I/O 和可选择性是紧密相连的,那也正是管理阻塞模式的 API 代码要在SelectableChannel 超级类中定义的原因。设置或重新设置一个通道的阻塞模式是很简单的,只要调用 configureBlocking( )方法即可,传递参数值为 true 则设为阻塞模式,参数值为 false 值设为非阻塞模式。可以通过调用 isBlocking( )方法来判断某个 socket 通道当前处于哪种模式。

AbstractSelectableChannel.java 中实现的 configureBlocking()方法如下:

 /**
     * Adjusts this channel's blocking mode.
     *
     * <p> If the given blocking mode is different from the current blocking
     * mode then this method invokes the {@link #implConfigureBlocking
     * implConfigureBlocking} method, while holding the appropriate locks, in
     * order to change the mode.  </p>
     */
    public final SelectableChannel configureBlocking(boolean block)
        throws IOException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if (blocking == block)
                return this;
            if (block && haveValidKeys())
                throw new IllegalBlockingModeException();
            implConfigureBlocking(block);
            blocking = block;
        }
        return this;
    }

非阻塞 socket 通常被认为是服务端使用的,因为它们使同时管理很多 socket 通道变得更容易。但是,在客户端使用一个或几个非阻塞模式的 socket 通道也是有益处的,例如,借助非阻塞 socket 通道,GUI 程序可以专注于用户请求并且同时维护与一个或多个服务器的会话。在很多程序上,非阻塞模式都是有用的。偶尔地,我们也会需要防止 socket 通道的阻塞模式被更改。API 中有一个blockingLock( )方法,该方法会返回一个非透明的对象引用。返回的对象是通道实现修改阻塞模式时内部使用的。只有拥有此对象的锁的线程才能更改通道的阻塞模式。
下面分别介绍这 3 个通道:

  • ServerSocketChannel:
abstract SocketChannelaccept() 接受与此频道套接字的连接。
ServerSocketChannelbind(SocketAddress local) 将通道的套接字绑定到本地地址,并配置套接字以监听连接。
abstract ServerSocketChannelbind(SocketAddress local, int backlog) 将通道的套接字绑定到本地地址,并配置套接字以监听连接。
abstract SocketAddressgetLocalAddress() 返回此通道的套接字所绑定的套接字地址。
static ServerSocketChannelopen() 打开服务器插槽通道。
abstract <T> ServerSocketChannelsetOption(SocketOption<T> name, T value) 设置套接字选项的值。
abstract ServerSocketsocket() 检索与此通道关联的服务器套接字。
intvalidOps() 返回确定此频道支持的操作的操作集。

ServerSocketChannel 是一个基于通道的 socket 监听器。它同我们所熟悉的java.net.ServerSocket 执行相同的任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。

由于 ServerSocketChannel 没有 bind()方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开始监听连接。我们也是使用对等 ServerSocket 的 API 来根据需要设置其他的 socket 选项。

同 java.net.ServerSocket 一样,ServerSocketChannel 也有 accept( )方法。一旦创建了一个ServerSo

以上是关于java-nio网络编程的主要内容,如果未能解决你的问题,请参考以下文章

Java-NIO 之 Selector 与 Pipe

Java-NIO:管道 (Pipe)

Java-NIO:DatagramChannel

Java-NIO:简介

Java-NIO:通道(Channel)的原理与获取

Java-NIO:缓冲区(Buffer)的数据存取