day25 【NIO 和AIO】

    技术2023-07-27  88

    1.Selector选择器

    1.多路复用的概念

    选择器Selector是NIO中的重要技术之一。它与SelectableChannel(可通过 Selector 实现多路复用的通道)联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。

    "多路复用"是指:多路复用的意思是一个Selector可以监听多个服务器端口.

    服务器端的非多路复用效果

    传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。

    如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。

    服务器端的多路复用效果

    Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

    说明:就是将通道(Channel)注册在选择器(Selector)上,然后选择器负责监听所有的通道,只要通道中的数据准备就绪好,那么选择器就会调用服务器端的一个或者多个线程来执行此通道。如果此时通道没有准备就绪,那么服务器端的一个或者多个线程就可以执行其他的任务。

    使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势

    2.Selector介绍

    ​ Selector被称为:选择器,也被称为:多路复用器,很多个Channel(通道)可以被注册到选择器上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。

    ​ 注意:

    ​ 1.在实际开发中不是线程越多越好,因为线程之间的切换对操作系统来说代价是很高的,并且每个线程也会占用一定的系统资源。

    ​ 2.Selector是一个选择器,可以用一个线程处理了之前多个线程的事务,这样就会给系统减轻负担,提高效率。

    3.Selector选择器的使用

    1.创建 Selector :

    通过调用 Selector.open() 方法创建一个 Selector。

    2.将通道注册到选择器上:

    ​ 为了能让Channel和Selector配合使用,我们需要把Channel注册到Selector上。通过ServerSocketChannel的间接父类SelectableChannel中的注册方法进行注册:

    SelectionKey register(Selector sel, int ops) 向给定的选择器注册此通道,返回一个选择键。 参数: sel:表示选择器对象 ops:表示选择器对通道的监听事件(所得键的可用操作集). 可以监听四种不同类型的事件,而且可以使用SelectionKey的四个常量表示: 1. 套接字连接--常量:SelectionKey.OP_CONNECT 2. 套接字接收--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel 在注册时只能使用此项) 3.--常量:SelectionKey.OP_READ 4.--常量:SelectionKey.OP_WRITE

    注意:

    ​ 1.对于ServerSocketChannel在注册时,只能使用OP_ACCEPT,否则抛出异常。相当于把服务器 ServerSocketChannel的accept()方法交给Selector去管理。

    ​ 2.注册的Channel 必须设置成非阻塞模式才可以,否则非阻塞IO就无法工作,就会报异常。这就意味着我们不能把一个FileChannel注册到Selector,因为FileChannel没有非阻塞模式,但是网络编程中的ServerSocketChannel是可以的。

    其实就是执行代码:ssc.configureBlocking(false);

    //1.创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //2.绑定端口号 ssc.bind(new InetSocketAddress(8888)); //3.设置非阻塞 ssc.configureBlocking(false);

    3.Selector选择器常见方法

    int select():选择一组键,其相应的通道已为 I/O 操作准备就绪。

    此方法是Selector帮服务器去等待客户端的方法

    此方法会返回一个int值,表示有几个客户端连接了服务器。

    此方法在连接到第一个客户端之前,是阻塞的状态。

    连接到客户端之后,如果在服务器端没有处理客户端请求,那么select()方法会进入非阻塞状态。

    如果在服务器端已经对连接到的客户端请求进行处理,select()方法会再次进入阻塞状态

    代码演示:

    服务器端:

    public class ServerDemo02 { public static void main(String[] args) throws IOException { //1.创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //2.绑定端口号 ssc.bind(new InetSocketAddress(8888)); //3.设置非阻塞 ssc.configureBlocking(false); //4.要把服务器的事件交给Selector去做 //创建Selector对象 Selector selector = Selector.open(); //5.把Channel注册到Selector上 //把服务器的accept()方法交给Selector去管理 /* SelectionKey register(Selector sel, int ops) 向给定的选择器注册此通道,返回一个选择键。 */ ssc.register(selector, SelectionKey.OP_ACCEPT); //循环 while(true){ System.out.println(1); //Selector现在管理着服务器,在这里等待客户端的连接 selector.select(); System.out.println(2); //处理获取到的客户端 Set<SelectionKey> set = selector.selectedKeys(); for (SelectionKey key : set) { // SelectableChannel channel = key.channel(); ServerSocketChannel channel = (ServerSocketChannel)key.channel(); SocketChannel sc = channel.accept(); } } } }

    客户端:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); //关流 sc.close(); } }

    abstract Set selectedKeys() :当客户端来连接服务器之时,Selector会把被连接的服务器对象放到Set集合中。

    服务器代码演示:

    public class ServerDemo02 { public static void main(String[] args) throws IOException { //1.创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //2.绑定端口号 ssc.bind(new InetSocketAddress(8888)); //3.设置非阻塞 ssc.configureBlocking(false); //4.要把服务器的事件交给Selector去做 //创建Selector对象 Selector selector = Selector.open(); //5.把Channel注册到Selector上 //把服务器的accept()方法交给Selector去管理 /* SelectionKey register(Selector sel, int ops) 向给定的选择器注册此通道,返回一个选择键。 */ ssc.register(selector, SelectionKey.OP_ACCEPT); //循环 //获取存在被连接的服务器对象的集合 Set<SelectionKey> set = selector.selectedKeys(); System.out.println("集合中的服务器对象个数:"+set.size()); System.out.println(1); //Selector现在管理着服务器,在这里等待客户端的连接 selector.select(); System.out.println(2); System.out.println("集合中的服务器对象个数:"+set.size()); } }

    打印结果:

    4.使用Selector选择器接收来自客户端的数据并打印服务器端

    服务器端代码:

    package com.itheima.sh.demo_18; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Set; public class ServerDemo { public static void main(String[] args) throws Exception{ //创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //绑定端口 ssc.bind(new InetSocketAddress(8888)); //设置成非阻塞 ssc.configureBlocking(false); //创建Selector Selector selector = Selector.open(); //注册 //把服务器的accept()交给选择器去管理 ssc.register(selector, SelectionKey.OP_ACCEPT); //让Selector去接受客户端 selector.select(); //把被连接到的服务器对象放到Set集合中 Set<SelectionKey> set = selector.selectedKeys(); //获取被连接的服务器对象,执行代码 /* SelectionKey:属于一个类,表示 SelectableChannel(通道) 和 Selector(选择器) 之间的注册关系。 每次向选择器注册通道时就会选择一个事件(选择键,例如SelectionKey.OP_ACCEPT)。 方法:abstract SelectableChannel channel() 返回为之创建此键的通道。 SelectableChannel属于ServerSocketChannel的父类 */ for (SelectionKey key : set) { //获取通道对象 SelectableChannel channel = key.channel(); //向下转型 ServerSocketChannel ss = (ServerSocketChannel) channel; //获取客户端 SocketChannel s = ss.accept(); //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取数据 int len = s.read(buffer); //打印数据 //buffer.array() 变为了普通数组,将普通数组转换为字符串,这里不用切换读模式 System.out.println(new String(buffer.array(),0,len)); } } }

    说明:

    1.SelectionKey:属于一个类,表示 SelectableChannel(通道) 和 Selector(选择器) 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键,例如SelectionKey.OP_ACCEPT)。 2.SelectionKey中的方法:abstract SelectableChannel channel() 返回为之创建此键的通道。 补充: SelectableChannel属于ServerSocketChannel的父类

    客户端代码:

    package com.itheima.sh.demo_18; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); //客户端发数据 //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //数组中添加数据 buffer.put("你好啊~".getBytes()); //切换读模式 buffer.flip(); //发出数据 sc.write(buffer); //关流 sc.close(); } }

    结果:

    4.Selector管理多个ServerSocketChannel

    我们已经知道Selector选择器可以管理多个通道,上面我们只是演示管理一个通道,接下来我们开始演示使用Selector选择器管理多个通道。

    1.Selector选择器的keys()方法(了解)

    上述我们已经讲解了一个selectedKeys()方法,表示将被连接的服务器对象放到Set集合中。而keys()方法表示将Selector所管理的所有的服务器对象都在这个Set集合里。

    代码演示如下:

    服务器端:

    public class ServerDemo { public static void main(String[] args) throws Exception { //创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //绑定端口 ssc.bind(new InetSocketAddress(8888)); //创建服务器对象 ServerSocketChannel ssc2 = ServerSocketChannel.open(); //绑定端口 ssc2.bind(new InetSocketAddress(9999)); //设置成非阻塞 ssc.configureBlocking(false); ssc2.configureBlocking(false); //创建Selector Selector selector = Selector.open(); //注册 //把服务器的accept()交给选择器去管理 ssc.register(selector, SelectionKey.OP_ACCEPT); ssc2.register(selector, SelectionKey.OP_ACCEPT); //获取存放所有服务器对象的Set集合 Set<SelectionKey> set2 = selector.keys(); System.out.println("set2集合存放服务器的对象个数:" + set2.size()); //把被连接到的服务器对象放到Set集合中 Set<SelectionKey> set = selector.selectedKeys(); System.out.println("set集合存放服务器的对象个数:" + set.size()); //让Selector去接受客户端 selector.select(); System.out.println("set2集合存放服务器的对象个数:" + set2.size()); System.out.println("set集合存放服务器的对象个数:" + set.size()); } }

    客户端:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); //关流 sc.close(); } }

    代码演示结果:

    2.Selector选择器管理多个通道的问题

    ​ Selector把被连接的服务器对象放在了一个Set集合中,但是使用完后并没有删除。导致在遍历集合时,遍历到了已经没用的对象,出现了异常。

    问题演示:

    服务器端:

    public class ServerDemo { public static void main(String[] args) throws Exception { //创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //绑定端口 ssc.bind(new InetSocketAddress(8888)); //创建服务器对象 ServerSocketChannel ssc2 = ServerSocketChannel.open(); //绑定端口 ssc2.bind(new InetSocketAddress(9999)); //设置成非阻塞 ssc.configureBlocking(false); ssc2.configureBlocking(false); //创建Selector Selector selector = Selector.open(); //注册 //把服务器的accept()交给选择器去管理 ssc.register(selector, SelectionKey.OP_ACCEPT); ssc2.register(selector, SelectionKey.OP_ACCEPT); while (true) { //把被连接到的服务器对象放到Set集合中 Set<SelectionKey> set = selector.selectedKeys(); System.out.println("set集合存放服务器的对象个数:" + set.size()); //让Selector去接受客户端 selector.select(); System.out.println("set集合存放服务器的对象个数:" + set.size()); //遍历集合set for (SelectionKey key : set) { //取出服务器对象 ServerSocketChannel ss = (ServerSocketChannel) key.channel(); //获取到客户端 SocketChannel s = ss.accept(); //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取数据 int len = s.read(buffer); //打印 System.out.println(new String(buffer.array(), 0, len)); } } } }

    客户端代码:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); //客户端发数据 //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //数组中添加数据 buffer.put("你好啊~".getBytes()); //切换读模式 buffer.flip(); //发出数据 sc.write(buffer); //关流 sc.close(); } }

    1)先运行服务器端:

    2)客户端先连接服务端的端口9999的通道,运行客户端:

    服务端接收到了客户端的请求数据

    3)修改客户端代码,变为连接端口号是8888的通道:

    服务器端控制台:

    报异常原因:

    问题就出现在获取selectedKeys()的集合。

    第一次的9999连接,selectedKeys()获取的集合中只有一个SelectionKey对象。

    第二次的8888连接,selectedKeys()获取的集合中有2个SelectionKey对象,一个是连接9999客户端的,另一个是连接8888客户端的。而此时应该只处理连接8888客户端的,所以在上一次处理完9999的数据后,应该将其SelectionKey对象移除。

    3.解决Selector选择器管理多个通道的问题

    解决办法:自己在使用完对象后,从集合中删除。因为遍历删除元素可能出现并发修改异常,所以要使用迭代器的方法进行删除。

    代码演示:

    public class ServerDemo { public static void main(String[] args) throws Exception { //创建服务器对象 ServerSocketChannel ssc = ServerSocketChannel.open(); //绑定端口 ssc.bind(new InetSocketAddress(8888)); //创建服务器对象 ServerSocketChannel ssc2 = ServerSocketChannel.open(); //绑定端口 ssc2.bind(new InetSocketAddress(9999)); //设置成非阻塞 ssc.configureBlocking(false); ssc2.configureBlocking(false); //创建Selector Selector selector = Selector.open(); //注册 //把服务器的accept()交给选择器去管理 ssc.register(selector, SelectionKey.OP_ACCEPT); ssc2.register(selector, SelectionKey.OP_ACCEPT); while (true) { //把被连接到的服务器对象放到Set集合中 Set<SelectionKey> set = selector.selectedKeys(); System.out.println("set集合存放服务器的对象个数:" + set.size()); //让Selector去接受客户端 selector.select(); System.out.println("set集合存放服务器的对象个数:" + set.size()); //遍历集合set // for (SelectionKey key : set) { Iterator<SelectionKey> it = set.iterator(); while (it.hasNext()) { //取出服务器对象 SelectionKey key = it.next(); ServerSocketChannel ss = (ServerSocketChannel) key.channel(); //获取到客户端 SocketChannel s = ss.accept(); //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取数据 int len = s.read(buffer); //打印 System.out.println(new String(buffer.array(), 0, len)); //使用完了这个对象就从集合中删除 it.remove(); } // } } } }

    客户端代码:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); //客户端发数据 //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //数组中添加数据 buffer.put("你好啊~".getBytes()); //切换读模式 buffer.flip(); //发出数据 sc.write(buffer); //关流 sc.close(); } }

    效果:

    2.NIO2-AIO(异步、非阻塞)

    2.1 AIO概述

    ​ AIO (Asynchronous I/O)AIO 也就是 NIO 2,异步IO的缩写。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

    ​ 同步:调用方法之后,必须要得到一个返回值。

    ​ 异步:调用方法之后,没有返回值,但是会有回调函数。回调函数指的是满足条件之后会自动执行的方法(回过 头来调用的函数,由底层调用)。

    ​ 阻塞:如果没有达到方法的目的,就一直停在这里【等待】。

    ​ 非阻塞:不管有没有达到目的,都直接【往下执行】。

    2.2 AIO同步写法【听下就可以了,用不到】

    在演示AIO同步写法之前,我们先来认识下有关AIO的常见类以及方法:

    1.主要在Java.nio.channels包下增加了下面四个异步通道:

    AsynchronousSocketChannel 异步的客户端通道**AsynchronousServerSocketChannel **异步的服务器端通道AsynchronousFileChannel 关于本地文件的异步通道AsynchronousDatagramChannel 关于udp的异步通道

    2.AIO实现同步的网络编程:

    1)在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel这个类。

    获取服务器端对象: static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。

    绑定服务器端口号

    AsynchronousServerSocketChannel bind(SocketAddress local) 将通道的套接字绑定到本地地址,并配置套接字以监听连接。

    侦听获取客户端

    abstract Future<AsynchronousSocketChannel> accept() 接受连接 说明:该方法将接收的客户端存储到Future<V>接口中,需要使用该接口中的V get() 取出客户端

    读取客户端的数据:使用客户端的异步套接字类AsynchronousSocketChanne中的方法:

    abstract Future<Integer> read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。 说明:该方法将接收的客户端的数据存储到Future<V>接口中,需要使用该接口中的V get() 取出客户端请求的 数据

    2)客户端使用的通道是AsynchronousSocketChannel这个类。

    AIO同步写法代码实现:

    服务器端:

    public class Demo{ public static void main(String[] args) throws Exception { //创建对象 AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open(); //绑定端口 assc.bind(new InetSocketAddress(8888)); //获取连接 //Future里面放的就是方法的结果 //********同步******** System.out.println("准备连接客户端"); Future<AsynchronousSocketChannel> future = assc.accept(); //Future方法需要调用get()方法获取真正的返回值 AsynchronousSocketChannel sc = future.get(); System.out.println("连接上了客户端"); //读取客户端发来的数据 ByteBuffer buffer = ByteBuffer.allocate(1024); //读取 //以前返回的是读取到的个数,真正的个数就在Future里面放着 //********同步******** System.out.println("准备读取数据"); Future<Integer> future2 = sc.read(buffer); //获取真正的返回值 Integer len = future2.get(); System.out.println("读取到了数据"); //打印 System.out.println(new String(buffer.array(),0,len)); } }

    客户端代码:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888)); Thread.sleep(10000); //客户端发数据 //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //数组中添加数据 buffer.put("你好啊~".getBytes()); //切换读模式 buffer.flip(); //发出数据 sc.write(buffer); //关流 sc.close(); } }

    2.3 AIO异步非阻塞连接

    在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数)。

    对于服务器端,我们不再使用之前同步的方法:

    abstract Future<AsynchronousSocketChannel> accept() 接受连接

    我们现在使用的是异步的方法:

    abstract <A> void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler) 接受连接 参数: attachment:表示要附加到I / O操作的对象; 可以是null handler:AIO中的事件处理是CompletionHandler<V,A>接口,这个接口定义了如下两个方法,分别在异步 操作成功和失败时被回调。 1void completed(V result, A attachment);当请求到来后,监听成功,应该做什么 参数:result参数就是和客户端直接建立关联的通道。 2void failed(Throwable exc, A attachment); 当服务器代码出现异常的时候,做什么 事情

    使用代码演示AIO异步非阻塞连接:

    服务器端:

    public class ServerDemo{ public static void main(String[] args) throws IOException { //服务器异步的连接方法 //创建对象 AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open(); //绑定端口 assc.bind(new InetSocketAddress(8000)); //【异步非阻塞】方式!!!!!连接客户端 System.out.println("11111111111"); assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override //回调函数,当成功连接了客户端之后,会自动回来调用这个方法 public void completed(AsynchronousSocketChannel result, Object attachment) { //completed是完成,如果连接成功会自动调用这个方法 System.out.println("completed"); } @Override public void failed(Throwable exc, Object attachment) { //failed是失败,如果连接失败会自动调用这个方法 } }); System.out.println("22222222222"); //写一个死循环让程序别结束(因为程序结束了无法演示效果) while(true){ } } }

    客户端代码:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000)); //客户端发数据 //创建数组 // ByteBuffer buffer = ByteBuffer.allocate(1024); // //数组中添加数据 // buffer.put("你好啊~".getBytes()); // //切换读模式 // buffer.flip(); // //发出数据 // sc.write(buffer); //关流 sc.close(); } }

    结果:

    2.4 AIO异步非阻塞读

    import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; public class ServerDemo { public static void main(String[] args) throws Exception { //创建对象 AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open(); //绑定端口 assc.bind(new InetSocketAddress(8000)); //异步非阻塞连接!!!! //第一个参数是一个附件 System.out.println(1); assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel s, Object attachment) {//attachment就是accept方法的第一个参数null System.out.println("attachment = " + attachment); //如果连接客户端成功,应该获取客户端发来的数据 //completed()的第一个参数表示的是Socket对象. System.out.println(5); //创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); System.out.println(3); //异步非阻塞读!!!!! /* <A> void read​(ByteBuffer dst, A attachment, CompletionHandler<Integer,? super A> handler) 参数: dst - 将客户端的数据存储到该字节缓冲区中 attachment - 要附加到I / O操作的对象; 可以是null handler - 完成处理程序 */ s.read(buffer, null, new CompletionHandler<Integer, Object>() { @Override //读取成功会自动调用这个方法 //completed()方法的第一个参数len是read()读取到的实际个数 public void completed(Integer len, Object attachment) { //打印数据 System.out.println(6); System.out.println(new String(buffer.array(), 0, len)); } @Override public void failed(Throwable exc, Object attachment) { } }); System.out.println(4); } @Override public void failed(Throwable exc, Object attachment) { } }); System.out.println(2); //让程序别结束写一个死循环 while (true) { } } } 执行顺序: 1 2 5 3 4 6

    客户端代码:

    public class ClientDemo { public static void main(String[] args) throws Exception{ //创建对象 SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000)); //休眠10s Thread.sleep(10000); // 客户端发数据 // 创建数组 ByteBuffer buffer = ByteBuffer.allocate(1024); //数组中添加数据 buffer.put("你好啊~".getBytes()); //切换读模式 buffer.flip(); //发出数据 sc.write(buffer); } }
    Processed: 0.012, SQL: 9