BIO的服务端通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理每次处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答通信模型。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了。
同步阻塞式I/O创建的Server源码
package testio.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* BIO服务端源码
* @version 1.0
*/
public final class ServerNormal {
// 单例的ServerSocket
private static ServerSocket server;
// 这个方法不会被大量并发访问,不太需要考虑效率,直接进行方法同步就行了
public synchronized static void start() throws IOException {
if (server != null)
return;
try {
// 通过构造函数创建ServerSocket
// 如果端口合法且空闲,服务端就监听成功
server = new ServerSocket(12345);
System.out.println("服务器已启动,端口号:" + 12345);
// 通过无线循环监听客户端连接
// 如果没有客户端接入,将阻塞在accept操作上。
while (true) {
Socket socket = server.accept();
new Thread(new ServerHandler(socket)).start();
}
} finally {
if (server != null) {
System.out.println("服务器已关闭。");
server.close();
server = null;
}
}
}
}
客户端消息处理线程ServerHandler源码:
package testio.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerHandler implements Runnable {
private Socket socket;
public ServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
String mess;
while (true) {
if ((mess = in.readLine()) == null)
break;
System.out.println("服务器收到消息:" + mess);
// 返回的消息
out.println(mess + "handler");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 一些必要的清理工作
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (out != null) {
out.close();
out = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
同步阻塞式I/O创建的Client源码:
package testio.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client {
public static void send(String mess) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket("127.0.0.1", 12345);
// 发送给服务端的数据
out = new PrintWriter(socket.getOutputStream(), true);
out.println(mess);
System.out.println("客户端发送消息:" + mess);
// 服务端返回的数据
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("服务端返回结果为:" + in.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 一下必要的清理工作
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
if (out != null) {
out.close();
out = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
服务端启动类
package testio.bio;
import java.io.IOException;
public class TestServer {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
ServerNormal.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
客服端启动类
package testio.bio;
import java.util.Random;
public class TestClicent {
// 测试主方法
public static void main(String[] args) throws InterruptedException {
// 运行客户端
new Thread(new Runnable() {
public void run() {
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < 100; i++) {
String mess = i + "";
Client.send(mess);
try {
Thread.currentThread().sleep(random.nextInt(3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
伪异步I/O编程
为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程
实现1个或多个线程处理N个客户端的模型,通常被称为“伪异步I/O模型“。
使用FixedThreadPool我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N:M的伪异步I/O模型。
但是,正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用
修改服务的端代码
package testio.bio2;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import testio.nio.ServerHandler;
/**
* BIO服务端源码
*
* @version 1.0
*/
public final class ServerNormal2 {
// 单例的ServerSocket
private static ServerSocket server;
// 线程池 懒汉式的单例
private static ExecutorService executorService = Executors.newFixedThreadPool(60);
// 这个方法不会被大量并发访问,不太需要考虑效率,直接进行方法同步就行了
public synchronized static void start() throws IOException {
if (server != null)
return;
try {
// 通过构造函数创建ServerSocket
// 如果端口合法且空闲,服务端就监听成功
server = new ServerSocket(12345);
System.out.println("服务器已启动,端口号:" + 12345);
// 通过无线循环监听客户端连接
// 如果没有客户端接入,将阻塞在accept操作上。
while (true) {
Socket socket = server.accept();
executorService.execute(new ServerHandler(socket));
}
} finally {
if (server != null) {
System.out.println("服务器已关闭。");
server.close();
server = null;
}
}
}
}
NIO理解
缓冲区 Buffer
Buffer是一个对象,包含一些要写入或者读出的数据。
在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。
具体的缓存区有这些:
ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。
通道 Channel
我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作.
Channel主要分两大类:
SelectableChannel:用户网络读写
FileChannel:用于文件操作
多路复用器 Selector
Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
ServerHandle_nio类代码
package testio.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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class ServerHandle_nio implements Runnable {
private Selector selector;
private ServerSocketChannel serverChannel;
private volatile boolean started;
public ServerHandle_nio(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("服务器已启动,端口号:" + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void run() {
// 循环遍历selector
while (started) {
try {
// 无论是否有读写事件发生,selector每隔1s被唤醒一次
selector.select(1000);
System.out.println("select server......");
// 阻塞,只有当至少一个注册的事件发生的时候才会继续.
// selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
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 expression = new String(bytes, "UTF-8");
System.out.println("服务器收到消息:" + expression);
// 处理数据
String result = null;
try {
result = expression + "_return";
} catch (Exception e) {
result = "计算错误:" + e.getMessage();
}
// 发送应答消息
doWrite(sc, result);
}
// 链路已经关闭,释放资源
else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
}
public void stop() {
started = false;
}
// 异步发送应答消息
private void doWrite(SocketChannel channel, String response) throws IOException {
// 将消息编码为字节数组
byte[] bytes = response.getBytes();
// 根据数组容量创建ByteBuffer
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
// 将字节数组复制到缓冲区
writeBuffer.put(bytes);
// 回绕缓冲区,这个方法用来将缓冲区准备为数据传出状态
writeBuffer.flip();
// 发送缓冲区的字节数组
channel.write(writeBuffer);
// ****此处不含处理“写半包”的代码
}
}
Server_nio服务端代码
package testio.nio;
public class Server_nio {
private static ServerHandle_nio serverHandle;
private static int DEFAULT_PORT = 12345;
public static void start() {
start(DEFAULT_PORT);
}
public static synchronized void start(int port) {
if (serverHandle != null)
serverHandle.stop();
serverHandle = new ServerHandle_nio(port);
new Thread(serverHandle, "Server").start();
}
}
server启动类
package testio.nio;
public class Test_server {
public static void main(String[] args) {
// 运行服务器
Server_nio.start();
}
}
-----------------分割线--------------------------------
客户端ClientHandle_nio代码
package testio.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;
import java.util.Set;
public class ClientHandle_nio implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean started;
public ClientHandle_nio(String ip, int port) {
this.host = ip;
this.port = port;
try {
// 创建选择器
selector = Selector.open();
// 打开监听通道
socketChannel = SocketChannel.open();
// 如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
socketChannel.configureBlocking(false);// 开启非阻塞模式
started = true;
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
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 = null;
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())
;
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, "UTF-8");
System.out.println("客户端收到消息:" + result);
}
// 没有读取到字节 忽略
// else if(readBytes==0);
// 链路已经关闭,释放资源
else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
}
private void doConnect() throws IOException {
if (socketChannel.connect(new InetSocketAddress(host, port)))
;
else
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
public void sendMsg(String msg) throws Exception {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel, msg);
}
// 异步发送消息
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);
// ****此处不含处理“写半包”的代码
}
public void stop() {
started = false;
}
}
Client_nio代码
package testio.nio;
public class Client_nio {
private static String DEFAULT_HOST = "127.0.0.1";
private static int DEFAULT_PORT = 12345;
private static ClientHandle_nio clientHandle;
public static void start() {
start(DEFAULT_HOST, DEFAULT_PORT);
}
public static synchronized void start(String ip, int port) {
if (clientHandle != null)
clientHandle.stop();
clientHandle = new ClientHandle_nio(ip, port);
new Thread(clientHandle, "client").start();
}
// 向服务器发送消息
public static boolean sendMsg(String msg) throws Exception {
if (msg.equals("q"))
return false;
clientHandle.sendMsg(msg);
return true;
}
}
启动客户端
package testio.nio;
import java.util.Scanner;
public class Test_client {
public static void main(String[] args) throws Exception {
// 运行客户端
Client_nio.start();
Thread.sleep(1000);
for (int i = 0; i < 10; i++) {
String handinStr = new Scanner(System.in).nextLine();
Client_nio.sendMsg(handinStr);
}
}
}
因为应答消息的发送,SocketChannel也是异步非阻塞的,所以不能保证一次能吧需要发送的数据发送完,此时就会出现写半包的问题。我们需要注册写操作,不断轮询Selector将没有发送完的消息发送完毕,然后通过Buffer的hasRemain()方法判断消息是否发送完成。
-------------------------------------------分割线-----------------------
AIO
AsyncServerHandler
package testio.aio.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.CountDownLatch;
public class AsyncServerHandler implements Runnable {
public CountDownLatch latch;
public AsynchronousServerSocketChannel channel;
public AsyncServerHandler(int port) {
try {
// 创建服务端通道
channel = AsynchronousServerSocketChannel.open();
// 绑定端口
channel.bind(new InetSocketAddress(port));
System.out.println("服务器已启动,端口号:" + port);
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
// CountDownLatch初始化
// 它的作用:在完成一组正在执行的操作之前,允许当前的现场一直阻塞
// 此处,让现场在此阻塞,防止服务端执行完成后退出
// 也可以使用while(true)+sleep
// 生成环境就不需要担心这个问题,以为服务端是不会退出的
latch = new CountDownLatch(1);
// 用于接收客户端的连接
channel.accept(this, new AcceptServerHandler());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
AcceptServerHandler
package testio.aio.server;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
//作为handler接收客户端连接
public class AcceptServerHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncServerHandler> {
public void completed(AsynchronousSocketChannel channel, AsyncServerHandler serverHandler) {
// 继续接受其他客户端的请求
Server_aio.clientCount++;
System.out.println("连接的客户端数:" + Server_aio.clientCount);
serverHandler.channel.accept(serverHandler, this);
// 创建新的Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 异步读 第三个参数为接收消息回调的业务Handler
channel.read(buffer, buffer, new ReadServerHandler(channel));
}
public void failed(Throwable exc, AsyncServerHandler serverHandler) {
exc.printStackTrace();
serverHandler.latch.countDown();
}
}
ReadServerHandler
package testio.aio.server;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
public class ReadServerHandler implements CompletionHandler<Integer, ByteBuffer> {
// 用于读取半包消息和发送应答
private AsynchronousSocketChannel channel;
public ReadServerHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
// 读取到消息后的处理
public void completed(Integer result, ByteBuffer attachment) {
// flip操作
attachment.flip();
// 根据
byte[] message = new byte[attachment.remaining()];
attachment.get(message);
try {
String expression = new String(message, "UTF-8");
System.out.println("服务器收到消息: " + expression);
String calrResult = null;
try {
calrResult = expression + "_return";
} catch (Exception e) {
calrResult = "计算错误:" + e.getMessage();
}
// 向客户端发送消息
doWrite(calrResult);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// 发送消息
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 CompletionHandler<Integer, ByteBuffer>() {
public void completed(Integer result, ByteBuffer buffer) {
// 如果没有发送完,就继续发送直到完成
if (buffer.hasRemaining())
channel.write(buffer, buffer, this);
else {
// 创建新的Buffer
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
// 异步读 第三个参数为接收消息回调的业务Handler
channel.read(readBuffer, readBuffer, new ReadServerHandler(channel));
}
}
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
}
}
});
}
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server_aio
package testio.aio.server;
public class Server_aio {
private static int DEFAULT_PORT = 12345;
private static AsyncServerHandler serverHandle;
public volatile static long clientCount = 0;
public static void start() {
start(DEFAULT_PORT);
}
public static synchronized void start(int port) {
if (serverHandle != null)
return;
serverHandle = new AsyncServerHandler(port);
new Thread(serverHandle, "Server").start();
}
}
Test_Server
package testio.aio.server;
public class Test_Server {
public static void main(String[] args) {
Server_aio.start();
}
}
----------------------aio客户端---------------
AsyncClientHandler
package testio.aio.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
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();
}
}
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();
} catch (IOException e) {
e.printStackTrace();
}
}
//连接服务器成功
//意味着TCP三次握手完成
public void completed(Void result, AsyncClientHandler attachment) {
System.out.println("客户端成功连接到服务器...");
}
//连接服务器失败
public void failed(Throwable exc, AsyncClientHandler attachment) {
System.err.println("连接服务器失败...");
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 WriteClientHandler(clientChannel, latch));
}
}
ReadClientHandler
package testio.aio.client;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
public class ReadClientHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public ReadClientHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) {
this.clientChannel = clientChannel;
this.latch = latch;
}
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body;
try {
body = new String(bytes, "UTF-8");
System.out.println("客户端收到结果:" + body);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("数据读取失败...");
try {
clientChannel.close();
latch.countDown();
} catch (IOException e) {
}
}
}
WriteClientHandler
package testio.aio.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
public class WriteClientHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public WriteClientHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch) {
this.clientChannel = clientChannel;
this.latch = latch;
}
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 ReadClientHandler(clientChannel, latch));
}
}
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("数据发送失败...");
try {
clientChannel.close();
latch.countDown();
} catch (IOException e) {
}
}
}
Client_aio
package testio.aio.client;
public class Client_aio {
private static String DEFAULT_HOST = "127.0.0.1";
private static int DEFAULT_PORT = 12345;
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) throws Exception {
if (msg.equals("q"))
return false;
clientHandle.sendMsg(msg);
return true;
}
}
Test_client
package testio.aio.client;
import java.util.Scanner;
public class Test_client {
public static void main(String[] args) throws Exception {
// 运行客户端
Client_aio.start();
Thread.sleep(2000);
System.out.println("请输入请求消息:");
for (int i = 0; i < 10; i++) {
String handinStr = new Scanner(System.in).nextLine();
Client_aio.sendMsg(handinStr);
}
}
}