在上一章NIO Channel篇中我们解释了它在NIO中的作用。Channel(通道)是一个核心概念,它提供了与IO设备(如文件、网络套接字等)进行数据传输的能力。下面我将从多个方面详细讲解NIO的Channel。Channel本身不直接存储数据,它通过与Buffer(缓冲区)的交互来实现数据的读写。数据首先被读入到Buffer中,然后再从Buffer中写入到Channel,或者从Channel读入到Buffer。
Channel的工作原理
  • 与Buffer的交互‌:Channel本身不直接存储数据,它通过与Buffer(缓冲区)的交互来实现数据的读写。数据首先被读入到Buffer中,然后再从Buffer中写入到Channel,或者从Channel读入到Buffer。
  • 非阻塞模式‌:Channel支持非阻塞模式,这意味着线程可以在等待IO操作完成时继续执行其他任务,而不是被阻塞。这大大提高了程序的并发处理能力。
  • Selector机制‌:NIO还提供了Selector机制,它允许单个线程同时处理多个Channel的IO事件。Selector会不断地轮询注册在其上的Channel,检查是否有IO事件发生,并通知相应的线程进行处理。
 
 

Channel(通道)的主要类型

这里不对Java NIO全部通道类型进行过多的描述,仅仅聚焦于介绍其中最为重要的四种Channel(通道)实现:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel。
对于以上四种通道,说明如下:
  1. FileChannel文件通道,用于文件的数据读写;
  1. SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写;
  1. ServerSocketChannel服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道;
  1. DatagramChannel数据报通道,用于UDP协议的数据读写。
这个四种通道,涵盖了文件IO、TCP网络、UDP IO三类基础IO读写操作。下面从通道的获取、读取、写入、关闭四个重要的操作入手,对四种通道进行简单的介绍。

FileChannel文件通道

FileChannel是专门操作文件的通道。通过FileChannel,既可以从一个文件中读取数据,也可以将数据写入到文件中。特别申明一下,FileChannel为阻塞模式,不能设置为非阻塞模式。
下面分别介绍:FileChannel的获取、读取、写入、关闭四个操作。
  1. 获取FileChannel通道
    1. 可以通过文件的输入流、输出流获取FileChannel文件通道,示例如下:
//创建一个文件输入流 FileInputStream fis = new FileInputStream(srcFile); //获取文件流的通道 FileChannel inChannel = fis.getChannel(); //创建一个文件输出流 FileOutputStream fos = new FileOutputStream(destFile); //获取文件流的通道 FileChannel outchannel = fos.getChannel(); 也可以通过RandomAccessFile文件随机访问类,获取FileChannel文件通道实例,代码如下: // 创建RandomAccessFile随机访问对象 RandomAccessFile rFile = new RandomAccessFile("filename.txt""rw"); //获取文件流的通道(可读可写) FileChannel channel = rFile.getChannel();
Java
 
2. 读取FileChannel通道 在大部分应用场景,从通道读取数据都会调用通道的int read(ByteBufferbuf)方法,它从通道读取到数据写入到ByteBuffer缓冲区,并且返回读取到的数据量。
RandomAccessFile aFile = new RandomAccessFile(fileName, "rw"); //获取通道(可读可写) FileChannel channel=aFile.getChannel(); //获取一个字节缓冲区 ByteBuffer buf = ByteBuffer.allocate(CAPACITY); int length = -1; //调用通道的read方法,读取数据并买入字节类型的缓冲区 while ((length = channel.read(buf)) != -1) { //……省略buf中的数据处理
Java
以上代码channel.read(buf)虽然是读取通道的数据,对于通道来说是读取模式,但是对于ByteBuffer缓冲区来说则是写入数据,这时,ByteBuffer缓冲区处于写入模式。
 
  1. 写入FileChannel通道
