在先前的文章《Unix之IO模型》已经讲述到5种IO模型以及对应的同步异步和阻塞非阻塞相关核心概念,接下来看下Java的IO模型在服务端的网络编程中是如何演进,注意这里用启动Java程序表示一个JVM进程,而JVM进程中以多线程方式进行协作,这里讲述以线程为主展开
BIO 概述
accept
& read
都需要等待客户端建立连接和发起请求才能够进行让服务端程序进行响应,也就是上述的方法在服务端的编程中会让服务端的主线程产生阻塞,当其他客户端与Java服务端尝试建立连接和发请求的时候会被阻塞,等待前面一个客户端处理完之后才会处理下一个客户端的连接和请求,以上是Java的BIO体现服务端单线程BIO模型
// server.java
// 仅写部分服务端核心代码
ServerSocket server = new ServerSocket(ip, port);
while(true){
Socket socket = server.accept(); // 接收客户端的连接,会阻塞
out.put("收到新连接:" + socket.toString());
// client have connected
// start read
BufferedReader br = new BuufferedReader(new InputstreamReader(socket.getInputStream));
String line = br.readLine(); // 等待客户端发起请求进行读取操作,会阻塞
// decode ..
// process ..
// encode ..
// send ..
}
基于1:1的多线程BIO模型
// thread-task.java
public class IOTask implements Runnable{
private Socket client;
public IOTask(Socket client){
this.client = client;
}
run(){
while(!Thread.isInterrupt()){
// read from socket inputstream
// encode reading text
// process
// decode sent text
// send
}
}
}
// server.java
ServerSocket server = new ServerSocket(ip, port);
while(true){
Socket client = server.accept();
out.put(“收到新连接:” + client.toString());
new Thread(new IOTask(client)),start();
}
基于M:N的线程池实现的BIO模式
// server.java
ExecutorService executors = Executros.newFixedThreadPool(MAX_THREAD_NUM);
ServerSocket server = new ServerSocket(ip,port);
while(true){
Socket client = server.accept();
out.put(“收到新连接:” + client.toString());
threadPool.submit(new IOTask(ckient));
}
NIO概述
select()
向操作系统进行事件注册基于单线程通道轮询的NIO模式(NIO模型)
// server.java
ServerSocketChannel server = ServerSocketChannel.open();
// 设置所有的socket默认伪阻塞,必须设置服务端的通道为非阻塞模式
server.configureBlocking(false);
// 绑定端口以及最大可接收的连接数backlog
server.socket().bind(new InetSocketAddress(PORT), MAX_BACKLOG_NUM);
while(true){
SocketChannel client = server.accept();
// 非阻塞获取,所以client可能为null
if(null != client){
// 设置客户端的通道为非阻塞
client.configureBlocking(false);
// 进行IO操作
// read
ByteBuffer req = ByteBuffer.allocate(MAX_SIZE);
while(client.isOpen() &/& client.read(req)!= -1){
// BufferCoding是自己封装的一个解码工具类,结合ByteBuffer与Charset使用,这里不演示代码实现
// decode
byte[] data = BufferCoding.decode(req);
if(data != null){
break;
}
}
// prepared data to send
sentData = process(data);
// encode
ByteBuffer sent = BufferCoding.encode(sentData);
// write
client.writeAndFlush(sent);
}
}
基于单线程的select事件轮询IO模式(IO多路复用模型)
// server.java
ServerSocketChannel server = ServerSocketChannel,open();
server.configureBlocking(false);
Selector selector = Selector.open();
// 服务端只注册ACCEPT,作为接入客户端的连接
// DataWrap封装读写缓存ByteBuffer
server.register(selector, SelectionKey.OP_ACCEPT, server);
server.socket().bind(new InetSocketAddress(PORT), MAX_BACKLOG_NUM);
while(true){
int key = selector.select();
if(key == 0) continue;
// 获取注册到selecor所有感兴趣的事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while(it.hasNext()){
SelectionKey key = it.next();
it.remove();
if(key.isAcceptable()){
// 接收accept事件
ServerSocketChannel serverChannel = (ServerSocketChannel)key.attachment();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 客户端已经获取到连接,告诉客户端的channel可以开始进行读写操作
client.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, new DataWrap(256, 256));
}
// read
if(key.isReadable()){
//...
// 在事件中添加写操作
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
}
if(key.isWriteable()){
// ...
// 成功完成写操作,这个时候取消写操作
key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
}
}
}
基于多线程实现的NIO(Reactor模型)
后面会详细写高性能IO编程模型,会再详细讲Reactor以及对应的实现