粗汉之IO实战
文章目录
1、BIO、NIO、AIO之间的根本区别。
BIO
同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务端资源要求比较高,并发局限于应用中,在jdk1.4以前是唯一的io现在,但程序直观简单易理解
NIO
同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1,4开始支持 【线程池】
AIO
异步非阻塞I/O,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由操作系统先完成了再通知服务器用其启动线程进行处理。AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,jdk1.7开始支持。 【事件驱动、回调】
因为由操作系统来回调,所以操作系统的性能,决定IO性能(存在兼容问题)
IO和NIO区别
IO面向流,NIO面向缓冲区
IO的各种流是阻塞的,NIO是非阻塞模式
Java NIO的选择允许一个单独的线程来监视多个输入通道,可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入或选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道
2、BIO代码简易实现
服务端
public class BIOClient {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8080);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in))) {
String result = sin.readLine();
while (!"bye".equals(result)) {
out.println(result);
System.out.println("server : " + in.readLine());
result = sin.readLine();
}
} catch (IOException e) {
e.getStackTrace();
}
}
}
客户端
public class BIOServer {
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("client : " + in.readLine());
String result = sin.readLine();
while (!"bye".equals(result)) {
out.println(result);
System.out.println("client : " + in.readLine());
result = sin.readLine();
}
} catch (IOException e) {
e.getStackTrace();
}
}
}
3、NIO代码简易实现。
学习之前,先了解几种SelectionKey状态
SelectionKey.OP_ACCEPT
如果注册的时候设置的这个,那么此通道就是端口监听
SelectionKey.OP_CONNECT
如果注册的时候设置的这个,那么此通道就是可连接
SelectionKey.OP_READ
如果注册的时候设置的这个,那么此通道就是可读
SelectionKey.OP_WRITE
如果注册的时候设置的这个,那么此通道就是待写再来了解几个概念
Selector
选择器,说白了就是公司门口的接待
SelectionKey
包装的通道+状态
SocketChannel
就是通道了,也就是有需要的话就把自己注册到Selector里面,并且标注SelectorKey状态 a、serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
b、socketChannel.register(selector, SelectionKey.OP_READ);
所以呢?所以呢?
说白了就是去遍历Selector然后取出可用的SelectionKey取出来看是什么情况,然后根据状态不同做不同处理,所以有点麻烦,也就是因为麻烦所以才出来了NIO框架嘛
可以使用NIO实现 群聊哈
服务端
public class Server {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
//注册到selector中,设置事件为监听事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//selector.select()是阻塞的
if (selector.select() != 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 移除 ,避免重复消费
iterator.remove();
SocketChannel socketChannel = null;
// 如果是请求接入
if (selectionKey.isAcceptable()) {
socketChannel = acceptSelectionKey(selectionKey, serverSocketChannel.accept());
}
// 如果是可读
if (selectionKey.isReadable()) {
socketChannel = readSelectionKey(selectionKey, true);
}
// 再次注册,并且监听可读事件
if (socketChannel != null) {
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
}
}
}
/**
* 监听请求
*/
private static SocketChannel acceptSelectionKey(SelectionKey selectionKey, SocketChannel socketChannel) throws IOException {
// 设置成非阻塞
socketChannel.configureBlocking(false);
// 回复客户端消息
socketChannel.write(Charset.defaultCharset().encode("您与聊天室其他人都不是朋友关系,请注意隐私安全!"));
return socketChannel;
}
/**
* 读取消息
*/
public static SocketChannel readSelectionKey(SelectionKey selectionKey, boolean isBoardCase) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
StringBuffer result = new StringBuffer();
while (socketChannel.read(byteBuffer) > 0) {
//转成读模式
byteBuffer.flip();
result.append(Charset.defaultCharset().decode(byteBuffer));
byteBuffer.clear();
}
if (result.toString().length() > 0 && isBoardCase) {
//可以看是否广播给其他变量
System.out.println(result);
}
return socketChannel;
}
}
客户端
public class Client {
// 这里也可以用nio写法和服务端差不多
// 不过在发消息的时候需要用到线程或线程池,这样就可以不影响自己接受消息,达到聊天的效果
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8080));
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String result = scanner.next();
if (result != null && result.length() > 0) {
socketChannel.write(Charset.defaultCharset().encode("client-李四:" +result));
}
}
}
}
4、AIO代码简易实现
服务端
package org.example;
import java.io.IOException;
import java.net.InetSocketAddress;
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.Executors;
/**
* AIO 服务端
*/
public class AIOServer {
private final int port;
public static void main(String args[]) {
int port = 8000;
new AIOServer(port);
}
public AIOServer(int port) {
this.port = port;
listen();
}
private void listen() {
try {
ExecutorService executorService = Executors.newCachedThreadPool();
AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
// 工作线程,用来侦听回调的,事件响应的时候需要回调
final AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(threadGroup);
server.bind(new InetSocketAddress(port));
System.out.println("服务已启动,监听端口" + port);
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
final ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
// 由操作系统来触发回调的
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("IO 操作成功,开始获取数据");
try {
buffer.clear();
result.read(buffer).get();
buffer.flip();
result.write(buffer);
buffer.flip();
} catch (Exception e) {
System.out.println(e.toString());
} finally {
try {
result.close();
server.accept(null, this);
} catch (Exception e) {
System.out.println(e.toString());
}
}
System.out.println("操作完成");
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("IO 操作是失败: " + exc);
}
});
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException ex) {
System.out.println(ex);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
客户端
package org.example;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* AIO 客户端
*/
public class AIOClient {
private final AsynchronousSocketChannel client;
public AIOClient() throws Exception {
client = AsynchronousSocketChannel.open();
}
public void connect(String host, int port) throws Exception {
client.connect(new InetSocketAddress(host, port), null, new CompletionHandler<Void, Void>() {
// 由操作系统来触发回调的
@Override
public void completed(Void result, Void attachment) {
try {
client.write(ByteBuffer.wrap("这是一条测试数据".getBytes())).get();
System.out.println("已发送至服务器");
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
final ByteBuffer bb = ByteBuffer.allocate(1024);
client.read(bb, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("IO 操作完成" + result);
System.out.println("获取反馈结果" + new String(bb.array()));
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
}
);
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException ex) {
System.out.println(ex);
}
}
public static void main(String args[]) throws Exception {
new AIOClient().connect("localhost", 8000);
}
}