写入数据到通道,在大部分应用场景,都会调用通道的write(ByteBuffer)方法,此方法的参数是一个ByteBuffer缓冲区实例,是待写数据的来源。
write(ByteBuffer)方法的作用,是从ByteBuffer缓冲区中读取数据,然后写入到通道自身,而返回值是写入成功的字节数。
//如果buf处于写入模式(如刚写完数据),需要flip翻转buf,使其变成读取模式 buf.flip(); int outlength = 0; //调用write方法,将buf的数据写入通道 while ((outlength = outchannel.write(buf)) != 0) { System.out.println("写入的字节数:" + outlength); }
Java
在以上的outchannel.write(buf)调用中,对于入参buf实例来说,需要从其中读取数据写入到outchannel通道中,所以入参buf必须处于读取模式,不能处于写入模式。
 
  1. 关闭通道
当通道使用完成后,必须将其关闭。关闭非常简单,调用close( )方法即可。
//关闭通道 channel.close( );
Java
  1. 强制刷新到磁盘
在将缓冲区写入通道时,出于性能原因,操作系统不可能每次都实时将写入数据落地(或刷新)到磁盘,完成最终的数据保存。
如果在将缓冲数据写入通道时,需要保证数据能落地写入到磁盘,可以在写入后调用一下FileChannel的force()方法。
//强制刷新到磁盘 channel.force(true);
Java
 
