• NIO Channel(通道)类


    Channel(通道)类

    NIO中一个连接就是用一个Channel(通道)来表示。大家知道,从更广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。然而,远远不止如此,除了可以对应到底层文件描述符,Java NIO的通道还可以更加细化。例如,对应不同的网络传输协议类型,在Java中都有不同的NIO Channel(通道)实现。

    1 Channel(通道)的主要类型

    这里不对纷繁复杂的Java NIO通道类型进行过多的描述,仅仅聚焦于介绍其中最为重要的四种Channel(通道)实现:FileChannelSocketChannelServerSocketChannelDatagramChannel。对于以上四种通道,说明如下:

    (1)FileChannel文件通道,用于文件的数据读写。
    (2)SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写。(3)ServerSocketChannel服务器嵌套字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道。(4)DatagramChannel数据报通道,用于UDP协议的数据读写。这个四种通道,涵盖了文件IO、TCP网络、UDP IO基础IO。下面从Channel(通道)的获取、读取、写入、关闭四个重要的操作,来对四种通道进行简单的介绍

    2 FileChannel文件通道

    可以通过文件的输入流、输出流获取FileChannel文件通道,示例如下:

            //创建一条文件输入流
            FileInputStreamfis = new FileInputStream(srcFile);
            //获取文件流的通道
            FileChannelinChannel = fis.getChannel();
    
            //创建一条文件输出流
            FileOutputStreamfos = new FileOutputStream(destFile);
            //获取文件流的通道
            FileChanneloutchannel = fos.getChannel();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    也可以通过RandomAccessFile文件随机访问类,获取FileChannel文件通道:

            // 创建RandomAccessFile随机访问对象
            RandomAccessFileaFile = new RandomAccessFile("filename.txt", "rw");
            //获取文件流的通道
            FileChannelinChannel = aFile.getChannel();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.读取FileChannel通道

    在大部分应用场景,从通道读取数据都会调用通道的int read(ByteBufferbuf)方法,它从通道读取到数据写入到ByteBuffer缓冲区,并且返回读取到的数据量。

            RandomAccessFileaFile = new RandomAccessFile(fileName, "rw");
            //获取通道
            FileChannelinChannel=aFile.getChannel();
            //获取一个字节缓冲区
            ByteBufferbuf = ByteBuffer.allocate(CAPACITY);
            int length = -1;
            //调用通道的read方法,读取数据并买入字节类型的缓冲区
            while ((length = inChannel.read(buf)) ! = -1) {
            //省略……处理读取到的buf中的数据
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    虽然对于通道来说是读取数据,但是对于ByteBuffer缓冲区来说是写入数据,这时,ByteBuffer缓冲区处于写入模式

    3.写入FileChannel通道

    写入数据到通道,在大部分应用场景,都会调用通道的int write(ByteBufferbuf)方法。此方法的参数——ByteBuffer缓冲区,是数据的来源。write方法的作用,是从ByteBuffer缓冲区中读取数据,然后写入到通道自身,而返回值是写入成功的字节数。

            //如果buf刚写完数据,需要flip翻转buf,使其变成读取模式
            buf.flip();
            int outlength = 0;
            //调用write方法,将buf的数据写入通道
            while ((outlength = outchannel.write(buf)) ! = 0) {
                    System.out.println("写入的字节数:" + outlength);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此时的ByteBuffer缓冲区要求是可读的,处于读模式下。

    4.关闭通道

    当通道使用完成后,必须将其关闭。关闭非常简单,调用close方法即可。

            //关闭通道
            channel.close();
    
    
    • 1
    • 2
    • 3

    5.强制刷新到磁盘

    在将缓冲区写入通道时,出于性能原因,操作系统不可能每次都实时将数据写入磁盘。如果需要保证写入通道的缓冲数据,最终都真正地写入磁盘,可以调用FileChannel的force()方法。

            //强制刷新到磁盘
            channel.force(true);
    
    
    • 1
    • 2
    • 3

    3 使用FileChannel完成文件复制的实践案例
    下面是一个简单的实战案例:使用文件通道复制文件。其功能是:使用FileChannel文件通道,将原文件复制一份,也就是把原文中的数据都复制到目标文件中。完整代码如下:

            package com.crazymakercircle.iodemo.fileDemos;
            //...省略import的类,具体请参见源代码工程
            public class FileNIOCopyDemo {
                public static void main(String[] args) {
                  //演示复制资源文件
                  nioCopyResouceFile();
                }
                /**
                * 复制两个资源目录下的文件
                */
                public static void nioCopyResouceFile() {
                  String sourcePath = NioDemoConfig.FILE_RESOURCE_SRC_PATH;
                  String srcPath = IOUtil.getResourcePath(sourcePath);
                  Logger.info("srcPath=" + srcPath);
    
                  String destPath = NioDemoConfig.FILE_RESOURCE_DEST_PATH;
                  String destDecodePath = IOUtil.builderResourcePath(destPath);
                  Logger.info("destDecodePath=" + destDecodePath);
    
                  nioCopyFile(srcDecodePath, destDecodePath);
                }
                /**
                * nio方式复制文件
                * @param srcPath
                * @param destPath
                */
                public static void nioCopyFile(String srcPath, String destPath) {
                  File srcFile = new File(srcPath);
                  File destFile = new File(destPath);
                  try {
                      //如果目标文件不存在,则新建
                      if (! destFile.exists()) {
                          destFile.createNewFile();
                      }
                  long startTime = System.currentTimeMillis();
                  FileInputStreamfis = null;
                  FileOutputStreamfos = null;
                  FileChannelinChannel = null;
                  FileChanneloutchannel = null;
                  try {
                      fis = new FileInputStream(srcFile);
                      fos = new FileOutputStream(destFile);
                      inChannel = fis.getChannel();
                      outchannel = fos.getChannel();
                      int length = -1;
                      ByteBufferbuf = ByteBuffer.allocate(1024);
                      //从输入通道读取到buf
                      while ((length = inChannel.read(buf)) ! = -1) {
                      //第一次切换:翻转buf,变成读取模式
                          buf.flip();
    
                          int outlength = 0;
                          //将buf写入到输出的通道
                          while ((outlength = outchannel.write(buf)) ! = 0) {
                              System.out.println("写入的字节数:" + outlength);
                          }
                          //第二次切换:清除buf,变成写入模式
                          buf.clear();
                      }
                      //强制刷新到磁盘
                      outchannel.force(true);
                    } finally {
                      //关闭所有的可关闭对象
                      IOUtil.closeQuietly(outchannel);
                      IOUtil.closeQuietly(fos);
                      IOUtil.closeQuietly(inChannel);
                      IOUtil.closeQuietly(fis);
                    }
                    long endTime = System.currentTimeMillis();
                    Logger.info("base复制毫秒数:" + (endTime - startTime));
                } catch (IOException e) {
                      e.printStackTrace();
                    }
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    特别强调一下,除了FileChannel的通道操作外,还需要注意ByteBuffer的模式切换。新建的ByteBuffer,默认是写入模式,可以作为inChannel.read(ByteBuffer)的参数。inChannel.read方法将从通道inChannel读到的数据写入到ByteBuffer。

    此后,需要调用缓冲区的flip方法,将ByteBuffer切换成读取模式,才能作为outchannel.write(ByteBuffer)方法的参数,从ByteBuffer读取数据,再写入到outchannel输出通道。

    如此,便是完成一次复制。在进入下一次复制前,还要进行一次缓冲区的模式切换。ByteBuffer数据读完之后,需要将通过clear方法切换成写入模式,才能进入下一次的复制。

    在示例代码中,外层的每一轮while循环,都需要两次模式ByteBuffer切换:第一次切换时,翻转buf,变成读取模式;第二次切换时,清除buf,变成写入模式。

    上面的示例代码,主要的目的在于:演示文件通道以及字节缓冲区的使用。作为文件复制的程序来说,实战代码的效率不是最高的。

    更高效的文件复制,可以调用文件通道的transferFrom方法。具体的代码,可以参见源代码工程中的FileNIOFastCopyDemo类,完整源文件的路径为:com.crazymakercircle.iodemo.fileDemos.FileNIOFastCopyDemo

    4 SocketChannel套接字通道

    在NIO中,涉及网络连接的通道有两个,一个是SocketChannel负责连接传输,另一个是ServerSocketChannel负责连接的监听。NIO中的SocketChannel传输通道,与OIO中的Socket类对应。NIO中的ServerSocketChannel监听通道,对应于OIO中的ServerSocket类。ServerSocketChannel应用于服务器端,而SocketChannel同时处于服务器端和客户端。换句话说,对应于一个连接,两端都有一个负责传输的SocketChannel传输通道。无论是ServerSocketChannel,还是SocketChannel,都支持阻塞和非阻塞两种模式。如何进行模式的设置呢?调用configureBlocking方法,具体如下:
    (1)socketChannel.configureBlocking(false)设置为非阻塞模式。(2)socketChannel.configureBlocking(true)设置为阻塞模式。

    在阻塞模式下,SocketChannel通道的connect连接、read读、write写操作,都是同步的和阻塞式的,在效率上与Java旧的OIO的面向流的阻塞式读写操作相同。因此,在这里不介绍阻塞模式下的通道的具体操作。在非阻塞模式下,通道的操作是异步、高效率的,这也是相对于传统的OIO的优势所在。下面详细介绍在非阻塞模式下通道的打开、读写和关闭操作等操作。

    1.获取SocketChannel传输通道

    在客户端,先通过SocketChannel静态方法open()获得一个套接字传输通道;然后,将socket套接字设置为非阻塞模式;最后,通过connect()实例方法,对服务器的IP和端口发起连接。

                  //获得一个套接字传输通道
            SocketChannelsocketChannel = SocketChannel.open();
                 //设置为非阻塞模式
            socketChannel.configureBlocking(false);
                  //对服务器的IP和端口发起连接
            socketChannel.connect(new InetSocketAddress("127.0.0.1",80));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    非阻塞情况下,与服务器的连接可能还没有真正建立,socketChannel.connect方法就返回了,因此需要不断地自旋,检查当前是否是连接到了主机:

            while(! socketChannel.finishConnect() ){
                //不断地自旋、等待,或者做一些其他的事情……
            }
    
    
    • 1
    • 2
    • 3
    • 4

    在服务器端,如何获取传输套接字呢?

    当新连接事件到来时,在服务器端的ServerSocketChannel能成功地查询出一个新连接事件,并且通过调用服务器端ServerSocketChannel监听套接字的accept()方法,来获取新连接的套接字通道:

            //新连接事件到来,首先通过事件,获取服务器监听通道
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            //获取新连接的套接字通道
            SocketChannelsocketChannel = server.accept();
            //设置为非阻塞模式
            socketChannel.configureBlocking(false);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    强调一下,NIO套接字通道,主要用于非阻塞应用场景。所以,需要调用configureBlocking(false),从阻塞模式设置为非阻塞模式。

    2.读取SocketChannel传输通道

    当SocketChannel通道可读时,可以从SocketChannel读取数据,具体方法与前面的文件通道读取方法是相同的。调用read方法,将数据读入缓冲区ByteBuffer。

            ByteBufferbuf = ByteBuffer.allocate(1024);
            int bytesRead = socketChannel.read(buf);
    
    
    • 1
    • 2
    • 3

    在读取时,因为是异步的,因此我们必须检查read的返回值,以便判断当前是否读取到了数据。read()方法的返回值,是读取的字节数。如果返回-1,那么表示读取到对方的输出结束标志,对方已经输出结束,准备关闭连接。实际上,通过read方法读数据,本身是很简单的,比较困难的是,在非阻塞模式下,如何知道通道何时是可读的呢?这就需要用到NIO的新组件——Selector通道选择器,稍后介绍。

    3.写入到SocketChannel传输通道

    和前面的把数据写入到FileChannel文件通道一样,大部分应用场景都会调用通道的int write(ByteBufferbuf)方法。

            //写入前需要读取缓冲区,要求ByteBuffer是读取模式
            buffer.flip();
            socketChannel.write(buffer);
    
    
    • 1
    • 2
    • 3
    • 4

    4.关闭SocketChannel传输通道

    在关闭SocketChannel传输通道前,如果传输通道用来写入数据,则建议调用一次shutdownOutput()终止输出方法,向对方发送一个输出的结束标志(-1)。然后调用socketChannel.close()方法,关闭套接字连接。

            //终止输出方法,向对方发送一个输出的结束标志
            socketChannel.shutdownOutput();
            //关闭套接字连接
            IOUtil.closeQuietly(socketChannel);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5 使用SocketChannel发送文件的实践案例

    下面的实践案例是使用FileChannel文件通道读取本地文件内容,然后在客户端使用SocketChannel套接字通道,把文件信息和文件内容发送到服务器。客户端的完整代码如下:

            package com.crazymakercircle.iodemo.socketDemos;
            //...
            public class NioSendClient {
                private Charset charset = Charset.forName("UTF-8");
                /**
                * 向服务器端传输文件
                */
                public void sendFile() throws Exception {
                    try {
                        String sourcePath = NioDemoConfig.SOCKET_SEND_FILE;
                        String srcPath = IOUtil.getResourcePath(sourcePath);
                        Logger.info("srcPath=" + srcPath);
                        String destFile = NioDemoConfig.SOCKET_RECEIVE_FILE;
                        Logger.info("destFile=" + destFile);
                        File file = new File(srcPath);
                        if (! file.exists()) {
                            Logger.info("文件不存在");
                            return;
                        }
                        FileChannelfileChannel = new FileInputStream(file).getChannel();
                        SocketChannelsocketChannel = SocketChannel.open();
                        socketChannel.socket().connect(
                            InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
                                              NioDemoConfig.SOCKET_SERVER_PORT));
                            socketChannel.configureBlocking(false);
                            while(! socketChannel.finishConnect() ){
                                //不断地自旋、等待,或者做一些其他的事情
                            }
                            Logger.info("Client成功连接服务器端");
                            //发送文件名称
                            ByteBufferfileNameByteBuffer = charset.encode(destFile);
                            socketChannel.write(fileNameByteBuffer);
                            //发送文件长度
                            ByteBuffer buffer = ByteBuffer.allocate
                                                (NioDemoConfig.SEND_BUFFER_SIZE);
                            buffer.putLong(file.length());
                            buffer.flip();
                            socketChannel.write(buffer);
                            buffer.clear();
                            //发送文件内容
                            Logger.info("开始传输文件");
                            int length = 0;
                            long progress = 0;
                            while ((length = fileChannel.read(buffer)) > 0) {
                                buffer.flip();
                                socketChannel.write(buffer);
                                buffer.clear();
                                progress += length;
                                Logger.info("| "+(100 * progress / file.length()) + "% |");
                            }
                            if (length == -1) {
                                IOUtil.closeQuietly(fileChannel);
                                //在SocketChannel传输通道关闭前,尽量发送一个输出结束标志到对端
                                socketChannel.shutdownOutput();
                                IOUtil.closeQuietly(socketChannel);
                            }
                            Logger.info("======== 文件传输成功 ========");
                        } catch (Exception e) {
                            e.printStackTrace();
                          }
                  }
                  public static void main(String[] args) {
                  NioSendClient client = new NioSendClient(); // 启动客户端连接
                  client.sendFile(); // 传输文件
                }
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    以上代码中的文件发送过程:首先发送目标文件名称(不带路径),然后发送文件长度,最后是发送文件内容。代码中的配置项,如服务器的IP、服务器端口、待发送的源文件名称(带路径)、远程的目标文件名称等配置信息,都是从system.properties配置文件中读取的,通过自定义的NioDemoConfig配置类来完成配置。

    在运行以上客户端的程序之前,需要先运行服务器端的程序。服务器端的类与客户端的源代码在同一个包下,类名为NioReceiveServer,具体参见源代码工程,我们稍后再详细介绍这个类。

    6 DatagramChannel数据报通道

    和Socket套接字的TCP传输协议不同,UDP协议不是面向连接的协议。使用UDP协议时,只要知道服务器的IP和端口,就可以直接向对方发送数据。在Java中使用UDP协议传输数据,比TCP协议更加简单。在Java NIO中,使用DatagramChannel数据报通道来处理UDP协议的数据传输。

    1.获取DatagramChannel数据报通道

    获取数据报通道的方式很简单,调用DatagramChannel类的open静态方法即可。然后调用configureBlocking(false)方法,设置成非阻塞模式。

            //获取DatagramChannel数据报通道
            DatagramChannel channel = DatagramChannel.open();
            //设置为非阻塞模式
            datagramChannel.configureBlocking(false);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果需要接收数据,还需要调用bind方法绑定一个数据报的监听端口,具体如下:

            //调用bind方法绑定一个数据报的监听端口
            channel.socket().bind(new InetSocketAddress(18080));
    
    
    • 1
    • 2
    • 3

    2.读取DatagramChannel数据报通道数据

    当DatagramChannel通道可读时,可以从DatagramChannel读取数据。和前面的SocketChannel的读取方式不同,不是调用read方法,而是调用receive(ByteBufferbuf)方法将数据从DatagramChannel读入,再写入到ByteBuffer缓冲区中。

            //创建缓冲区
            ByteBufferbuf = ByteBuffer.allocate(1024);
            //从DatagramChannel读入,再写入到ByteBuffer缓冲区
            SocketAddressclientAddr= datagramChannel.receive(buffer);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通道读取receive(ByteBufferbuf)方法的返回值,是SocketAddress类型,表示返回发送端的连接地址(包括IP和端口)。通过receive方法读数据非常简单,但是,在非阻塞模式下,如何知道DatagramChannel通道何时是可读的呢?和SocketChannel一样,同样需要用到NIO的新组件——Selector通道选择器,稍后介绍。

    3.写入DatagramChannel数据报通道

    向DatagramChannel发送数据,和向SocketChannel通道发送数据的方法也是不同的。这里不是调用write方法,而是调用send方法。示例代码如下:

            //把缓冲区翻转到读取模式
            buffer.flip();
            //调用send方法,把数据发送到目标IP+端口
            dChannel.send(buffer,   new  InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
                            NioDemoConfig.SOCKET_SERVER_PORT));
            //清空缓冲区,切换到写入模式
            buffer.clear();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    由于UDP是面向非连接的协议,因此,在调用send方法发送数据的时候,需要指定接收方的地址(IP和端口)。

    4.关闭DatagramChannel数据报通道

    这个比较简单,直接调用close()方法,即可关闭数据报通道。

            //简单关闭即可
            dChannel.close();
    
    
    • 1
    • 2
    • 3

    使用DatagramChannel数据包通道发送数据的实践案例
    下面是一个使用DatagramChannel数据包通到发送数据的客户端示例程序代码。其功能是:获取用户的输入数据,通过DatagramChannel数据报通道,将数据发送到远程的服务器。客户端的完整程序代码如下:

            package com.crazymakercircle.iodemo.udpDemos;
            //...
            public class UDPClient {
                public void send() throws IOException {
                  //获取DatagramChannel数据报通道
                  DatagramChanneldChannel = DatagramChannel.open();
                  //设置为非阻塞
                  dChannel.configureBlocking(false);
                  ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_
                                        SIZE);
                  Scanner scanner = new Scanner(System.in);
                  Print.tcfo("UDP客户端启动成功!");
                  Print.tcfo("请输入发送内容:");
                  while (scanner.hasNext()) {
                      String next = scanner.next();
                      buffer.put((Dateutil.getNow() + " >>" + next).getBytes());
                      buffer.flip();
                      //通过DatagramChannel数据报通道发送数据
                      dChannel.send(buffer, new InetSocketAddress(NioDemoConfig.
                                  SOCKET_SERVER_IP, NioDemoConfig.SOCKET_SERVER_PORT));
                      buffer.clear();
                  }
                  //操作四:关闭DatagramChannel数据报通道
                  dChannel.close();
                }
                public static void main(String[] args) throws IOException {
                  new UDPClient().send();
                }
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    通过示例程序代码可以看出,在客户端使DatagramChannel数据报通道发送数据,比起在客户端使用套接字SocketChannel发送数据,简单很多。

    接下来看看在服务器端应该如何使用DatagramChannel数据包通道接收数据呢?

    下面贴出服务器端通过DatagramChannel数据包通道接收数据的程序代码,可能大家目前不一定可以看懂,因为代码中用到了Selector选择器,但是不要紧,下一个小节就介绍它。

    服务器端的接收功能是:通过DatagramChannel数据报通道,绑定一个服务器地址(IP+端口),接收客户端发送过来的UDP数据报。服务器端的完整代码如下:

            package com.crazymakercircle.iodemo.udpDemos;
            //...
            public class UDPServer {
                public void receive() throws IOException {
                  //获取DatagramChannel数据报通道
                  DatagramChanneldatagramChannel = DatagramChannel.open();
                  //设置为非阻塞模式
                  datagramChannel.configureBlocking(false);
                  //绑定监听地址
                  datagramChannel.bind(new InetSocketAddress(NioDemoConfig.SOCKET
                                        _SERVER_IP, NioDemoConfig.SOCKET_SERVER_PORT));
                  Print.tcfo("UDP服务器启动成功!");
                  //开启一个通道选择器
                  Selector selector = Selector.open();
                  //将通道注册到选择器
                  datagramChannel.register(selector, SelectionKey.OP_READ);
                  //通过选择器,查询IO事件
                  while (selector.select() > 0) {
                      Iterator<SelectionKey> iterator = selector.selectedKeys()
                                                        .iterator();
                      ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND
                                          _BUFFER_SIZE);
                      //迭代IO事件
                      while (iterator.hasNext()) {
                          SelectionKeyselectionKey = iterator.next();
                          //可读事件,有数据到来
                          if (selectionKey.isReadable()) {
                              //读取DatagramChannel数据报通道的数据
                              SocketAddress client = datagramChannel.receive(buffer);
                              buffer.flip();
                              Print.tcfo(new String(buffer.array(), 0, buffer.limit()));
                              buffer.clear();
                          }
                      }
                      iterator.remove();
                  }
                  //关闭选择器和通道
                  selector.close();
                  datagramChannel.close();
                }
                public static void main(String[] args) throws IOException {
                  new UDPServer().receive();
                }
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    在服务器端,首先调用了bind方法绑定datagramChannel的监听端口。当数据到来后,调用了receive方法,从datagramChannel数据包通道接收数据,再写入到ByteBuffer缓冲区中。除此之外,在服务器端代码中,为了监控数据的到来,使用了Selector选择器。

  • 相关阅读:
    laravel5.1反序列化
    【GitHub】如果在进行PR时忘记 cloning forked repo,该如何进行修补呢
    React函数组件状态Hook—useState《进阶-对象&&数组》
    【刷题专项】— 模拟
    FineReport智能表格软件-JS实现大数据集导出(一)
    关于MongoDb查询Decimal128转BigDecimal问题
    LetCode刷题[简单题](5)按摩师,迭代出最优解(卡尔曼滤波也是类似迭代)
    Pyspider 使用带认证redis集群作为消息队列
    时间复杂度和空间复杂度
    Redis数据持久化(详解+样例)
  • 原文地址:https://blog.csdn.net/yitian881112/article/details/127624530