什么是阻塞什么是非阻塞,阻塞==同步?要理解两个概念的区别我们看unix 操作系统的io模型是怎么定义的。
操作系统的io操作需要经历两个阶段,
从上图可以看到,io操作分为五种模式,
小结
我们看前四种io方式都是要等待io数据到来或者是等待数据从内核复制到用户进程,这种需要等待内核完成io操作的方式称为同步io,同步io中以是否需要等待io数据到来区分阻塞和非阻塞模式。
最后一种不需要等待内核完成io操作的模式为完全的异步非阻塞模式。
理解了操作系统的io模式,我们以java为例,看java是怎么使用这几种模式来完成io操作的。
这种方式就是同步阻塞的实现,代码如下可以看到代码和简单,这也是bio的一个优势,实现简单,但是性能是很低的,作为server端如果有大量链接进来那么,每个read write都是阻塞的一个连接的请求处理完之前,下一个连接就必须等待。看下代码
public class BioServer {
public BioServer() throws IOException {
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost",6666));
Socket client;
while (null != (client = serverSocket.accept())) {
//使用线程池处理读写操作
byte[] recBuf = new byte[128];
byte[] sendBuf = new byte[128];
InputStream inputStream = client.getInputStream();
inputStream.read(recBuf);
System.out.println(new String(recBuf));
OutputStream outputStream = client.getOutputStream();
outputStream.write("hello client".getBytes());
}
}
}
//客户端
public class BioClient {
public static void main(String[] args) throws IOException {
Socket client = new Socket();
client.connect(new InetSocketAddress("localhost",6666));
OutputStream outputStream = client.getOutputStream();
outputStream.write("hello server".getBytes());
InputStream inputStream = client.getInputStream();
byte[] recvBuf = new byte[128];
inputStream.read(recvBuf);
System.out.println(new String(recvBuf));
}
}
一个优化方式是使用线程池去处理客户端读写操作,但是每个连接都要消耗一个线程,耗费很多内存,其实也不能支撑更大数量级的连接。
java nio是基于io复用模式的java实现,底层依赖在linux上是epoll的io复用机制。
java提供了一下几个核心的类
话不多说看下 java nio的是怎么使用的,使用在注释中写的很详细。
java nio使用分下面的步骤:
public class NioNonBlockingServer {
public static void main(String[] args) throws IOException {
//1.初始化
ServerSocketChannel nonBlockingServer = ServerSocketChannel.open();
//2.bind
nonBlockingServer.bind(new InetSocketAddress("localhost", 7777));
//!!配置非阻塞
nonBlockingServer.configureBlocking(false);
//3.创建selector
Selector selector = Selector.open();
//4.注册事件监听
nonBlockingServer.register(selector, SelectionKey.OP_ACCEPT);
int selectNum = 0;
while (true) {
try {
//5.阻塞select,等待io事件就绪
selectNum = selector.select();
if (selectNum == 0) {
continue;
}
//6.io已就绪的channel 集合,遍历
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
//!!!手动remove,否则会导致select一直返回0
iterator.remove();
//6.1连接事件
if (next.isAcceptable()) {
//6.1.1 客户端新连接,channel
SocketChannel clientChannel = nonBlockingServer.accept();
if (clientChannel != null) {
clientChannel.configureBlocking(false);
System.out.println("新连接:" + clientChannel);
//6.1.2 注册read write事件监听
clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
//6.2 读事件就绪
} else if (next.isReadable()) {
//6.2.1 receiveBuffer
ByteBuffer receiveBuffer = ByteBuffer.allocate(1024);
SocketChannel channel = (SocketChannel) next.channel();
channel.read(receiveBuffer);
//6.2.1 buffer切换读写模式
receiveBuffer.flip();
System.out.println(next.channel() + "客户端发来数据:" + new java.lang.String(receiveBuffer.array()));
next.interestOps(SelectionKey.OP_WRITE);
//6.3写事件就绪
} else if (next.isWritable()) {
ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
sendBuffer.put("hello world from server".getBytes());
SocketChannel channel = (SocketChannel) next.channel();
sendBuffer.flip();
System.out.println("服务端发送返回:---》" + channel);
channel.write(sendBuffer);
next.interestOps(SelectionKey.OP_READ);
}
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
//客户端实现
/**
* 阻塞式的io
*/
public class NioBlockingClient {
public static void main(String[] args) throws IOException, InterruptedException {
// sendAndReceiveClient();
for (int i=0;i<10;i++) {
//创建多个并发请求
new Thread(new Runnable() {
@Override
public void run() {
try {
sendAndReceiveClient();
} catch (IOException e) {
System.out.println(e);
}
}
}).start();
}
Thread.sleep(100000);
}
private static void sendAndReceiveClient() throws IOException {
SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("localhost", 6666));
//阻塞模式
// clientChannel.configureBlocking(false);
ByteBuffer sendBuffer = ByteBuffer.allocate(128);
sendBuffer.put("hello world".getBytes());
sendBuffer.flip();
clientChannel.write(sendBuffer);
System.out.println(clientChannel+"发送到服务端:"+new String(sendBuffer.array()));
ByteBuffer receiveBuffer = ByteBuffer.allocate(128);
clientChannel.read(receiveBuffer);
System.out.println(clientChannel+"接受到服务端:"+new String(receiveBuffer.array()));
// System.out.println(new String(receiveBuffer.array()));
}
}
io复用的nio核心操作的server端的第5步操作,select()方法,他会轮询selector中的Selecttionkey,在操作系统中是通过select()方法的系统调用实现的。
nio使用需要注意的是ByteBuffer的使用需要进行读写切换。写入完数据之后需要执行flip()操作才能从里面读数据。
ByteBuffer使用三个指针来确定读写的位置。
执行flip()操作使position复位,limit移动到数据的长度的位置。
可以看到使用nio实现的代码比较复杂,那么就需要一种能简化的方式或者说将这种耦合度很高的代码进行解耦,将不同的同能使用不同的模块来实现,比如说下面的reactor模式。
reactor模式将server 分解成几个模块
基本的结构图如下
直接看代码实现更直观
public abstract class AbstractReactor implements Runnable {
Selector selector;
ServerSocketChannel serverSocket;
ExecutorService threadPool ;
@Override
public void run() {
while (!Thread.interrupted()) {
try {
//select 和register竞争锁会阻塞,需要有个超时时间
selector.select(100);
Set selected = selector.selectedKeys();
itertor(selected);
} catch (Exception ex) {
System.out.println(ex);
}
}
}
private void itertor(Set selected) {
Iterator it = selected.iterator();
while (it.hasNext()) {
SelectionKey next = (SelectionKey) it.next();
//线程不安全的
it.remove();
dispatch((SelectionKey) next,selected);
}
}
void dispatch(SelectionKey selectionKey, Set selected) {
try {
if (selectionKey.isAcceptable()) {
Acceptor r = (Acceptor) (selectionKey.attachment());
if (r != null) {
r.run();
}
}
if (selectionKey.isValid()&&(selectionKey.isReadable()||selectionKey.isWritable())) {
Runnable workerHandler = (Runnable) (selectionKey.attachment());
execute(workerHandler);
//线程不安全的,remove有问题
// selected.remove(selectionKey);
}
} catch (Exception e) {
e.printStackTrace();
}
}
void start() {
threadPool.execute(this);
}
void execute(Runnable runnable) {
}
}
/**
* main reactor 和sub reactor,io数据处理,在同一个线程
*轮询selector和分发客户端io连接事件给acceptor处理
*/
class MainReactor extends AbstractReactor {
MainReactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind( new InetSocketAddress(port));
serverSocket.configureBlocking(false);
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
//
sk.attach(new Acceptor(serverSocket,selector));
threadPool = Executors.newSingleThreadExecutor();
}
}
/**
* selectionkey和thread对应上
*/
final class SubReactor extends AbstractReactor{
//线程池模式
SubReactor(Selector sel, ServerSocketChannel c)
throws IOException {
threadPool = Executors.newFixedThreadPool(3);
selector = Selector.open();
}
public void registerInSubReactor(SocketChannel clientChannel) throws IOException {
clientChannel.configureBlocking(false);
SelectionKey selectionKey = clientChannel.register(selector, SelectionKey.OP_READ);
// selectionKey.interestOps();
selector.wakeup();
WorkerHandler workerHandler = new WorkerHandler(clientChannel,selectionKey);
//给selectionKey固定一个线程和handler去执行读写,netty eventgroup的目的?
selectionKey.attach(workerHandler);
workerHandler.registered(clientChannel);
}
/**
提交handler任务
*/
void execute(Runnable runnable) {
threadPool.execute(runnable);
}
}
/**
* 处理客户端socket连接事件,将读写事件给worker/hanlder处理
*/
class Acceptor {
ServerSocketChannel serverSocket;
private Selector selector;
SubReactor subReactor;
public Acceptor(ServerSocketChannel serverSocket,Selector selector) throws IOException {
this.serverSocket = serverSocket;
this.selector = selector;
this. subReactor = new SubReactor(selector, serverSocket);
subReactor.start();
}
public void run() {
try {
//客户端连接建立
SocketChannel client = serverSocket.accept();
if (client != null) {
System.out.println("收到连接请求");
//通过acceptor将mainreactor、subreactor连接起来
//netty handler对应
subReactor.registerInSubReactor(client);
}
} catch (IOException ex) {
System.out.println(ex);
ex.printStackTrace();
}
}
}
/**
* 一个channel一个handler
*/
public class WorkerHandler implements Runnable {
static final int READING = 0, SENDING = 1;
int state = READING;
final SocketChannel socket;
SelectionKey selectionKey;
ByteBuffer recvBuf = ByteBuffer.allocate(128);
ByteBuffer sendBuf = ByteBuffer.allocate(128);
public WorkerHandler(SocketChannel socket,SelectionKey selectionKey) {
this.selectionKey = selectionKey;
this.socket = socket;
}
/**
* 处理拆包,判断是否读完
* @return
*/
boolean readIsComplete(int l) {
//TODO
return true;
}
/**
* 处理粘包,判断是否写完
* @return
*/
boolean writeIsComplete(int l) {
//TODO
return true;
}
@Override
public void run() {
System.out.println(this+"运行在线程:"+Thread.currentThread());
try {
if (selectionKey.isReadable()) {
read();
} else if (selectionKey.isWritable()) {
send();
}else{
selectionKey.cancel();
socket.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
// handleInputData(socket,input, output);
}
//读数据
void read() throws IOException {
//read是线程安全的,枷锁,避免使用多线程访问同一个channel
int read = socket.read(recvBuf);
if (readIsComplete(read)) {
handleInputData(socket,recvBuf, sendBuf);
state = SENDING;
selectionKey.interestOps(SelectionKey.OP_WRITE);
}
}
//写数据
void send() throws IOException {
System.out.println("发送给客户端:"+socket+"数据:" + new String(sendBuf.array()));
sendBuf.flip();
if (socket.isConnected() && socket.isOpen()) {
int write = socket.write(sendBuf);
if (writeIsComplete(write)) {
// System.out.println("服务端写完了");
// selectionKey.cancel();
}
//需要注册其他事件否则一直会有可写事件
selectionKey.interestOps(SelectionKey.OP_READ);
}
}
void registered(SocketChannel socketChannel) {
System.out.println("新连接完成"+socketChannel);
}
/**
* 处理io数据包
* @param socket
* @param input
* @param output
*/
void handleInputData(SocketChannel socket, ByteBuffer input, ByteBuffer output) {
//TODO 处理读取的数据
//io读写模式
input.flip();
output.put(input);
System.out.println("收到客户端"+socket+"数据:" + new String(input.array()));
}
}
public class BootStrap {
public void start(int port) throws IOException {
AbstractReactor main = new MainReactor(port);
main.start();
}
}
public class ReactorTest {
public static void main(String[] args) throws IOException {
// new Thread(new MainReactor(6666)).start();
new BootStrap().start(6666);
System.in.read();
}
}
以上各个组件的实现如上所示,注释还算详细。
主要在mainreactor和subreactor两个类中,都有创建一个selector进行轮询io事件,mainreactor负责轮询accept事件,subreactor负责轮询读写事件。然后将事件分别发给acceptor和workerhandler。
最后,在ReactorTest启动这个server即可。其实,这也是netty的实现基本原理,但是netty处理了更多的细节,
更详细的netty解析,请看下一篇文章:手把手教你学习netty源码及原理。
有任何问题欢迎指出,共同交流。