BIO
BIO是传统的IO模型,阻塞模型。每个连接都需要创建一个线程,并且在IO操作期间线程是阻塞的,做不了其他事情。
服务端代码
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket clientSocket = null;
try {
//创建服务端
serverSocket = new ServerSocket(8888);
System.out.println("服务端已启动,等待客户端连接...");
while (true){
// 监听客户端请求,接收不到请求会一直等待
clientSocket = serverSocket.accept();
int port = clientSocket.getPort();
InetAddress inetAddress = clientSocket.getInetAddress();
System.out.println("客户端 "+inetAddress+":"+port+" 连接成功!");
//处理客户端消息
new Thread(new ServerThread(clientSocket)).start();
}
} catch (IOException e) {
System.out.println("客户端连接失败:" + e.getMessage());
} finally {
try {
if (clientSocket != null) {
clientSocket.close();
}
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
System.out.println("关闭资源失败:" + e.getMessage());
}
}
}
}
/**
* 服务端线程处理类
*/
class ServerThread implements Runnable{
private Socket clientSocket;
public ServerThread(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
//获取客户端输入流以便接收客户端数据
try {
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
//获取客户端输出流以便向客户端发送数据
PrintWriter out = new PrintWriter(clientSocket.getOutputStream());
int port = clientSocket.getPort();
InetAddress inetAddress = clientSocket.getInetAddress();
String address = inetAddress+":"+port;
String inputLine;
while ((inputLine = in.readLine()) != null) {
//接收客户端消息
System.out.println("客户端"+address+"发来消息:" + inputLine);
//给客户端发送消息
out.println("服务端已接收到消息并回复:"+inputLine);
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码
public class BIOClient {
public static void main(String[] args) throws IOException {
Socket clientSocket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
//绑定服务端ip和端口号
clientSocket = new Socket("localhost", 8888);
System.out.println("连接服务端成功!");
//获取输入流,接收服务端消息
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
//获取输出流,给服务端发送消息
out = new PrintWriter(clientSocket.getOutputStream(), true);
Scanner scanner = new Scanner(System.in);
while (true){
System.out.print("给服务端发送消息:");
String msg = scanner.nextLine();
out.println(msg);
String response;
if ((response = in.readLine()) != null) {
//接收服务端响应
System.out.println("服务端响应:" + response);
}
}
} catch (IOException e) {
System.out.println("连接服务端失败:" + e.getMessage());
} finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (clientSocket != null) {
clientSocket.close();
}
} catch (IOException e) {
System.out.println("关闭资源失败:" + e.getMessage());
}
}
}
}
服务端为每一个客户端都要创建一个线程进行处理。
NIO
NIO采用的三个组件
-
Channel 通道,可以同时进行读写操作的
-
Buffer 缓冲区
-
Selector 选择器: Selector是NIO中用于监控多个Channel的选择器,可以实现单线程管理多个Channel。Selector可以检测多个Channel是否有事件发生,包括连接、接收、读取和写入等事件,并根据不同的事件类型进行相应处理。Selector可以有效地减少单线程管理多个Channel时的资源占用,提高程序的运行效率。
Channel是一个数据读写的通道,所有的数据都通过Buffer来处理,避免将字节直接写入通道,多线程模式下,一个线程可以处理多个请求,客户端的连接请求注册到多路复用器上,由多路复用器轮询到连接有IO请求的时候进行处理。
那么为什么需要Buffer不能直接把数据读写使用Channel呢?
首先Buffer有指针管理,能够更好的对一块的数据进行管理,反转,清除等。一次IO就要触发一次系统调用,涉及到用户态切换到内核态,Buffer可以把很多小操作合成一次大操作,减少系统调用的次数。NIO支持非阻塞IO,缓冲区做的就是一个提前准备的功能,能够提高IO的效率。
服务端代码
public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
// 创建一个ServerSocketChannel并绑定到指定的端口
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9999));
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 将ServerSocketChannel注册到Selector上,并监听OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器已启动,等待客户端连接...");
while (true) {
// 阻塞,等待事件发生
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) { // 处理连接请求事件
SocketChannel client = serverSocketChannel.accept();
client.configureBlocking(false);
//监听OP_ACCEPT事件
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
client.getRemoteAddress();
//分配缓存区容量
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
String output = new String(buffer.array()).trim();
Socket socket = client.socket();
InetAddress inetAddress = socket.getInetAddress();
int port = socket.getPort();
String clientInfo = inetAddress+":"+port;
String message = String.format("来自客户端 %s , 消息:%s", clientInfo , output);
System.out.println(message);
System.out.print("回复消息: ");
writeMessage(selector, client, buffer);
}
keyIterator.remove();
}
}
}
private static void writeMessage(Selector selector, SocketChannel client, ByteBuffer buffer) throws IOException {
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
buffer.clear();
buffer.put(message.getBytes());
//从写模式切换到读模式
buffer.flip();
while (buffer.hasRemaining()) {
client.write(buffer);
}
// 重新监听OP_ACCEPT事件
client.register(selector, SelectionKey.OP_READ);
}
}
客户端代码
public class NIOClient {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 9999));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isConnectable()) {
SocketChannel client = (SocketChannel) key.channel();
if (client.isConnectionPending()) {
client.finishConnect();
}
System.out.print("Enter message to server: ");
Scanner scanner = new Scanner(System.in);
String message = scanner.nextLine();
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
client.write(buffer);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
String output = new String(buffer.array()).trim();
System.out.println("来自客户端的消息: " + output);
System.out.print("输入消息: ");
// 和服务端代码一样
writeMessage(selector, client, buffer);
}
keyIterator.remove();
}
}
}
}
客户端和服务端通信的时候,不需要新建线程,通过Selector IO多路复用。
AIO
NIO已经是非阻塞模型,但是其实Selector在选择的这个过程也是一定程度的阻塞。
AIO完全基于异步模式,是真正非阻塞的模型。但是应用还不广泛,并且依赖于操作系统的实现。epoll和netty都是基于NIO模型,AIO需要创建回调函数,只有在大量IO操作中有很明显的效率提升。
在AIO模型中,当一个异步操作完成后,会通知相关线程进行后续处理,这种处理方式称为“回调”。回调函数可以由开发者自行定义,用于处理异步操作的结果。