先叙述一下网络编程,而后进入BIO、NIO、AIO讲解
1.网络编程
网络编程从大的方面说就是对数据的发送与接收。
网络编程就是将运行在多个设备(计算机)的程序通过网络连接起来,进行通信。
网络编程的基本模型是C/S模型,也就是两个进程之间进行相互通信,其中服务器端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。
Windows Socket是Windows环境下,网络应用程序编程接口,即网络编程的解决方案。由于TCP/IP的核心内容被封装在操作系统中,如果应用程序要使用TCP/IP,可以通过系统提供的TCP/IP的编程接口来实现,Windows环境下我们使用的是Socket。下面讲述的BIO、NIO、AIO都是通过使用Socket实现的网络编程,但是它们是以不同的方式实现的。
2.BIO、NIO、AIO概述
先叙述一下同步,异步,阻塞,非阻塞四个概念
同步和异步:
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。
阻塞和非阻塞:
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
BIO编程:同步阻塞式
JDK 1.4版本之前,传统的BIO编程方式是常用的编程方式。编程实现过程为:服务端启动一个ServerSocket负责绑定IP地址,启动监听端口,客户端启动Socket负责发起连接操作。连接成功后双方通过输入、输出流进行同步阻塞试通信。BIO模式下,服务器端以阻塞方式监听客户端连接,收到连接后便停止阻塞,创建一个新的线程进行处理,处理完成后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。线程个数与客户端并发访问数呈1:1的正比关系。
NIO编程:同步非阻塞式
JDK 1.4 中引入的NIO。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。NIO使用缓冲区,通道,多路复用器Selector技术,实现了同步非阻塞式的通信。
AIO编程:异步非阻塞式
JDK 1.7中新增了一些与文件(网络)I/O相关的一些API。这些API被称为NIO.2,或称为AIO(Asynchronous I/O)。异步通信通常有两种方式,将来式,回调式。这里我们讲解回调式。AIO使用CompletionHandler实现了回调式的异步非阻塞式的通信。
同步、异步点:服务器端在收到客户端请求时进行处理,触发I/O操作。如果服务器端在触发I/O之后,用户进程可以继续执行下面的代码,称为异步。如果服务器端在触发I/O之后,要等待或者轮询I/O操作结果,不可以继续执行下面代码,称为同步。
阻塞、非阻塞点:Socket的阻塞点其实就是在两个函数上面,读写操作read()、write()方法。
3.BIO
BIO实现:
(1)服务器端:创建一个ServerSocket对象server,并绑定端口。在一个while无限循环里,调用server的accept()方法,监听客户端连接。如果没有客户端连接请求,进程阻塞在accept()操作上。一旦监听到客户端连接,accept()方法返回一个Socket对象,创建一个线程并传入这个Socket对象去进行处理(数据的读取和写入)。
(2)客户端:创建一个Socket对象socket,并尝试通过套接字(IP:端口)去连接服务器端。然后进行处理(数据的写入和读取)。
读取和写入操作:通过输入输出流完成。
阻塞:在read()、write()方法时会阻塞。
下面是BIO的一个demo实现。
Server端:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class TimeServer {
public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket(5600);
Socket socket = null;
while (true) {
System.out.println("服务器开始监听");
socket = server.accept();
System.out.println(socket.getInetAddress().getHostAddress());
System.out.println("服务器监听到客户端连接请求");
new Thread(new TimeServerHandle(socket)).start();
}
} catch (IOException e) {
System.out.println("The time server close");
e.printStackTrace();
} finally {
System.out.println("The time server close");
if (server != null) {
try {
server.close();
System.out.println("The time server close");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Server端处理函数:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class TimeServerHandle implements Runnable {
private Socket socket;
public TimeServerHandle(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);
System.out.println("到达读入前");
String read = in.readLine();
System.out.println("收到信息为:"+read);
out.println("第一次信息回应");
System.out.println("回应数据已发出");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Client端:
import java.io.*;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;
public class TimeClient {
public static void main(String[] args) throws IOException {
Socket socket = null;
BufferedReader in = null;
BufferedWriter out = null;
//BufferedOutputStream out = null;
//PrintWriter out = null;
try {
socket = new Socket("127.0.0.1",5600);
System.out.println(socket.getLocalAddress().getHostAddress());
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//out = new PrintWriter(socket.getOutputStream(),true);
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//out = new BufferedOutputStream(socket.getOutputStream());
out.write("第一次信息发送");
out.newLine();
out.flush();
//out.println("第一次信息发送");
System.out.println("呼叫数据已发出");
String read = in.readLine();
System.out.println("收到服务器回应");
System.out.println("回应信息为:"+read);
CountDownLatch count = new CountDownLatch(1);
count.await();
System.out.println("第一次检查"+socket.isClosed());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
System.out.println("第二次检查"+socket.isClosed());
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.NIO
NIO有三个核心技术,缓冲区Buffer、通道Channel、多路复用器Selector。
缓冲区Buffer:
Buffer
是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer
对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream
对象中。在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
通道Channel:
Channel是一个通道,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是一个方向上移动(一个流要么是输入流,要么是输出流),而通道可以用于读、写,是全双工的。
多路复用器Selector:
异步 I/O 中的核心对象名为 Selector
。Selector
就是您注册对各种 I/O 事件的兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生的事件。
(1)服务器端:
我们需要做的第一件事就是创建一个 Selector
:
1 |
|
然后,我们将对不同的通道对象调用 register()
方法,以便注册我们对这些对象中发生的 I/O 事件的兴趣。register()
的第一个参数总是这个 Selector
。
打开一个 ServerSocketChannel
为了接收连接,我们需要一个 ServerSocketChannel
。事实上,我们要监听的每一个端口都需要有一个 ServerSocketChannel
。对于每一个端口,我们打开一个ServerSocketChannel
,如下所示:
1 2 3 4 5 6 |
|
第一行创建一个新的 ServerSocketChannel
,最后三行将它绑定到给定的端口。第二行将 ServerSocketChannel
设置为 非阻塞的 。我们必须对每一个要使用的套接字通道调用这个方法,否则异步 I/O 就不能工作。
选择键
下一步是将新打开的 ServerSocketChannels
注册到 Selector
上。为此我们使用 ServerSocketChannel.register() 方法,如下所示:
1 |
|
register()
的第一个参数总是这个 Selector
。第二个参数是 OP_ACCEPT
,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel
的唯一事件类型。
请注意对 register()
的调用的返回值。 SelectionKey
代表这个通道在此Selector
上的这个注册。当某个 Selector
通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey
来进行的。SelectionKey
还可以用于取消通道的注册。
内部循环
现在已经注册了我们对一些 I/O 事件的兴趣,下面将进入主循环。使用 Selectors
的几乎每个程序都像下面这样使用内部循环:
1 2 3 4 5 6 7 8 9 |
|
首先,我们调用 Selector
的 select()
方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select()
方法将返回所发生的事件的数量。
接下来,我们调用 Selector
的 selectedKeys()
方法,它返回发生了事件的SelectionKey
对象的一个 集合
。
我们通过迭代 SelectionKeys
并依次处理每个 SelectionKey
来处理事件。对于每一个 SelectionKey
,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。
监听新连接
程序执行到这里,我们仅注册了 ServerSocketChannel
,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey
调用 readyOps()
方法,并检查发生了什么类型的事件:
1 2 3 4 5 6 |
|
可以肯定地说, readOps()
方法告诉我们该事件是新的连接。
接受新的连接
因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept()
操作会阻塞:
1 2 |
|
下一步是将新连接的 SocketChannel
配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel
注册到Selector
上,如下所示:
1 2 |
|
注意我们使用 register()
的 OP_READ
参数,将 SocketChannel
注册用于 读取 而不是 接受 新连接。
删除处理过的 SelectionKey
在处理 SelectionKey
之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey
从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove()
方法来删除处理过的 SelectionKey
:
1 |
|
现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。
传入的 I/O
当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select()
,并返回一个或者多个 I/O 事件。这一次, SelectionKey
将被标记为 OP_READ
事件,如下所示:
1 2 3 4 5 6 |
|
与以前一样,我们取得发生 I/O 事件的通道并处理它。在本例中,由于这是一个 echo server,我们只希望从套接字中读取数据并马上将它发送回去。关于这个过程的细节,请参见 参考资料 中的源代码 (MultiPortEcho.java)。
回到主循环
每次返回主循环,我们都要调用 select
的 Selector()
方法,并取得一组SelectionKey
。每个键代表一个 I/O 事件。我们处理事件,从选定的键集中删除SelectionKey
,然后返回主循环的顶部。
这个程序有点过于简单,因为它的目的只是展示异步 I/O 所涉及的技术。在现实的应用程序中,您需要通过将通道从 Selector
中删除来处理关闭的通道。而且您可能要使用多个线程。这个程序可以仅使用一个线程,因为它只是一个演示,但是在现实场景中,创建一个线程池来负责 I/O 事件处理中的耗时部分会更有意义。
非阻塞:在执行read()、write()方法时,不用担心操作阻塞。因为我们已经用selector监听到了读、写事件。
(2)客户端:
省略。
下面是NIO的一个demo实现。
Server端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.nio.charset.Charset;
import java.util.Iterator;
public class ServerDemo{
private ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
private ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024);
private Selector selector;
public ServerDemo() throws IOException{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8080));
System.out.println("监听端口:8080");
this.selector = Selector.open();
// 绑定channel的accept
System.out.println("服务器开始监听");
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public static void main(String[] args) throws Exception{
new ServerDemo().go();
}
private void go() throws Exception{
// block api
while(selector.select()>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
// 新连接
if(selectionKey.isAcceptable()){
System.out.println("此密钥的通道已准备好接受新的套接字连接");
ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
// 新注册channel
SocketChannel socketChannel = server.accept();
System.out.println("服务器监听到客户端连接请求");
if(socketChannel==null){
continue;
}
socketChannel.configureBlocking(false);
// 注意!这里和阻塞io的区别非常大,在编码层面之前的等待输入已经变成了注册事件,这样我们就可以在等待的时候做别的事情,
// 比如监听更多的socket连接,也就是之前说了一个线程监听多个socket连接。这也是在编码的时候最直观的感受
socketChannel.register(selector, SelectionKey.OP_READ| SelectionKey.OP_WRITE);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
buffer.put("第一次信息回应".getBytes());
//调换这个buffer的当前位置,并且设置当前位置是0
buffer.flip();
//从给定的缓冲区向该通道写入一个字节序列
socketChannel.write(buffer);
System.out.println("回应数据已发出");
}
// 服务端关心的可读,意味着有数据从client传来了,根据不同的需要进行读取,然后返回
if(selectionKey.isReadable()){
System.out.println("isReadable");
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
readBuffer.clear();
socketChannel.read(readBuffer);
readBuffer.flip();
String receiveData= Charset.forName("UTF-8").decode(readBuffer).toString();
System.out.println("receiveData:"+receiveData);
// 把读到的数据绑定到key中
selectionKey.attach("server message echo:"+receiveData);
}
// 实际上服务端不在意这个,这个写入应该是client端关心的,这只是个demo,顺便试一下selectionKey的attach方法
if(selectionKey.isWritable()){
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
String message = (String) selectionKey.attachment();
if(message==null){
continue;
}
selectionKey.attach(null);
writeBuffer.clear();
writeBuffer.put(message.getBytes());
writeBuffer.flip();
while(writeBuffer.hasRemaining()){
socketChannel.write(writeBuffer);
}
}
}
}
}
}
Client端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
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.nio.charset.Charset;
import java.util.Iterator;
public class ClientDemo{
private final ByteBuffer sendBuffer=ByteBuffer.allocate(1024);
private final ByteBuffer receiveBuffer=ByteBuffer.allocate(1024);
private Selector selector;
public ClientDemo()throws IOException{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(),8080));
socketChannel.configureBlocking(false);
System.out.println("与服务器的连接建立成功");
selector=Selector.open();
socketChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public static void main(String[] args) throws Exception{
final ClientDemo client=new ClientDemo();
// Thread receiver=new Thread(client::receiveFromUser);
System.out.println("1111");
//receiver.start();
client.talk();
}
private void talk()throws IOException {
while (selector.select() > 0 ){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey key = it.next();
it.remove();
if (key.isReadable()) {
receive(key);
}
// 实际上只要注册了关心写操作,这个操作就一直被激活
if (key.isWritable()) {
send(key);
}
}
}
}
private void send(SelectionKey key)throws IOException{
SocketChannel socketChannel=(SocketChannel)key.channel();
synchronized(sendBuffer){
sendBuffer.flip(); //设置写
while(sendBuffer.hasRemaining()){
socketChannel.write(sendBuffer);
}
sendBuffer.compact();
}
}
private void receive(SelectionKey key)throws IOException{
SocketChannel socketChannel=(SocketChannel)key.channel();
//从该通道读取到给定缓冲区的字节序列
socketChannel.read(receiveBuffer);
receiveBuffer.flip();
String receiveData=Charset.forName("UTF-8").decode(receiveBuffer).toString();
System.out.println("回应信息为:"+receiveData);
receiveBuffer.clear();
}
private void receiveFromUser() {
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
try{
String msg;
while ((msg = bufferedReader.readLine()) != null){
synchronized(sendBuffer){
sendBuffer.put((msg+"\r\n").getBytes());
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
5.AIO
JDK7主要增加了三个新的异步通道:
- AsynchronousFileChannel: 用于文件异步读写;
- AsynchronousSocketChannel: 客户端异步socket;
- AsynchronousServerSocketChannel: 服务器异步socket。
因为AIO的实施需充分调用OS参与,IO需要操作系统支持、并发也同样需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。
下面讲解回调式异步处理。
异步:回调式处理技术,用户进程派一个侦查员CompletionHandler到独立的线程中执行IO操作,侦查员操作完成返回主进程,触发它自己的completed或failed方法。用户进程派出侦查员后可以继续执行下面代码。
(1)服务器端:
在回调式异步处理中,有一个核心接口CompletionHandler。
回调式所采用的事件处理技术类似于Swing UI编程采用的机制。基本思想是主线程会派一个侦查员CompletionHandler到独立的线程中执行IO操作。这个侦查员将带着IO的操作的结果返回到主线程中,这个结果会触发它自己的completed或failed方法(要重写这两个方法)。在异步IO活动结束后,接口java.nio.channels.CompletionHandler会被调用,其中V是结果类型,A是提供结果的附着对象。此时必须已经有了该接口completed(V,A)和failed(V,A)方法的实现,你的程序才能知道异步IO操作成功或失败时该如何处理。
(2)客户端:
省略。
下面是AIO的一个demo实现。
Server端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class AioServer {
public static AsynchronousServerSocketChannel serverSocketChannel;//第二次接收的时候会使用到,因此作为一个属性
public static void main(String[] args) {
//新建一个线程池,使得aio中的操作都使用这个线程池中的线程,而且,还传入一个ThreadFactory对象,自己去定义线程
ExecutorService executorService = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
try {
//创建使用的公共线程池资源
AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
//创建AsynchronousServerSocketChannel对象
if(serverSocketChannel==null){
serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
}
//设定一些参数
//接收缓冲区的大小
serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 64 * 1024);
//是否重用本地地址
serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
//绑定监听的端口号
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",5432));
//开始接收请求,第一个参数是根据自己的需求出入对应的对象
//第二个参数CompletionHandler的子对象
serverSocketChannel.accept(new AioServer(), new MyCompletion());
System.out.println("服务器开始监听");
//保持程序不停止
waitRevicer();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void waitRevicer(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
//System.out.println("保持程序运行不停止...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
//用于消除异步I/O操作结果的处理程序
class MyCompletion implements CompletionHandler<AsynchronousSocketChannel,AioServer>{
@Override
public void completed(AsynchronousSocketChannel result, AioServer attachment) {
System.out.println("completed");
//用来缓存数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
//读取客户端数据,存放到ByteBuffer对象中
result.read(byteBuffer).get();
//重新设置position和limit的值,为读取值做准备
byteBuffer.flip();
//根据值的长度创建对象,并将ByteBuffer中的数据存入,打印输出
byte[] input = new byte[byteBuffer.remaining()];
byteBuffer.get(input);
System.out.println(new String(input));
//重新设置position的值,并将客户端发送的值返回给客户端
byteBuffer.position(0);
result.write(byteBuffer);
// String sayHello = new String("你好啊,客户端"+result.getRemoteAddress().toString());
// System.out.println("sayHello:"+sayHello);
// result.write(ByteBuffer.wrap(sayHello.getBytes("UTF-8")));
attachment.serverSocketChannel.accept(attachment, this);//不重新设置接收的话就只能接收一次了
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, AioServer attachment) {
System.out.println("failed");
System.out.println(attachment);
System.out.println(exc);
}
}
Client端:
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class AioClient {
public static void main(String[] args) throws InterruptedException, ExecutionException {
new AioClientThread().start();
new AioClientThread().start();
new AioClientThread().start();
}
}
class AioClientThread extends Thread{
@Override
public void run() {
super.run();
try {
//获取AsynchronousSocketChannel的实例,这里可以跟服务器端一样传入一个AsynchronousChannelGroup的对象
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
//连接服务器
Future<Void> future = socketChannel.connect(new InetSocketAddress("127.0.0.1",5432));
//注意,这个get是一个阻塞的方法,只有当连接上了之后才会往下走
//如果不执行get方法,等程序没有连接成功时进行发送数据的操作会发生错误
future.get();
//拼接发送给服务器的数据
Random rom = new Random();
StringBuffer writeBuf = new StringBuffer();
writeBuf.append("this is client").append(rom.nextInt(1000));
System.out.println(writeBuf.toString());
ByteBuffer byteBuffer = ByteBuffer.wrap(writeBuf.toString().getBytes());
//发送数据
socketChannel.write(byteBuffer);
//读取服务器的返回的数据
read(socketChannel);
} catch (Exception e) {
e.printStackTrace();
}
}
private void read(AsynchronousSocketChannel socketChannel){
ByteBuffer byteBufer = ByteBuffer.allocate(10);
try {
StringBuffer strBuf = new StringBuffer("客户端:");
boolean hasData = true;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while(hasData){
socketChannel.read(byteBufer).get();
if(byteBufer.capacity()>byteBufer.position()){//当容器大小大于容器里面的数量的时候,认为读取完毕
hasData = false;
}
byteBufer.flip();
byte[] buf = new byte[byteBufer.remaining()];
byteBufer.get(buf);
if(hasData){
System.out.println("数据没有读取完毕继续读取");
}else{
System.out.println("数据读取完毕");
}
out.write(buf);
byteBufer.clear();
}
strBuf.append(new String(out.toByteArray(),"UTF-8"));
System.out.println(strBuf);
//read(socketChannel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
参考文献:
1. Netty权威指南,作者:李林锋
2. NIO入门,作者:Greg Travis,链接:https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html
3. java中的AIO,作者:林湾村龙猫,链接:https://www.jianshu.com/p/c5e16460047b