BIO 的概念:当用户进程发起请求时,一直阻塞直到数据拷贝到用户空间为止才返回。
public class BioServer {
public static void main(String[] args) throws IOException {
//1.创建服务端,指定端口号
ServerSocket socket = new ServerSocket(10000);
System.out.println("服务端启动,准备接收连接");
while (true) {
//2.等待连接
Socket accept = socket.accept();
System.out.println("一个socket连接" + accept.getInetAddress() + ":" + accept.getLocalPort());
new Thread(() -> {
try {
//3.去的连接后获得写入写出buffer
BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
//4.接收或发送消息
String readMessage = reader.readLine();
System.out.println(readMessage);
String writeMessage = "你好,我是服务端,我收到了你发送的消息n";
writer.write(writeMessage);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
启动了一个线程来处理这个连接,如果不启动线程,那么我们只能把处理连接的数据放在主线程中,这时候主线程只能处理当前连接的这一个客户端,而不能同时处理多个客户端。
public class BioClient {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("127.0.0.1",10000);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String writeMessage = "你好,我是客户端,请问你收到我的消息了吗?n";
writer.write(writeMessage);
writer.flush();
String readMessage = reader.readLine();
System.out.println(readMessage);
}
}
因此,对于BIO模式,一个连接对应一个线程(费线程)。如果客户端一直增加,服务端线程会无限增加,直到服务器资源耗尽。
NIO程序Java 中的 NIO 使用的是 IO 多路复用技术实现的:多个 IO 操作共同使用一个 selector(选择器)去询问哪些 IO 准备好了,selector 负责通知那些数据准备好了的 IO,它们再自己去请求内核数据。
selector.select()选择一组键,其对应的通道已准备好进行 I/O操作。这个方法执行一个阻塞的。 只有在选择了至少一个通道后才返回。
public class NioServer {
public static void main(String[] args) throws IOException {
//1.创建Selector
Selector selector = Selector.open();//创建Selector
//2.创建ServerSocketChannel
ServerSocketChannel channel = ServerSocketChannel.open();//创建ServerSocketChannel
//3.绑定端口号
channel.bind(new InetSocketAddress(10002));
//4.设置阻塞模式
channel.configureBlocking(false);//设置为非阻塞模式
//5.注册channel,指定事件模式
channel.register(selector, SelectionKey.OP_ACCEPT);//将channel注册到selector上,并注册accept事件
System.out.println("服务端启动...");
while (true) {
//6.select阻塞
int select = selector.select();//选择一组键,其对应的通道已准备好进行 I/O,这个方法执行一个阻塞的,选择只有在选择了至少一个通道后才返回。
// 如果使用的是select(timeout)或selectNow()需要判断返回值是否大于0
// 有就绪的Channel
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//7.根据事件属性执行对应操作
if (selectionKey.isAcceptable()) {//accept()事件
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("一个客户端接入");
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);//将socketChannel注册到selector上,并注册读取事件
} else if (selectionKey.isReadable()) {//读事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();//强制转换为SocketChannel
//8.创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);// 创建Buffer用于读取数据
int length = socketChannel.read(buffer);
if (length > 0) {
buffer.flip();//调用flip之后,读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)。
byte[] bytes = new byte[buffer.remaining()];//buffer.remaining()缓冲区剩余元素数。
buffer.get(bytes);// 将数据读入到byte数组中
String content = new String(bytes, "UTF-8").replace("rn", "");
System.out.println(content);
}
}
}
iterator.remove();
}
}
}
public class NioClient {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("127.0.0.1",10002);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String message = "你好,我是客户端发送的消息 n";
bufferedWriter.write(message);
bufferedWriter.flush();
}
}
NIO 始终只有一个线程,并没有启动额外的线程来处理每个连接的事务,解决了 BIO 线程无限增加的问题,所以,NIO 是非常高效的。
但是,如果连接非常多的情况下,有可能一次 Select 拿到的 SelectionKey 非常多,而且取数据本身还要把数据从内核空间拷贝到用户空间,这是一个阻塞操作,这时候都放在主线程中来遍历所有的 SelectionKey 就会变得非常慢了,当然,我们也可以把处理数据的部分扔到线程池中来处理,那么,除了这种方式有没有更高效的方式了呢?让我们来看看 AIO 是否能解决这个问题。
AIO 程序AIO,异步 IO,相对于 AIO,其它的 IO 模型本质上都是同步 IO。
AIO 的概念:用户进程发起读取请求后立马返回,当数据完全拷贝到用户空间后通知用户直接使用数据。
public class AioServer {
public static void main(String[] args) throws IOException {
//1.创建服务端,并指定端口
AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(10003));
System.out.println("服务端启动");
// 2.等待连接监听accept事件,完全异步,不会阻塞
serverSocketChannel.accept(null, new CompletionHandler() {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
try {
System.out.println("一个客户端已连接" + socketChannel.getRemoteAddress());
//3.再次监听accept事件
serverSocketChannel.accept(null, this);
//4.处理消息
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将数据读入到buffer中
Future future = socketChannel.read(buffer);
if (future.get() > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String content = new String(bytes, "UTF-8");
if (content.equals("rn")) {
continue;
}
System.out.println("收到客户端消息: " + content);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("失败");
}
});
// 阻塞住主线程
System.in.read();
}
}
AsynchronousServerSocketChannel,它与 BIO 中的 ServerSocket 和 NIO 中的 ServerSocketChannel 类似,是一个服务端进程;
accept () 方法监听客户端连接,用法跟 BIO 和 NIO 都一样,但是,这个 accept () 执行方式完全不一样了,BIO 中的 accept () 是完全阻塞当前线程的,NIO 中的 accept () 是通过 Accept 事件来实现的,而 AIO 中的 accept () 是完全异步的,执行了这个方法之后会立即执行后续的代码,不会停留在 accept () 这一行,所以,在 main () 方法的最后需要加一行阻塞代码,否则 main () 方法执行完毕,进程就结束了。
最后,在 accept () 方法的回调方法 complete () 中处理数据,这里的数据已经经历过数据准备和从内核空间拷贝到用户空间两个阶段了,到达用户空间是真正可用的数据,而不像 BIO 和 NIO 那样还要自己去阻塞着把数据从内核空间拷贝到用户空间再使用。
public class AioClient {
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket("127.0.0.1",10003);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String message = " 你好,我是客户端发送的消息 n";
bufferedWriter.write(message);
bufferedWriter.flush();
}
}
从效率上来看,AIO 无疑是最高的,然而,遗憾地是,目前作为广大服务器使用的系统 linux 对 AIO 的支持还不完善,导致我们还不能放心地使用 AIO 这项技术。



