传统的BIO编程
ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作、连接成功之后,双方通过输入和输出流进行同步阻塞通信。
伪异步BIO编程
为了解决传统的BIO编程面临的一个I/O一个线程的问题,伪异步用线程池来处理多个客户端的请求接入。
BIO编程&伪异步BIO编程代码
由于这两种编程方式都比较简单,所以就代码就放一起啦。
public class TimeServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = null;
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
serverSocket = new ServerSocket(Main.PORT);
System.out.println("The time server is start in port: " + Main.PORT);
Socket socket = null;
while (true) {
socket = serverSocket.accept();
// bio 线程和连接一对一,损耗资源
// new Thread(new TimeServerHandler(socket)).start();
// bio refactor 线程池模式,如果IO阻塞就会阻塞线程
executorService.submit(new TimeServerHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
serverSocket.close();
System.out.println("The time server closed.");
}
}
}
}
public class TimeServerHandler implements Runnable {
private final Socket socket;
public TimeServerHandler(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 currentTime = null;
String body = null;
while (true) {
body = in.readLine();
if (body == null) {
break;
}
System.out.println("The time server received order: " + body);
currentTime = Main.QUERY_COMMAND.equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString()
: Main.BAD_COMMAND;
out.println(currentTime);
}
} catch (Exception exception) {
exception.printStackTrace();
} finally {
Main.close(socket, in, out);
}
}
}
public class TimeClient {
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(Main.HOST, Main.PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println(Main.QUERY_COMMAND);
System.out.println("send order to server succeed.");
String response = in.readLine();
System.out.println("Now is: " + response);
} catch (Exception exception) {
exception.printStackTrace();
} finally {
Main.close(socket, in, out);
}
}
}
public class Main {
public static final String HOST = "127.0.0.1";
public static final int PORT = 9090;
public static final String QUERY_COMMAND = "Query time order";
public static final String BAD_COMMAND = "Bad order";
public static void close(Socket socket, BufferedReader in, PrintWriter out) {
if (out != null) {
try {
out.close();
} catch (Exception outException) {
outException.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (Exception inException) {
inException.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (Exception socketException) {
socketException.printStackTrace();
}
}
}
}
NIO编程
非阻塞编程提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现,都支持阻塞和非阻塞两种模式。另外提供多路复用器Selector,它是NIO的基础。Selector提供选择已经就绪的任务的能力,简单说就是Selector会不断轮询注册在其上的channel,如果某个channel上面发生读或写事件,这个channel就处于就绪状态,会被selector轮询出来,然后通过selectionKey可以换取就绪channel的集合,进行后续IO操作。
一个selector可以同时轮询多个channel,由于JDK使用epoll()实现,所以没有最大句柄数的限制,意味着一个selector可以接入成千上万的客户端。
NIO代码编程如下
public class TimeServer {
public static void main(String[] args) throws Exception {
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(Main.PORT);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
public class MultiplexerTimeServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile boolean stop;
//创建Reactor线程
public MultiplexerTimeServer(int port) {
try {
//创建多路复用器
selector = Selector.open();
//用于监听客户端连接,它是所有连接的父管道
serverSocketChannel = ServerSocketChannel.open();
//设置连接为非阻塞模式
serverSocketChannel.configureBlocking(false);
//绑定监听端口
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
//将serverSocketChannel注册到Reactor线程的多路复用器selector上,监听accept事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("The time server is start in port: " + port);
} catch (Exception exception) {
exception.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
public void run() {
//无限循环轮询准备就绪的selectKey
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeySet.iterator();
SelectionKey selectionKey = null;
while (iterator.hasNext()) {
selectionKey = iterator.next();
iterator.remove();
handleInput(selectionKey);
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
//多路复用器关闭之后,所有注册的channel和pipe会自动去关闭
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey selectionKey) {
try {
if (selectionKey.isValid()) {
//处理新客户端接入请求
if (selectionKey.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes);
System.out.println("The time server received order: " + body);
String currentTime =
Main.QUERY_COMMAND.equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString()
: Main.BAD_COMMAND;
doWrite(socketChannel, currentTime);
} else if (readBytes < 0) {
//链路已经关闭,需要关闭socketchannel释放资源
selectionKey.cancel();
socketChannel.close();
}
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
private void doWrite(SocketChannel socketChannel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
}
}
public class TimeClient {
public static void main(String[] args) {
new Thread(new TimeClientHandler(Main.HOST, Main.PORT), "NIO-TimeClient-001").start();
}
}
public class TimeClientHandler implements Runnable {
private final String host;
private final int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
public TimeClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
//创建多路复用器
selector = Selector.open();
//打开socketchannel,绑定客户端本地地址(默认随机分配)
socketChannel = SocketChannel.open();
//设置为非阻塞
socketChannel.configureBlocking(false);
} catch (Exception exception) {
exception.printStackTrace();
System.exit(1);
}
}
public void run() {
try {
//异步连接服务端
doConnect();
} catch (Exception exception) {
exception.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeySet.iterator();
SelectionKey selectionKey = null;
while (iterator.hasNext()) {
selectionKey = iterator.next();
iterator.remove();
try {
handleInput(selectionKey);
} catch (Exception ex) {
if (selectionKey != null) {
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
}
}
} catch (Exception exception) {
exception.printStackTrace();
System.exit(1);
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey selectionKey) throws IOException {
if (selectionKey.isValid()) {
SocketChannel sc = (SocketChannel) selectionKey.channel();
if (selectionKey.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
} else {
System.exit(1);
}
}
if (selectionKey.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes);
System.out.println("Now is: " + body);
this.stop = true;
} else if (readBytes < 0) {
selectionKey.cancel();
sc.close();
}
}
}
}
private void doConnect() throws IOException {
if (socketChannel.connect(new InetSocketAddress(host, port))) {
//连接成功则注册read事件
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
//连接失败则注册connect事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel socketChannel) throws IOException {
byte[] request = Main.QUERY_COMMAND.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(request.length);
writeBuffer.put(request);
writeBuffer.flip();
socketChannel.write(writeBuffer);
if (!writeBuffer.hasRemaining()) {
System.out.println("send order to server succeed.");
}
}
}
AIO编程
NIO 2.0引入了新的异步通道的概念,是真正的异步非阻塞IO,对应UNIX网络编程中的AIO,,并提供了异步文件通道和异步套接字通道的实现。异步通道提供两种方式获取操作结果。
- 通过Future类来表示异步操作的结果
- 异步操作时传入channels
CompletionHandler接口的实现类为操作完成的回调。
//TODO