文章目录
- 重要概念
- 同步和异步
- 阻塞和非阻塞
- 1. BIO(同步阻塞IO)
- 1.1 BIO 的处理流程
- 1.2 使用示例
- 1.2.1 客户端
- 1.2.2 服务端
- 1.2.3 测试类
- 2. NIO(同步非阻塞IO)
- 2.1 NIO 的处理流程
- 2.2 使用示例
- 2.2.1 客户端
- 2.2.2 服务端
- 2.2.3 测试类
- 3. AIO(异步非阻塞IO)
- 3.1 AIO 的处理流程
- 3.2 使用示例
- 3.2.1 客户端
- 3.2.2 服务端
- 3.2.3 测试类
重要概念
同步和异步
同步
和异步
的差异如下,二者的语义重点在于执行的方式
概念 | 含义 |
| IO 时,发起 IO 的线程自己处理 IO 读写,必须要等待 IO 操作完成才往下执行(耗时的任务自己做,必须做完手头的任务才去做下一个) |
| IO 时,发起 IO 的线程将 IO 读写委托给操作系统,不需要等待 IO 完成就继续执行。委托 IO 时会将数据缓冲区地址和大小传给操作系统,当 IO 完成时线程会得到通知(耗时的任务交给别人做,自己直接去做下一任务,别人把耗时的任务做完后通知自己就行) |
阻塞和非阻塞
阻塞
和非阻塞
的概念如下,二者的语义重点在于状态
概念 | 含义 |
| 使用阻塞 IO 时,发起 IO 的线程在读写操作不可进行时,会进入阻塞状态,而不是一直占用 CPU 干等 |
| 使用非阻塞 IO 时,不管是否能读写,发起 IO 的线程会马上得到一个返回值,不需要进入阻塞状态,继续执行其他代码即可 |
1. BIO(同步阻塞IO)
1.1 BIO 的处理流程
BIO
的流程通常是服务端启动一个ServerSocket
,然后在客户端启动Socket
来连接服务端进行通信,默认情况下服务端需要对每个请求建立一个线程进行处理,而客户端发送请求后线程会等待获得响应后才继续执行
Blocking IO
的特点就是在IO执行的两个阶段,等待数据和拷贝数据,都被阻塞了。大部分的socket
接口都是阻塞型的,而所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞,直到该系统调用获得结果或者超时出错时才返回
1.2 使用示例
1.2.1 客户端
public class Client {
//默认的端口号
private static int DEFAULT_SERVER_PORT = 10086;
private static String DEFAULT_SERVER_IP = "127.0.0.1";
private static String QUIT_MSG = "q";
private static Socket socket;
public static void start() {
try {
socket = new Socket(DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT);
System.out.println("Client: start, port " + DEFAULT_SERVER_PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean send(String exp) {
if (QUIT_MSG.equals(exp)) {
System.out.println("Client: quit");
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
System.out.println("Client: send " + exp);
InputStreamReader inputStreamReader;
BufferedReader in;
PrintWriter out;
try {
inputStreamReader = new InputStreamReader(socket.getInputStream());
in = new BufferedReader(inputStreamReader);
out = new PrintWriter(socket.getOutputStream(), true);
out.println(exp);
System.out.println("Client: receive " + in.readLine());
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
1.2.2 服务端
- 服务器类
public class Server {
//默认的端口号
private static int DEFAULT_PORT = 10086;
private static ServerSocket server;
public static void start() {
start(DEFAULT_PORT);
}
public synchronized static void start(int port) {
if (server != null) return;
new Thread(() -> {
try {
// 通过构造函数创建ServerSocket,如果端口合法且空闲,服务端就监听成功
server = new ServerSocket(port);
System.out.println("Server: start, port " + port);
// 无限循环监听客户端连接,如果没有客户端接入,将阻塞在accept操作上
while (true) {
Socket socket = server.accept();
//当有新的客户端接入时,会执行下面的代码,创建一个新的线程处理这条 Socket 连接
new Thread(new ServerHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//一些必要的清理工作
if (server != null) {
System.out.println("Server: close");
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
server = null;
}
}
}, "Server").start();
}
}
- 服务器处理者
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStreamReader inputStreamReader;
BufferedReader in;
PrintWriter out;
try {
inputStreamReader = new InputStreamReader(socket.getInputStream());
in = new BufferedReader(inputStreamReader);
out = new PrintWriter(socket.getOutputStream(), true);
String exp;
while (null != (exp = in.readLine())) {
// 通过BufferedReader读取一行,如果已经读到输入流尾部或者对端关闭,返回null,退出循环
System.out.println("Server: receive " + exp);
String result = "Server: send " + exp;
out.println(result);
}
System.out.println("Server: handler quit");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != socket)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1.2.3 测试类
public class Test {
public static void main(String[] args) throws InterruptedException {
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
while (Client.send(new Scanner(System.in).nextLine()));
}
}
2. NIO(同步非阻塞IO)
NIO
本身是基于事件驱动思想来完成的,主要解决的是BIO
的大并发问题:
在使用
同步 I/O
的网络应用中,如果同时处理多个客户端请求,就必须使用多线程来处理,也就是将每一个客户端请求分配给一个线程来单独处理。这样做会带来一个问题,由于每创建一个线程就要为这个线程分配一定的内存空间,而操作系统本身资源是有限的,这样就对线程的总数构成了一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。
NIO 解决了线程创建过多的问题,不过在 NIO 的处理方式中,当一个请求进来,开启线程进行处理可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发量上来时还是会有和 BIO 一样的问题
NIO 基于 Reactor,当socket
有流可读或可写入 socket
时,操作系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的
BIO 与 NIO 比较重要的不同点:
- 使用 BIO 的时候往往会引入多线程,每个连接对应一个单独的线程;而 NIO 线程处理的都是有效连接(数据就绪),且一个线程可以分管处理多个连接上的就绪数据,节省线程资源开销
- BIO 面向流,NIO 面向缓冲区
2.1 NIO 的处理流程
当用户线程发起 I/O
请求后,会将 socket
连接及关注事件注册到服务端的selector(多路复用器,os级别线程)
上,selector
循环遍历注册其上的 socket
连接,看是否有其关注事件就绪,如果连接有数据就绪后,就通知应用程序建立线程进行数据读写,也就是实际的读写操作是由应用程序完成的,而不是操作系统
2.2 使用示例
2.2.1 客户端
- 客户端
public class Client {
private static String DEFAULT_HOST = "127.0.0.1";
private static int DEFAULT_PORT = 10086;
private static String QUIT_MSG = "q";
private static ClientHandler clientHandle;
public static void start() {
start(DEFAULT_HOST, DEFAULT_PORT);
}
public static synchronized void start(String ip, int port) {
if (clientHandle != null) {
return;
}
clientHandle = new ClientHandler(ip, port);
new Thread(clientHandle, "Client").start();
}
//向服务器发送消息
public static boolean sendMsg(String msg) throws Exception {
if (QUIT_MSG.equals(msg)) {
System.out.println("Client: exit");
clientHandle.stop();
return false;
}
clientHandle.sendMsg(msg);
return true;
}
}
- 客户端处理者
public class ClientHandler implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean started;
public ClientHandler(String ip, int port) {
this.host = ip;
this.port = port;
try {
//创建选择器
selector = Selector.open();
//打开监听通道
socketChannel = SocketChannel.open();
//如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
socketChannel.configureBlocking(false);
started = true;
System.out.println("Client: start, port " + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
started = false;
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
//循环遍历selector
while (started) {
try {
//无论是否有读写事件发生,selector每隔1s被唤醒一次
selector.select(1000);
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
//selector关闭后会自动释放里面管理的资源
if (selector != null)
try {
selector.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
System.out.println("Client: connect to server");
} else {
System.exit(1);
}
}
//读消息
if (key.isReadable()) {
//创建ByteBuffer,并开辟一个1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取请求码流,返回读取到的字节数
int readBytes = sc.read(buffer);
//读取到字节,对字节进行编解码
if (readBytes > 0) {
//将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
buffer.flip();
//根据缓冲区可读字节数创建字节数组
byte[] bytes = new byte[buffer.remaining()];
//将缓冲区可读字节数组复制到新建的数组中
buffer.get(bytes);
String result = new String(bytes, StandardCharsets.UTF_8);
System.out.println("Client: receive " + result);
} else if (readBytes < 0) {
//链路已经关闭,释放资源
key.cancel();
sc.close();
}
}
}
}
//异步发送消息
private void doWrite(SocketChannel channel, String request) throws IOException {
//将消息编码为字节数组
byte[] bytes = request.getBytes();
//根据数组容量创建ByteBuffer
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
//将字节数组复制到缓冲区
writeBuffer.put(bytes);
//flip操作
writeBuffer.flip();
//发送缓冲区的字节数组
channel.write(writeBuffer);
}
private void doConnect() throws IOException {
if (socketChannel.connect(new InetSocketAddress(host, port))) {
System.out.println("Client: already connect to Server");
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
public void sendMsg(String msg) throws Exception {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel, msg);
}
}
2.2.2 服务端
- 服务器
public class Server {
private static int DEFAULT_PORT = 10086;
private static ServerHandler serverHandle;
public static void start() {
start(DEFAULT_PORT);
}
public static synchronized void start(int port) {
if (serverHandle != null) {
return;
}
serverHandle = new ServerHandler(port);
new Thread(serverHandle, "Server").start();
}
}
- 服务端处理者
public class ServerHandler implements Runnable {
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean started;
/**
* 构造方法
*
* @param port 指定要监听的端口号
*/
public ServerHandler (int port) {
try {
//创建选择器
selector = Selector.open();
//打开监听通道
serverChannel = ServerSocketChannel.open();
//如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
serverChannel.configureBlocking(false);
//绑定端口 backlog设为1024
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
//监听客户端连接请求
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
//标记服务器已开启
started = true;
System.out.println("Server: start, port " + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
started = false;
}
@Override
public void run() {
//循环遍历selector
while (started) {
try {
//无论是否有读写事件发生,selector每隔1s被唤醒一次
selector.select(1000);
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
//selector关闭后会自动释放里面管理的资源
if (selector != null)
try {
selector.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
//处理新接入的请求消息
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//通过ServerSocketChannel的accept创建SocketChannel实例
//完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
SocketChannel sc = ssc.accept();
//设置为非阻塞的
sc.configureBlocking(false);
//注册处理读就绪事件
sc.register(selector, SelectionKey.OP_READ);
}
// 读就绪事件触发读消息
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
//创建ByteBuffer,并开辟一个1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取请求码流,返回读取到的字节数
int readBytes = sc.read(buffer);
//读取到字节,对字节进行编解码
if (readBytes > 0) {
//将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
buffer.flip();
//根据缓冲区可读字节数创建字节数组
byte[] bytes = new byte[buffer.remaining()];
//将缓冲区可读字节数组复制到新建的数组中
buffer.get(bytes);
String exp = new String(bytes, StandardCharsets.UTF_8);
System.out.println("Server: receive " + exp);
//处理数据
String result;
try {
result = "Server: send " + exp;
} catch (Exception e) {
result = "Error: " + e.getMessage();
}
//发送应答消息
doWrite(sc, result);
} else if (readBytes < 0) {
//链路已经关闭,释放资源
key.cancel();
sc.close();
}
}
}
}
//异步发送应答消息
private void doWrite(SocketChannel channel, String response) throws IOException {
//将消息编码为字节数组
byte[] bytes = response.getBytes();
//根据数组容量创建ByteBuffer
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
//将字节数组复制到缓冲区
writeBuffer.put(bytes);
//flip操作
writeBuffer.flip();
//发送缓冲区的字节数组
channel.write(writeBuffer);
}
}
2.2.3 测试类
public class Test {
public static void main(String[] args) throws InterruptedException {
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
while (Client.send(new Scanner(System.in).nextLine()));
}
}
3. AIO(异步非阻塞IO)
3.1 AIO 的处理流程
与NIO
不同,AIO 基于 Proactor,进行读写操作时,只须直接调用 API
的read/write
方法。这两种方法都是异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read
方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write
方法传递的流写入完毕时,也会主动通知应用程序。 即可以理解为,read/write
方法都是异步的,完成后会主动调用数据接收方的回调函数
3.2 使用示例
3.2.1 客户端
- 客户端
public class Client {
private static String DEFAULT_HOST = "127.0.0.1";
private static int DEFAULT_PORT = 10086;
private static String QUIT_MSG = "q";
private static AsyncClientHandler clientHandle;
public static void start() {
start(DEFAULT_HOST, DEFAULT_PORT);
}
public static synchronized void start(String ip, int port) {
if (clientHandle != null) {
return;
}
clientHandle = new AsyncClientHandler(ip, port);
new Thread(clientHandle, "Client").start();
}
//向服务器发送消息
public static boolean sendMsg(String msg) {
if (QUIT_MSG.equals(msg)) {
clientHandle.stop();
return false;
}
clientHandle.sendMsg(msg);
return true;
}
}
- 客户端处理者
public class AsyncClientHandler implements CompletionHandler<Void, AsyncClientHandler>, Runnable {
private AsynchronousSocketChannel clientChannel;
private String host;
private int port;
private CountDownLatch latch;
public AsyncClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
//创建异步的客户端通道
clientChannel = AsynchronousSocketChannel.open();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//创建CountDownLatch等待
latch = new CountDownLatch(1);
//发起异步连接操作,回调接口就是这个类本身,如果连接成功会回调completed方法
clientChannel.connect(new InetSocketAddress(host, port), this, this);
try {
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
clientChannel.close();
System.out.println("Client: close");
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop() {
latch.countDown();
}
//连接服务器成功
//意味着TCP三次握手完成
@Override
public void completed(Void result, AsyncClientHandler attachment) {
System.out.println("Client: connect to server");
}
//连接服务器失败
@Override
public void failed(Throwable exc, AsyncClientHandler attachment) {
System.err.println("Client: connect to server fail");
exc.printStackTrace();
try {
clientChannel.close();
latch.countDown();
} catch (IOException e) {
e.printStackTrace();
}
}
//向服务器发送消息
public void sendMsg(String msg) {
byte[] req = msg.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
//异步写
clientChannel.write(writeBuffer, writeBuffer, new ClientWriteHandler(clientChannel, latch));
}
}
- 客户端写处理
public class ClientWriteHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public ClientWriteHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) {
this.clientChannel = clientChannel;
this.latch = latch;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
//完成全部数据的写入
if (buffer.hasRemaining()) {
clientChannel.write(buffer, buffer, this);
} else {
//读取数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
clientChannel.read(readBuffer, readBuffer, new ClientReadHandler(clientChannel, latch));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Client: send fail");
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}
- 客户端读处理
public class ClientReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public ClientReadHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) {
this.clientChannel = clientChannel;
this.latch = latch;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body;
body = new String(bytes, StandardCharsets.UTF_8);
System.out.println("Client: receive " + body);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Client: receive fail");
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}
}
3.2.2 服务端
- 服务器
public class Server {
private static int DEFAULT_PORT = 10086;
private static AsyncServerHandler SERVER_HANDLER;
public static void start() {
start(DEFAULT_PORT);
}
public static synchronized void start(int port) {
if (SERVER_HANDLER != null) {
System.out.println("Server: already start");
return;
}
SERVER_HANDLER = new AsyncServerHandler(port);
new Thread(SERVER_HANDLER, "Server").start();
}
}
- 服务端处理者
public class AsyncServerHandler implements Runnable {
public CountDownLatch latch;
public AsynchronousServerSocketChannel serverSocketChannel;
public AsyncServerHandler(int port) {
try {
//创建服务端通道
serverSocketChannel = AsynchronousServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(port));
System.out.println("Server: start, port " + port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
// CountDownLatch 初始化
// 作用:在完成一组正在执行的操作之前,允许当前的线程一直阻塞,防止服务端执行完成后退出, 也可以使用 while(true) + sleep
latch = new CountDownLatch(1);
//用于接收客户端的连接
serverSocketChannel.accept(this, new AcceptHandler());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 服务端接受连接处理
public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncServerHandler> {
private static int CLIENT_COUNT = 0;
@Override
public void completed(AsynchronousSocketChannel channel, AsyncServerHandler attachment) {
//继续接受其他客户端的请求
CLIENT_COUNT++;
System.out.println("Server: client num " + CLIENT_COUNT);
attachment.serverSocketChannel.accept(attachment, this);
//创建新的Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//异步读 第三个参数为接收消息回调的业务Handler
channel.read(buffer, buffer, new ReadHandler(channel));
}
@Override
public void failed(Throwable exc, AsyncServerHandler serverHandler) {
exc.printStackTrace();
serverHandler.latch.countDown();
}
}
- 服务端读处理
public class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
//用于读取半包消息和发送应答
private AsynchronousSocketChannel channel;
public ReadHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
//读取到消息后的处理
@Override
public void completed(Integer result, ByteBuffer buffer) {
//flip操作
buffer.flip();
byte[] message = new byte[buffer.remaining()];
buffer.get(message);
String expr = new String(message, StandardCharsets.UTF_8);
System.out.println("Server: receive " + expr);
String resultMsg;
try {
resultMsg = "Server: send " + expr;
} catch (Exception e) {
resultMsg = "Error:" + e.getMessage();
}
//向客户端发送消息
doWrite(resultMsg);
}
//发送消息
private void doWrite(String result) {
byte[] bytes = result.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
//异步写数据 参数与前面的read一样
channel.write(writeBuffer, writeBuffer, new WriteHandler(channel));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 服务端写处理
public class WriteHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel;
public WriteHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
//如果没有发送完,就继续发送直到完成
if (buffer.hasRemaining()) {
channel.write(buffer, buffer, this);
} else {
//发送完毕,准备读取数据
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//异步读 第三个参数为接收消息回调的业务 Handler
channel.read(readBuffer, readBuffer, new ReadHandler(channel));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException ignored) {
}
}
}
3.2.3 测试类
public class Test {
public static void main(String[] args) throws Exception {
//运行服务器
Server.start();
//避免客户端先于服务器启动前执行代码
Thread.sleep(100);
//运行客户端
Client.start();
System.out.println("please input:");
Scanner scanner = new Scanner(System.in);
while (Client.sendMsg(scanner.nextLine()));
scanner.close();
}
}