以下为个人学习心得,只提供参考
一、阻塞io:
public void runServer(int port) throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String msg = new String(bytes, StandardCharsets.UTF_8);
System.out.println("我收到了消息" + msg);
}
}
下一个连接必须等待上一个连接IO处理完毕
二、伪异步io
伪异步IO指的是在接受网络套接字后,开启一个业务线程去处理IO事件。本质上还是采用上面的阻塞IO模型
//加上线程池
public ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
10, 0, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10),
new ThreadFactory() {
AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Thread-name" + atomicInteger.addAndGet(1));
}
}, new ThreadPoolExecutor.CallerRunsPolicy());
public void runServer(int port) throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
threadPoolExecutor.execute(() -> {
try {
handler(serverSocket.accept());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
}
}
三、非阻塞IO:
就是询问内核是否有数据,如果没数据此时不会阻塞会立即返回EWOULDBLOCK。虽然是非阻塞但是是需要不断的轮询。
1、IO多路复用:
如果使用io多路复用,则只需要一个单线程调用select,这时会等待多路复用器返回就绪状态集合。此时应用程序就可以调用recvfrom直接读取已就绪的数据了。整个过程我们只需要一个线程去向内核获取就绪状态的操作。io多路复用器帮我们获取并发的连接。
常见的多路复用器:select,poll,epoll
select: 只知道了有IO事件发生,不知道有几个IO事件,只能轮询所有的连接,时间复杂度为O(n),有连接数量的限制
poll: 和select一样。但是连接数无限制
epoll:直接将对应的连接发生了什么类型的事件告诉我们 速度上体现为O(1)
总结:阻塞IO:应用程序处理多条连接请求,需要一个一个等待accept()
非阻塞IO(多路复用):I/O多路复用器帮我们获取多个套接字的就绪状态,应用程序可以并发的去读取数据处理业务逻辑。
Reactor:
reactor线程模型是基于事件驱动的非阻塞线程模型。Reactor根据IO事件调用相应的处理器进行网络IO操作,如:连接请求调用Acceptor进行处理,IO读写事件调用读写处理器进行处理。
其中最主要的部件是IO多路复用器,没有IO多路复用器reactor线程模型就无法达到效果。IO多路复用器是底层操作系统提供的。比如select、poll、epoll。当有IO事件处于就绪状态时多路复用器就会返回给应用程序。
1、单线程Reactor:
代码如下:
public class SingleReactor {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
public SingleReactor() throws IOException {
serverSocketChannel = ServerSocketChannel.open();
//选出一个多路复用器
selector = Selector.open();
//非阻塞模式
serverSocketChannel.configureBlocking(false);
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
//新建ServerSocketChannel,并添加到Selector中,注册Accept为感兴趣事件
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//为Accept事件绑定 Acceptor处理器
selectionKey.attach(new Acceptor(serverSocketChannel, selector));
}
public void start() {
try {
while (!Thread.currentThread().isInterrupted()) {
//无事件阻塞,有事件立即返回
selector.select();
//一次性获得多个就绪状态的selectionKey
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
//执行处理器
dispatch(selectionKey);
}
iterator.remove();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void dispatch(SelectionKey selectionKey) {
//执行当前事件的处理器(如果程序刚跑第一次,此时的channel只有ServerSocketChannel必然为连接事件,就会执行Acceptor)
Handler eventHandler = (Handler) selectionKey.attachment();
eventHandler.execute();
}
}
//Acceptor
public class Acceptor implements Handler {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
public Acceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
this.serverSocketChannel = serverSocketChannel;
this.selector = selector;
}
@Override
public void execute() {
try {
SocketChannel socketChannel = serverSocketChannel.accept();
//selector注册socketChannel
new EventHandler(socketChannel, selector);
}catch (Exception e){
e.printStackTrace();
}
}
}
public class EventHandler implements Handler {
private SocketChannel socketChannel;
private SelectionKey selectionKey;
public EventHandler(SocketChannel socketChannel, Selector selector) throws IOException {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
selectionKey.attach(this);
}
@Override
public void execute() {
doExecute();
}
private void doExecute() {
if (selectionKey.isReadable()) {
System.out.println(Thread.currentThread().getName() + ":读ready");
}
}
}
单线程reactor由于处理IO事件和接受连接都是串行执行,面对多并发请求,吞吐量显然不够
2、多线程Reactor:
相比于单线程reactor这里只是在处理io事件时采用业务线程池进行处理。这种单线程处理连接,多线程处理I/O的模式大部分情况下其实够用了。比如netty中这样设置:
EventLoopGroup boss=new NioEventLoopGroup(1);
EventLoopGroup worker=new NioEventLoopGroup();
3、主从Reactor多线程:
相比于多线程Reactor,此处的连接请求处理也放入有一个业务线程池处理。主要是因为如果是大量客户的连接单线程去接受连接事件显然吞吐量是不够的。日常开发中像redis这种面向的是服务端web应用这样子,单线程就足够了。而像移动端连接到服务端的显然适用于主从Reactor多线程模式。
netty可以这样配置:
EventLoopGroup boss=new NioEventLoopGroup();
EventLoopGroup worker=new NioEventLoopGroup();
代码实现:
//主Reactor
public class MainReactor {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private Executor executor;
public MainReactor() {
try {
executor = Executors.newFixedThreadPool(4);
serverSocketChannel = ServerSocketChannel.open();
selector = Selector.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
selectionKey.attach(new Acceptor(serverSocketChannel));
} catch (Exception e) {
e.printStackTrace();
}
}
public void start() {
try {
while (!Thread.currentThread().isInterrupted()) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
selectionKeys.remove(selectionKey);
if (selectionKey.isAcceptable()) {
Acceptor acceptor = (Acceptor) selectionKey.attachment();
SocketChannel socketChannel = serverSocketChannel.accept();
executor.execute(() -> {
try {
acceptor.addSocketChannel(socketChannel);
selector.wakeup();
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//Acceptor
public class Acceptor {
private ServerSocketChannel serverSocketChannel;
private SubReactor subReactor;
public Acceptor(ServerSocketChannel serverSocketChannel) {
this.serverSocketChannel = serverSocketChannel;
subReactor = new SubReactor();
}
public void addSocketChannel(SocketChannel socketChannel) throws IOException {
subReactor.addSocketChannel(socketChannel);
}
}
//从Reactor
public class SubReactor {
private Selector selector;
private Executor executor;
ByteBuffer allocate = ByteBuffer.allocate(1024);
public SubReactor() {
try {
selector = Selector.open();
executor = Executors.newFixedThreadPool(4);
select();
} catch (IOException e) {
e.printStackTrace();
}
}
public void addSocketChannel(SocketChannel socketChannel) {
try {
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public void select() {
executor.execute(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
selector.selectNow();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isReadable()) {
readOrWrite(selectionKey);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void readOrWrite(SelectionKey selectionKey) {
try {
SocketChannel channel = (SocketChannel) selectionKey.channel();
if (selectionKey.isReadable()) {
channel.read(allocate);
String s = new String(allocate.array(), StandardCharsets.UTF_8);
System.out.println(s);
allocate.clear();
selectionKey.interestOps(SelectionKey.OP_WRITE);
} else if (selectionKey.isWritable()) {
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里贴的代码完全是按照自己的理解写的,可能有不对的地方,部分概念参考了《Netty权威指南》
开发中最好还是用Netty,解决或避免了不少JDK NIO包的bug。比如空轮询。