Java|从操作系统层面分析 Java IO 演进之路( 三 )


clientBytes = new byte[buffer.limit()
; //从缓冲区 读取到内存中 buffer.get(clientBytes); System.out.println(socketChannel.socket().getPort() + \":\" + new String(clientBytes)); //清空缓冲区 buffer.clear();2 发生的系统调用
启动
socket(AF_INET SOCK_STREAM IPPROTO_IP) = 4bind(4 {sa_family=AF_INET sin_port=htons(9090) sin_addr=inet_addr(\"0.0.0.0\") 16) = 0listen(4 50)fcntl(4 F_SETFL O_RDWR|O_NONBLOCK) = 0epoll_create(256) = 7epoll_ctl(7 EPOLL_CTL_ADD 5 {EPOLLIN {u32=5 u64=4324783852322029573) = 0epoll_ctl(7 EPOLL_CTL_ADD 4 {EPOLLIN {u32=4 u64=158913789956) = 0epoll_wait(7 关于对epoll_create(对应着Java的 Selector selector = Selector.open()) 的说明 , 本质上是在内存的操作系统保留区 , 创建一个epoll数据结构 。 用于后面当有client连接时 , 向该epoll区中添加监听 。
有连接
epoll_wait(7[{EPOLLIN {u32=4 u64=158913789956
8192 -1) = 1accept(4 {sa_family=AF_INET sin_port=htons(29597) sin_addr=inet_addr(\"42.120.74.252\") [16
) = 8fcntl(8 F_SETFL O_RDWR|O_NONBLOCK) = 0epoll_ctl(7 EPOLL_CTL_ADD 8 {EPOLLIN {u32=8 u64=3212844375897800712) = 0 关于epoll_ctl (对应着Java的 client.register(selector SelectionKey.OP_READ) ) 。 其中 EPOLLIN 恰好对应着Java的 SelectionKey.OP_READ 即监听数据到达读取事件 。
客户端发送数据
epoll_wait(7[{EPOLLIN {u32=8 u64=3212844375897800712
8192 -1) = 1read(8 \"hellomultiplex\\" 4096) = 16epoll_wait(7 note:epoll_wait第四个参数-1表示block 。
poll 和 epoll 对比
根据“1.BIO”中的poll函数调用和epoll函数对比如下:


poll和epoll本质上都是同步IO ,区别于BIO的是 多路复用充分降低了 system call , 而epoll更进一步 , 再次降低了system call的时间复杂度 。
3 优缺点
优点
线程数同样很少 , 甚至可以把acceptor线程和worker线程使用同一个 。 时间复杂度低 , Java实现的Selector(在Linux OS下使用的epoll函数)支持多个clientChannel事件的一次性获取 , 且时间复杂度维持在O(1) 。 CPU使用低:得益于Selector , 我们不用向 “2.NIO”中需要自己一个个ClientChannel手动去检查事件 , 因此使得CPU使用率大大降低 。 缺点
数据处理麻烦:目前socketChannel.read 读取数据完全是基于字节的 , 当我们需要需要作为HTTP服务网关时 , 对于HTTP协议的处理完全需要自己解析 , 这是个庞大、烦杂、容易出错的工作 。 性能现有socket数据的读取(socketChannel.read(buffer))全部通过一个buffer 缓冲区来接受 , 一旦连接多起来 , 这无疑是一个单线程读取 , 性能无疑是个问题 。 那么此时buffer我们每次读取都重新new出来呢?如果每次都new出来 , 这样的内存碎片对于GC无疑是一场灾难 。 如何平衡地协调好buffer的共享 , 既保证性能 , 又保证线程安全 , 这是个难题 。 四 Netty 1 研究的目标源码(netty提供的入门example)
TelnetServer
package telnet;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.logging.LogLevel;import io.netty.handler.logging.LoggingHandler;import io.netty.handler.ssl.SslContext;import io.netty.handler.ssl.SslContextBuilder;import io.netty.handler.ssl.util.SelfSignedCertificate;/** * Simplistic telnet server. */public final class TelnetServer { static final boolean SSL = System.getProperty(\"ssl\") != null; static final int PORT = Integer.parseInt(System.getProperty(\"port\" SSL? \"8992\" : \"8023\")); public static void main(String[
args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate() ssc.privateKey()).build();else { sslCtx = null;EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new TelnetServerInitializer(sslCtx)); b.bind(PORT).sync().channel().closeFuture().sync();finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();TelnetServerHandler