IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。
BIO是一个连接一个线程。
NIO(非阻塞的多路复用)是一个请求一个线程。
AIO是一个有效请求一个线程。
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)
前引 BIO
在JDK1.4之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个serverSocket,然后在客户端启动socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端线程会等待请求结束后才继续执行。
在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
一、NIO(同步非阻塞,又称 非阻塞的多路复用)
NIO都是同步的:不管是客户端还是服务端,都一直在轮询,主动获取对方消息,只要不连通,就一直轮询,直到客户端和服务端之间连接上。
BIO和NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者使用少量线程,每个连接公用一个线程
在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
1.0 概念名词
1.0.1多路复用
选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以
节省CPU资源,提高程序的运行效率。
"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。
服务器端的非多路复用效果
如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能
下降。
服务器端的非多路复用效果
使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、
高频段业务环境下有非常重要的优势
1.0.2 Selector(选择器,又称多路复用器)
Selector被称为:选择器,也被称为:多路复用器,它可以注册到很多个Channel上,监听各个Channel上发生的
事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连
接了。
有了Selector,我们就可以利用一个线程来处理所有的Channels。线程之间的切换对操作系统来说代价是很高的,
并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。
1.1 NIO服务端
package com.demo.myshiro_demo.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* nio 服务端
*
* @author lc
* @version 1.0
* @date 2022/3/11 13:36
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1. 开启服务器通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2. 设置监听的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
// 设置轮询,循环等待客户端连接
serverSocketChannel.configureBlocking(false); // true 默认阻塞(只尝试一次) false非阻塞(一直等待被连接)
while (true) {
//3. 调用accept,等到客户端连接
System.out.println("=================服务器等待客户端连接");
SocketChannel accept = serverSocketChannel.accept();
if (accept != null) {
System.out.println("==========有客户端连接了");
System.out.println("=============读取客户端发来的数据==============");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 缓存区
int len = accept.read(byteBuffer);
//可能读到的内容没有将ByteBuffer缓冲区填充满,所以我们缩小limit限定,合理利用ByteBuffer空间
byteBuffer.flip();
String message = new String(byteBuffer.array(),0,byteBuffer.limit());
System.out.println("服务端读到客户端的信息为:"+message);
System.out.println("================向客户端回写数据=============");
ByteBuffer byteBuffer1 = ByteBuffer.wrap("你好客户端".getBytes());
//向客户端回写数据
accept.write(byteBuffer1);
break; // 有客户端连接了,就不轮询了
} else {
System.out.println("=============没有客户端连接,睡了");
Thread.sleep(1000l);
System.out.println("============");
}
}
}
}
1.2 NIO客户端
package com.demo.myshiro_demo.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* nio 客户端
*
* @author lc
* @version 1.0
* @date 2022/3/11 13:47
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
while (true) {
try {
// 1.打开客户端通道
SocketChannel socketChannel = SocketChannel.open();
// 2.连接服务端
socketChannel.configureBlocking(true);
System.out.println("=============准备连接服务器");
boolean b = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
System.out.println("===========客户端服务器连接成功");
System.out.println("==============向服务器发送数据");
ByteBuffer byteBuffer = ByteBuffer.wrap("你好服务器".getBytes());
System.out.println("容量:" + byteBuffer.capacity());
System.out.println("索引:" + byteBuffer.position());
System.out.println("限定:" + byteBuffer.limit());
// 往服务器端写数据
socketChannel.write(byteBuffer);
System.out.println("=============向服务器发送数据完毕,开始读取服务器响应回来的数据==============");
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); // 缓冲区
int len = socketChannel.read(byteBuffer1);
//可能读到的内容没有将ByteBuffer缓冲区填充满,所以我们缩小limit限定,合理利用ByteBuffer空间
byteBuffer1.flip();
String message = new String(byteBuffer1.array(), 0, byteBuffer1.limit());
System.out.println("客户端读到服务端的信息为:"+message);
//关闭客户端通道
socketChannel.close();
//连接成功,结束轮询
break;
} catch (IOException e) {
System.out.println("========客户端连接服务器端失败");
e.printStackTrace();
}
}
}
}
先启动服务端,在启动客户端
1.3 实现多路复用
-
如何创建一个Selector
Selector selector = Selector.open(); -
注册Channel到Selector
channel.configureBlocking(false);
SelectionKey key =channel.register(selector,SelectionKey.OP_READ); -
四种不同类型的事件,而且可以使用SelectionKey的四个常量表示:
- 连接就绪–常量:SelectionKey.OP_CONNECT
- 接收就绪–常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key =channel.register(selector,SelectionKey.OP_READ); - 读就绪–常量:SelectionKey.OP_READ
- 写就绪–常量:SelectionKey.OP_WRITE
注意:对于ServerSocketChannel在注册时,只能使用OP_ACCEPT,否则抛出异常。
服务器端代码:
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;
import java.util.Set;
public class Server {
public static void main(String[] args) throws Exception {
//1.同时监听三个端口:7777,8888,9999
ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
serverChannel1.bind(new InetSocketAddress(7777));
serverChannel1.configureBlocking(false);
ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
serverChannel2.bind(new InetSocketAddress(8888));
serverChannel2.configureBlocking(false);
ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
serverChannel3.bind(new InetSocketAddress(9999));
serverChannel3.configureBlocking(false);
//2.获取一个选择器
Selector selector = Selector.open();
//3.注册三个通道
SelectionKey key1 = serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key2 = serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key3 = serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
//4.循环监听三个通道
while (true) {
System.out.println("等待客户端连接...");
int keyCount = selector.select();
System.out.println("连接数量:" + keyCount);
//遍历已连接的每个通道的SelectionKey
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey nextKey = it.next();
System.out.println("获取通道...");
ServerSocketChannel channel = (ServerSocketChannel)
nextKey.channel();
System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
SocketChannel socketChannel = channel.accept();
//接收数据
ByteBuffer inBuf = ByteBuffer.allocate(100);
socketChannel.read(inBuf);
inBuf.flip();
String msg = new String(inBuf.array(), 0, inBuf.limit());
System.out.println("【服务器】接收到通道【" +
channel.getLocalAddress() + "】的信息:" + msg);
}
}
}
}
客户端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws InterruptedException {
//两个线程,模拟两个客户端,分别连接服务器的7777,8888端口
new Thread(()->{
try(SocketChannel socket = SocketChannel.open()) {
System.out.println("7777客户端连接服务器......");
socket.connect(new InetSocketAddress("localhost", 7777));
System.out.println("7777客户端连接成功....");
//发送信息
ByteBuffer outBuf = ByteBuffer.allocate(100);
outBuf.put("我是客户端,连接7777端口".getBytes());
outBuf.flip();
socket.write(outBuf);
} catch (IOException e) {
System.out.println("7777异常重连");
}
}).start();
new Thread(()->{
try(SocketChannel socket = SocketChannel.open()) {
System.out.println("8888客户端连接服务器......");
socket.connect(new InetSocketAddress("localhost", 8888));
System.out.println("8888客户端连接成功....");
//发送信息
ByteBuffer outBuf = ByteBuffer.allocate(100);
outBuf.put("我是客户端,连接8888端口".getBytes());
outBuf.flip();
socket.write(outBuf);
} catch (IOException e) {
System.out.println("8888异常重连");
}
}).start();
}
}
先启动服务器,再启动客户端,打印结果:
问题就出现在获取selectedKeys()的集合。
第一次的7777连接,selectedKeys()获取的集合中只有一个SelectionKey对象。
第二次的8888连接,selectedKeys()获取的集合中有2个SelectionKey对象,一个是连接7777客户端的,另一
个是连接8888客户端的。而此时应该只处理连接8888客户端的,所以在上一次处理完7777的数据后,应该将
其SelectionKey对象移除。
更改服务器端代码:
public class Server {
public static void main(String[] args) throws Exception {
//1.同时监听三个端口:7777,8888,9999
ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
serverChannel1.bind(new InetSocketAddress(7777));
serverChannel1.configureBlocking(false);
ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
serverChannel2.bind(new InetSocketAddress(8888));
serverChannel2.configureBlocking(false);
ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
serverChannel3.bind(new InetSocketAddress(9999));
serverChannel3.configureBlocking(false);
//2.获取一个选择器
Selector selector = Selector.open();
//3.注册三个通道
SelectionKey key1 = serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key2 = serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key3 = serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
//4.循环监听三个通道
while (true) {
System.out.println("等待客户端连接...");
int keyCount = selector.select();
System.out.println("连接数量:" + keyCount);
//遍历已连接的每个通道的SelectionKey
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey nextKey = it.next();
System.out.println("获取通道...");
ServerSocketChannel channel = (ServerSocketChannel) nextKey.channel();
System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
SocketChannel socketChannel = channel.accept();
//接收数据
ByteBuffer inBuf = ByteBuffer.allocate(100);
socketChannel.read(inBuf);
inBuf.flip();
String msg = new String(inBuf.array(), 0, inBuf.limit());
System.out.println("【服务器】接收到通道【" + channel.getLocalAddress() +"】的信息:" + msg);
//移除此SelectionKey
it.remove();
}
}
}
}
二、AIO(异步非阻塞)
异步:指的是,不管是客户端还是服务端,不用轮询,先干别的事儿,什么时候通知我连接交相,我再什么时候交互
与NIO不同,当进行读写操作时,只需直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为, read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容成为AIO。
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。
服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是有OS先完成了再通知服务器应用去启动线程进行处理。
在JDK1.7中, 主要在Java.nio.channels包下增加了下
面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,
一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。在客户端使
用的通道是AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。
在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类
是CompletionHandler<V,A>,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调。
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
2.1 AIO服务端
创建AIO的服务器端:
1.相关的类:
java.nio.channels.AsynchronousServerSocketChannel:用于面向流的侦听套接字的异步通道。
2.获取对象的方法:
static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
3.成员方法:
void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)
参数:
A attachment:附件,可以传递null
CompletionHandler handler:事件处理类,用于处理accept方法监听到的ScoketChannel对象
CompletionHandler也叫回调函数,客户端请求服务器之后,会自动执行CompletionHandler接口中的方法
CompletionHandler接口中的方法:
void completed(AsynchronousSocketChannel result, A attachment);客户端连接成功执行的方法
result就是我们连接之后接收到的客户端对象
void failed(Throwable exc, A attachment);客户端连接失败执行的方法
4.实现步骤:
1.创建异步非阻塞AsynchronousServerSocketChannel服务器对象
2.使用AsynchronousServerSocketChannel对象中的方法bind绑定指定的端口号
3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端请求
public class Server {
public static void main(String[] args)throws Exception {
// 1.创建异步非阻塞AsynchronousServerSocketChannel服务器对象
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
// 2.使用AsynchronousServerSocketChannel对象中的方法bind绑定指定的端口号
channel.bind(new InetSocketAddress(8888));
System.out.println("========accept方法开始执行了========");
// 3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端请求
channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("有客户端连接成功");
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("有客户端连接失败!");
}
});
System.out.println("=============accept方法执行完毕=============");
//假如accept方法之后还有好多代码要执行,为了先不让服务器结束,我们先加个while(true){}
while(true){}
}
}
现在看不出效果,也调用不了回调函数,以为没有客户端,所以接下来写个客户端
1.启动服务端你之后,调用的accept不会等待,如果没有客户端执行,先执行accept方法外面的代码,这是非阻塞的体现
2.2 AIO客户端
创建AIO的客户端:
和客户端想关的类:
java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
获取对象的方法:
static AsynchronousSocketChannel open() 打开异步套接字通道。
成员方法:
abstract Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的ip地址和端口号
使用步骤:
1.创建异步非阻塞AsynchronousSocketChannel客户端
2.使用AsynchronousSocketChannel客户端对象中的方法connect连接服务器
public class Client {
public static void main(String[] args)throws Exception {
//1.创建异步非阻塞AsynchronousSocketChannel客户端
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel客户端对象中的方法connect连接服务器
channel.connect(new InetSocketAddress("127.0.0.1",8888));
}
}
2.3利用AIO完成异步阻塞的读写
//服务端
/*
AsynchronousSocketChannel客户端对象中的方法
Future<Integer> read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
+:Future中的方法->get();可用于获取客户端的信息
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建异步非阻塞服务器AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
//给AsynchronousServerSocketChannel对象绑定指定的端口号
channel.bind(new InetSocketAddress(8888));
System.out.println("accept方法开始执行....");
//使用AsynchronousServerSocketChannel对象中的方法accept监听请求的客户端对象
channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
//客户端连接成功执行的方法
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("有客户端连接成功!");
//获取客户端中发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
/*
result就是我们连接之后接收到的客户端对象
*/
Future<Integer> future = result.read(buffer);//是一个阻塞的方法,没读到写过来的数据,就一直阻塞
Integer len = null;
try {
len = future.get();//读取客户端中发送的信息
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
String msg = new String(buffer.array(),0,len);
System.out.println("服务器读取到客户端的信息:"+msg);
}
//客户端连接失败执行的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("有客户端连接失败!");
}
});
System.out.println("accept方法执行结束....");
while (true){}
}
}
创建AIO的客户端:
和客户端相关的类:
java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
获取对象的方法:
static AsynchronousSocketChannel open() 打开异步套接字通道。
成员方法:
Future<Void> connect(SocketAddress remote) 连接此频道。
Future<Integer> write(ByteBuffer src) 从给定的缓冲区向该通道写入一个字节序列。
Future<Integer> read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
//客户端
public class TCPClient {
public static void main(String[] args) throws IOException {
//创建异步非阻塞客户端AsynchronousSocketChannel对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//使用AsynchronousSocketChannel对象中的方法connect连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
//连接服务器成功,给服务器发送数据
ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
socketChannel.write(buffer);
}
}
2.4利用AIO完成异步非阻塞的读写
服务端:
AsynchronousSocketChannel中的方法
void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler) 是一个非阻塞的方法
参数
ByteBuffer dst - 要传输字节流缓冲区
long timeout - 完成I/O操作的最长时间
TimeUnit unit - timeout参数的时间单位
attachment - 要附加到I/O操作的对象; 可以是null
CompletionHandler handler - 消费结果的处理程序,回调函数
客户端给服务器发送数据之后,就会执行回调函数中的方法
//服务端
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.创建异步非阻塞AsynchronousServerSocketChannel服务器对象
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
//2.使用AsynchronousServerSocketChannel对象中的方法bind绑定指定的端口号
serverSocketChannel.bind(new InetSocketAddress(8888));
//3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端请求
serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
//客户端连接成功执行的方法
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("有客户端连接成功!");
//获取客户端发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
//Future<Integer> future = result.read(buffer);//是一个阻塞的方法,没有读取到客户端发送的数据,会阻塞
/*
void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler) 是一个非阻塞的方法
参数
ByteBuffer dst - 要传输字节的缓冲区
long timeout - 完成I / O操作的最长时间->
等待客户端发送数据,等客户端10秒
假如客户端经过5秒钟给服务端发数据,服务端设置的是等客户端10秒
那么5秒在我服务端容忍的10秒钟之内,那么将调用completed方法
正常读取到数据
假如客户端发数据超出了服务端设置的10秒钟,那么会自动调用
failed方法
TimeUnit unit - timeout参数的时间单位
attachment - 要附加到I / O操作的对象; 可以是null
CompletionHandler handler - 消费结果的处理程序,回调函数
客户端给服务器发送数据之后,就会执行回调函数中的方法
*/
result.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("读取客户端发送数据成功,执行的方法");
buffer.flip();//缩小limit的范围
String msg = new String(buffer.array(), 0, buffer.limit());
System.out.println("服务器读取到客户端发送的信息:"+msg);
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("读取客户端发送数据失败,执行的方法");
}
});
System.out.println("readu读取数据的方法执行完毕!");
}
//客户端连接失败执行的方法
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("有客户端连接失败!");
}
});
System.out.println("accept方法执行结束...");
while (true){}
}
}
//客户端
public class TCPClient {
public static void main(String[] args) throws IOException, InterruptedException {
//1.创建异步非阻塞AsynchronousSocketChannel客户端
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel客户端对象中的方法connect连接服务器
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
//连接服务器成功,给服务器发送数据
Thread.sleep(1000*11);
ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
socketChannel.write(buffer);
}
}