案例
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(srcPath, 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(); FileInputStream fis = null; FileOutputStream fos = null; FileChannel inChannel = null; // 输入通道 FileChannel outChannel = null; // 输出通道 try { fis = new FileInputStream(srcFile); fos = new FileOutputStream(destFile); inChannel = fis.getChannel(); outChannel = fos.getChannel(); int length = -1; // 新建buf,处于写入模式 ByteBuffer buf = ByteBuffer.allocate(1024); // 从输入通道读取到buf while ((length = inChannel.read(buf)) != -1) { // buf第一次模式切换:翻转buf,从写入模式变成读取模式 buf.flip(); int outLength = 0; // 将buf写入到输出的通道 while ((outLength = outChannel.write(buf)) != 0) { System.out.println("写入的字节数:" + outLength); } // buf第二次模式切换:清除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(); } } }
Java
除了FileChannel的通道操作外,还需要注意代码执行过程中隐藏的ByteBuffer的模式切换。由于新建的ByteBuffer是写入模式,才可作为inChannel.read(ByteBuffer)方法的参数,inChannel.read(…)方法将从通道inChannel读到的数据写入到ByteBuffer。然后,需要调用缓冲区的flip方法,将ByteBuffer从写入模式切换成读取模式,才能作为outchannel.write(ByteBuffer)方法的参数,以便从ByteBuffer读取数据,最终写入到outchannel输出通道。
完成一次复制之后,在进入下一次复制前,还要进行一次缓冲区的模式切换。此时,需要将通过clear方法将Buffer切换成写入模式,才能进入下一次的复制。所以,在示例代码中,每一轮外层的while循环,都需要两次ByteBuffer模式切换:第一次模式切换时,翻转buf,变成读取模式;第二次模式切换时,清除buf,变成写入模式。
上面的示例代码,主要的目的在于:演示文件通道以及字节缓冲区的使用。然而,作为文件复制的程序来说,以上实战代码的效率不是最高的。更高效的文件复制,可以调用文件通道的transferFrom方法。

SocketChannel套接字通道

在NIO中,涉及网络连接的通道有两个:一个是SocketChannel负责连接的数据传输,另一个是ServerSocketChannel负责连接的监听。
其中,NIO中的SocketChannel传输通道,与OIO中的Socket类对应;NIO中的ServerSocketChannel监听通道,对应于OIO中的ServerSocket类。
ServerSocketChannel仅仅应用于服务器端,而SocketChannel则同时处于服务器端和客户端,所以,对应于一个连接,两端都有一个负责传输的SocketChannel传输通道。
无论是ServerSocketChannel,还是SocketChannel,都支持阻塞和非阻塞两种模式。如何进行模式的设置呢?调用configureBlocking方法,具体如下:
  1. socketChannel.configureBlocking(false)设置为非阻塞模式。
  1. socketChannel.configureBlocking(true)设置为阻塞模式。
在阻塞模式下,SocketChannel通道的connect连接、read读、write写操作,都是同步的和阻塞式的,在效率上与Java旧的OIO的面向流的阻塞式读写操作相同。因此,在这里不介绍阻塞模式下的通道的具体操作。在非阻塞模式下,通道的操作是异步、高效率的,这也是相对于传统的OIO的优势所在。下面仅仅详细介绍在非阻塞模式下通道的打开、读写和关闭操作等操作。
  1. 获取SocketChannel传输通道
在客户端,先通过SocketChannel静态方法open()获得一个套接字传输通道;然后,将socket套接字设置为非阻塞模式;最后,通过connect()实例方法,对服务器的IP和端口发起连接。
//获得一个套接字传输通道 SocketChannel socketChannel = SocketChannel.open(); //设置为非阻塞模式 socketChannel.configureBlocking(false); //对服务器的IP和端口发起连接 socketChannel.connect(new InetSocketAddress("127.0.0.1"80));
Java
非阻塞情况下,与服务器的连接可能还没有真正建立,socketChannel.connect方法就返回了,因此需要不断地自旋,检查当前是否是连接到了主机:
while(! socketChannel.finishConnect() ){ //不断地自旋、等待,或者做一些其他的事情…… }
Java
在服务器端,如何获取与客户端对应的传输套接字呢?
在连接建立的事件到来时,服务器端的ServerSocketChannel能成功地查询出这个新连接事件,并且通过调用服务器端ServerSocketChannel监听套接字的accept()方法,来获取新连接的套接字通道:
//新连接事件到来,首先通过事件,获取服务器监听通道 ServerSocketChannel server = (ServerSocketChannel) key.channel(); //获取新连接的套接字通道 SocketChannel socketChannel = server.**accept**(); //设置为非阻塞模式 socketChannel.configureBlocking(false);
Java
NIO套接字通道,主要用于非阻塞的传输场景。所以,基本上都需要调用通道的configureBlocking(false)方法,将通道从阻塞模式切换为非阻塞模式。
 
  1. 读取SocketChannel传输通道
当SocketChannel传输通道可读时,可以从SocketChannel读取数据,具体方法与前面的文件通道读取方法是相同的。调用read方法,将数据读入缓冲区ByteBuffer。
ByteBufferbuf = ByteBuffer.allocate(1024); int bytesRead = socketChannel.read(buf);
Java
在读取时,因为是异步的,因此我们必须检查read的返回值,以便判断当前是否读取到了数据。read()方法的返回值是读取的字节数,如果返回-1,那么表示读取到对方的输出结束标志,对方已经输出结束,准备关闭连接。实际上,通过read方法读数据,本身是很简单的,比较困难的是,在非阻塞模式下,如何知道通道何时是可读的呢?这就需要用到NIO的新组件——Selector通道选择器。
 
  1. 写入到SocketChannel传输通道
和前面的把数据写入到FileChannel文件通道一样,大部分应用场景都会调用通道的int
write(ByteBufferbuf)方法。
//写入前需要读取缓冲区,要求ByteBuffer是读取模式 buffer.flip(); socketChannel.write(buffer);
Java
  1. 关闭SocketChannel传输通道
在关闭SocketChannel传输通道前,如果传输通道用来写入数据,则建议调用一次shutdownOutput()终止输出方法,向对方发送一个输出的结束标志(-1)。然后调用socketChannel.close()方法,关闭套接字连接。
//调用终止输出方法,向对方发送一个输出的结束标志 socketChannel.shutdownOutput(); //关闭套接字连接 IOUtil.closeQuietly(socketChannel);
Java
 
 
服务端案例
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOServer { //通道管理器 private Selector selector; /** * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 * @param port 绑定的端口号 * @throws IOException */ public void initServer(int port) throws IOException { // 获得一个ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 设置通道为非阻塞 serverChannel.configureBlocking(false); // 将该通道对应的ServerSocket绑定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 获得一个通道管理器 this.selector = Selector.open(); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); } /** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { System.out.println("服务端启动成功!"); // 轮询访问selector while (true) { //当注册的事件到达时,方法返回;否则,该方法会一直阻塞 selector.select(); // 获得selector中选中的项的迭代器,选中的项为注册的事件 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 删除已选的key,以防重复处理 ite.remove(); // 客户端请求连接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key .channel(); // 获得和客户端连接的通道 SocketChannel channel = server.accept(); // 设置成非阻塞 channel.configureBlocking(false); //在这里可以给客户端发送信息哦 channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes())); //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ); // 获得了可读的事件 } else if (key.isReadable()) { read(key); } } } } /** * 处理读取客户端发来的信息 的事件 * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException{ // 服务器可读取消息:得到事件发生的Socket通道 SocketChannel channel = (SocketChannel) key.channel(); // 创建读取的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(10); channel.read(buffer); byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服务端收到信息:"+msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer);// 将消息回送给客户端 } /** * 启动服务端测试 * @throws IOException */ public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8000); server.listen(); } }
Java
 
客户端案例
package cn.nio; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOClient { //通道管理器 private Selector selector; /** * 获得一个Socket通道,并对该通道做一些初始化的工作 * @param ip 连接的服务器的ip * @param port 连接的服务器的端口号 * @throws IOException */ public void initClient(String ip,int port) throws IOException { // 获得一个Socket通道 SocketChannel channel = SocketChannel.open(); // 设置通道为非阻塞 channel.configureBlocking(false); // 获得一个通道管理器 this.selector = Selector.open(); // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调 //用channel.finishConnect();才能完成连接 channel.connect(new InetSocketAddress(ip,port)); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。 channel.register(selector, SelectionKey.OP_CONNECT); } /** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { // 轮询访问selector while (true) { selector.select(); // 获得selector中选中的项的迭代器 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 删除已选的key,以防重复处理 ite.remove(); // 连接事件发生 if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key .channel(); // 如果正在连接,则完成连接 if(channel.isConnectionPending()){ channel.finishConnect(); } // 设置成非阻塞 channel.configureBlocking(false); //在这里可以给服务端发送信息哦 channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes())); //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ); // 获得了可读的事件 } else if (key.isReadable()) { read(key); } } } } /** * 处理读取服务端发来的信息 的事件 * @param key * @throws IOException */ public void read(SelectionKey key) throws IOException{ //和服务端的read方法一样 } /** * 启动客户端测试 * @throws IOException */ public static void main(String[] args) throws IOException { NIOClient client = new NIOClient(); client.initClient("localhost",8000); client.listen(); } }
Java
 

DatagramChannel数据报通道

在Java中使用UDP协议传输数据,比TCP协议更加简单。和Socket套接字的TCP传输协议不同,UDP协议不是面向连接的协议。使用UDP协议时,只要知道服务器的IP和端口,就可以直接向对方发送数据。在Java NIO中,使用DatagramChannel数据报通道来处理UDP协议的数据传输。
  1. 获取DatagramChannel数据报通道
获取数据报通道的方式很简单,调用DatagramChannel类的open静态方法即可。然后调用configureBlocking(false)方法,设置成非阻塞模式。
//获取DatagramChannel数据报通道 DatagramChannel channel = DatagramChannel.open(); //设置为非阻塞模式 datagramChannel.configureBlocking(false);
Java
如果需要接收数据,还需要调用bind方法绑定一个数据报的监听端口,具体如下:
//调用bind方法绑定一个数据报的监听端口 channel.socket().bind(new InetSocketAddress(18080));
Java
  1. 读取DatagramChannel数据报通道数据
当DatagramChannel通道可读时,可以从DatagramChannel读取数据。和前面的SocketChannel读取方式不同,这里不调用read方法,而是调用receive(ByteBufferbuf)方法将数据从DatagramChannel读入,再写入到ByteBuffer缓冲区中。
//创建缓冲区 ByteBuffer buf = ByteBuffer.allocate(1024); //从DatagramChannel读入,再写入到ByteBuffer缓冲区 SocketAddress clientAddr= datagramChannel.receive(buf);
Java
通道读取receive(ByteBufferbuf)方法虽然读取了数据到buf缓冲区,但是其返回值是SocketAddress类型,表示返回发送端的连接地址(包括IP和端口)。通过receive方法读取数据非常简单,但是,在非阻塞模式下,如何知道DatagramChannel通道何时是可读的呢?和SocketChannel一样,同样需要用到NIO的新组件—Selector通道选择器
 
  1. 写入DatagramChannel数据报通道
向DatagramChannel发送数据,和向SocketChannel通道发送数据的方法也是不同的。这里不是调用write方法,而是调用send方法。示例代码如下:
//把缓冲区翻转到读取模式 buffer.flip(); //调用send方法,把数据发送到目标IP+端口 dChannel.send(buffer, new InetSocketAddress("127.0.0.1",18899)); //清空缓冲区,切换到写入模式 buffer.clear();
Java
由于UDP是面向非连接的协议,因此,在调用send方法发送数据的时候,需要指定接收方的地址(IP和端口)。 4. 关闭DatagramChannel数据报通道 这个比较简单,直接调用close()方法,即可关闭数据报通道。
//简单关闭即可 dChannel.close();
Java
 
下面是一个使用DatagramChannel数据包通到发送数据的客户端示例程序代码。其功能是:获取用户的输入数据,通过DatagramChannel数据报通道,将数据发送到远程的服务器。客户端的完整程序代码如下:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.util.Scanner; public class UDPClient { public void send() throws IOException { // 获取DatagramChannel数据报通道 DatagramChannel dChannel = DatagramChannel.open(); // 设置为非阻塞 dChannel.configureBlocking(false); // 分配缓冲区 ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE); // 创建Scanner对象,用于读取用户输入 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("127.0.0.1", 18899)); // 清空缓冲区,准备下一次读写操作 buffer.clear(); } // 关闭DatagramChannel数据报通道 dChannel.close(); } public static void main(String[] args) throws IOException { new UDPClient().send(); } }
Java
通过示例程序代码可以看出,在客户端使DatagramChannel数据报通道发送数据,比起在客户端使用套接字SocketChannel发送数据简单很多。
接下来看看在服务器端应该如何使用DatagramChannel数据包通道接收数据呢?
下面贴出服务器端通过DatagramChannel数据包通道接收数据的程序代码,可能大家目前不一定可以看懂,因为代码中用到了Selector选择器,但是不要紧,下一个小节就介绍它。
服务器端的接收功能是:通过DatagramChannel数据报通道,绑定一个服务器地址(IP+端口),接收客户端发送过来的UDP数据报。服务器端的完整代码如下:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Iterator; public class UDPServer { public void receive() throws IOException { // 获取DatagramChannel数据报通道 DatagramChannel datagramChannel = DatagramChannel.open(); // 设置为非阻塞模式 datagramChannel.configureBlocking(false); // 绑定监听地址 datagramChannel.bind(new InetSocketAddress("127.0.0.1", 18899)); 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()) { SelectionKey selectionKey = 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(); } }
Java
在服务器端,首先调用了bind方法绑定datagramChannel的监听端口。当数据到来后,调用了receive方法,从datagramChannel数据包通道接收数据,再写入到ByteBuffer缓冲区中。
 
一个超实用的Java集合处理库——collection-completeJava IO — NIO Buffer
Loading...
Honesty
Honesty
花有重开日,人无再少年.
统计
文章数:
120