参考
Linux 网络包发送过程:25 张图,一万字,拆解 Linux 网络包发送过程 (qq.com)
图解Linux网络包接收过程: 图解Linux网络包接收过程 (qq.com)
当内核收到了一个网络包:当内核收到了一个网络包 (qq.com)
epoll 是如何实现 IO 多路复用的:图解 | 深入揭秘 epoll 是如何实现 IO 多路复用的! (qq.com)
你管这破玩意叫 IO 多路复用: 你管这破玩意叫 IO 多路复用? (qq.com)
认认真真的聊聊中断: 认认真真的聊聊中断 (qq.com)
认认真真的聊聊"软"中断:认认真真的聊聊"软"中断 (qq.com)
【视频】netty视频
【书】计算网络自顶向下
【书】UNIX网络编程卷1:套接字联网API(第三版)
【书】图解TCP_IP
本文笔者只是做整合以及阅读总结,建议大家看看原文## 参考
Linux 网络包发送过程:25 张图,一万字,拆解 Linux 网络包发送过程 (qq.com)
图解Linux网络包接收过程: 图解Linux网络包接收过程 (qq.com)
当内核收到了一个网络包:当内核收到了一个网络包 (qq.com)
epoll 是如何实现 IO 多路复用的:图解 | 深入揭秘 epoll 是如何实现 IO 多路复用的! (qq.com)
你管这破玩意叫 IO 多路复用: 你管这破玩意叫 IO 多路复用? (qq.com)
认认真真的聊聊中断: 认认真真的聊聊中断 (qq.com)
认认真真的聊聊"软"中断:认认真真的聊聊"软"中断 (qq.com)
【视频】netty视频
【书】计算网络自顶向下
【书】UNIX网络编程卷1:套接字联网API(第三版)
【书】图解TCP_IP
本文笔者只是做整合以及阅读总结,建议大家看看原文
异步非阻塞I/O
顾名思义就是异步比如read事件,你只需要提共一个回调函数给read函数,那么它就会再读到数据之后直接调用用户回调函数。
AIO 用来解决数据复制阶段的阻塞问题
- 同步意味着,在进行读写操作时,线程需要等待结果,还是相当于闲置
- 异步意味着,在进行读写操作时,线程不必等待结果,而是将来由操作系统来通过回调方式由另外的线程来获得结果
异步模型需要底层操作系统(Kernel)提供支持
- Windows 系统通过 IOCP 实现了真正的异步 IO
- Linux 系统异步 IO 在 2.6 版本引入,但其底层实现还是用多路复用模拟了异步 IO,性能没有优势
网络AIO
先来看看 AsynchronousFileChannel
public class AioServer {
public static void main(String[] args) throws IOException {
AsynchronousServerSocketChannel ssc = AsynchronousServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8080));
ssc.accept(null, new AcceptHandler(ssc));
System.in.read();
}
private static void closeChannel(AsynchronousSocketChannel sc) {
try {
System.out.printf("[%s] %s close\n", Thread.currentThread().getName(), sc.getRemoteAddress());
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class ReadHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel sc;
public ReadHandler(AsynchronousSocketChannel sc) {
this.sc = sc;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
try {
if (result == -1) {
closeChannel(sc);
return;
}
System.out.printf("[%s] %s read\n", Thread.currentThread().getName(), sc.getRemoteAddress());
attachment.flip();
System.out.println(Charset.defaultCharset().decode(attachment));
attachment.clear();
// 处理完第一个 read 时,需要再次调用 read 方法来处理下一个 read 事件
sc.read(attachment, attachment, this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
closeChannel(sc);
exc.printStackTrace();
}
}
private static class WriteHandler implements CompletionHandler<Integer, ByteBuffer> {
private final AsynchronousSocketChannel sc;
private WriteHandler(AsynchronousSocketChannel sc) {
this.sc = sc;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
// 如果作为附件的 buffer 还有内容,需要再次 write 写出剩余内容
if (attachment.hasRemaining()) {
sc.write(attachment);
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
closeChannel(sc);
}
}
private static class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {
private final AsynchronousServerSocketChannel ssc;
public AcceptHandler(AsynchronousServerSocketChannel ssc) {
this.ssc = ssc;
}
@Override
public void completed(AsynchronousSocketChannel sc, Object attachment) {
try {
System.out.printf("[%s] %s connected\n", Thread.currentThread().getName(), sc.getRemoteAddress());
} catch (IOException e) {
e.printStackTrace();
}
ByteBuffer buffer = ByteBuffer.allocate(16);
// 读事件由 ReadHandler 处理
sc.read(buffer, buffer, new ReadHandler(sc));
// 写事件由 WriteHandler 处理
sc.write(Charset.defaultCharset().encode("server hello!"), ByteBuffer.allocate(16), new WriteHandler(sc));
// 处理完第一个 accpet 时,需要再次调用 accept 方法来处理下一个 accept 事件
ssc.accept(null, this);
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
}
}
可以看到
- 响应文件读取成功的是另一个线程 Thread-5
- 主线程并没有 IO 操作阻塞
总结
此小姐我们总结了4中I/O模式并逐个分析了优缺点,但其实还有一个信号驱动模式这里没有提及,想要了解的可以直接去看看《UNIX网络编程》这本书的第25章。从I/O演进来看其实每一中都是上一种IO的模式的迭代和改进,epoll和IOCP也不会是终点。此外虽然上层在疯狂变化但是底层仍然是TCP/IP,epoll的关键仍然有红黑树这种数据结构。可见这些基础的重要性,这是不变的东西。当你把另外一项技术研究到底的时候,你迟早会和他们再次相遇。