源码分析netty服务器创建过程vs java nio服务器创建
1.Java NIO服务端创建
首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写。
示例代码(参考文献【2】):
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.CharBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
- import java.nio.charset.Charset;
- import java.nio.charset.CharsetDecoder;
- import java.util.Iterator;
- import java.util.Set;
- /**
- * User: mihasya
- * Date: Jul 25, 2010
- * Time: 9:09:03 AM
- */
- public class JServer {
- public static void main (String[] args) {
- ServerSocketChannel sch = null;
- Selector sel = null;
- try {
- // setup the socket we're listening for connections on.
- InetSocketAddress addr = new InetSocketAddress(8400);
- sch = ServerSocketChannel.open();
- sch.configureBlocking(false);
- sch.socket().bind(addr);
- // setup our selector and register the main socket on it
- sel = Selector.open();
- sch.register(sel, SelectionKey.OP_ACCEPT);
- } catch (IOException e) {
- System.out.println("Couldn't setup server socket");
- System.out.println(e.getMessage());
- System.exit(1);
- }
- // fire up the listener thread, pass it our selector
- ListenerThread listener = new ListenerThread(sel);
- listener.run();
- }
- /*
- * the thread is completely unnecessary, it could all just happen
- * in main()
- */
- class ListenerThread extends Thread {
- Selector sel = null;
- ListenerThread(Selector sel) {
- this.sel = sel;
- }
- public void run() {
- while (true) {
- // our canned response for now
- ByteBuffer resp = ByteBuffer.wrap(new String("got it\n").getBytes());
- try {
- // loop over all the sockets that are ready for some activity
- while (this.sel.select() > 0) {
- Set keys = this.sel.selectedKeys();
- Iterator i = keys.iterator();
- while (i.hasNext()) {
- SelectionKey key = (SelectionKey)i.next();
- if (key.isAcceptable()) {
- // this means that a new client has hit the port our main
- // socket is listening on, so we need to accept the connection
- // and add the new client socket to our select pool for reading
- // a command later
- System.out.println("Accepting connection!");
- // this will be the ServerSocketChannel we initially registered
- // with the selector in main()
- ServerSocketChannel sch = (ServerSocketChannel)key.channel();
- SocketChannel ch = sch.accept();
- ch.configureBlocking(false);
- ch.register(this.sel, SelectionKey.OP_READ);
- } else if (key.isReadable()) {
- // one of our client sockets has received a command and
- // we're now ready to read it in
- System.out.println("Accepting command!");
- SocketChannel ch = (SocketChannel)key.channel();
- ByteBuffer buf = ByteBuffer.allocate(200);
- ch.read(buf);
- buf.flip();
- Charset charset = Charset.forName("UTF-8");
- CharsetDecoder decoder = charset.newDecoder();
- CharBuffer cbuf = decoder.decode(buf);
- System.out.print(cbuf.toString());
- // re-register this socket with the selector, this time
- // for writing since we'll want to write something to it
- // on the next go-around
- ch.register(this.sel, SelectionKey.OP_WRITE);
- } else if (key.isWritable()) {
- // we are ready to send a response to one of the client sockets
- // we had read a command from previously
- System.out.println("Sending response!");
- SocketChannel ch = (SocketChannel)key.channel();
- ch.write(resp);
- resp.rewind();
- // we may get another command from this guy, so prepare
- // to read again. We could also close the channel, but
- // that sort of defeats the whole purpose of doing async
- ch.register(this.sel, SelectionKey.OP_READ);
- }
- i.remove();
- }
- }
- } catch (IOException e) {
- System.out.println("Error in poll loop");
- System.out.println(e.getMessage());
- System.exit(1);
- }
- }
- }
- }
- }
从上面的代码可以看出java nio的通用步骤:
1.打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父通道,绑定监听端口,设置客户端连接方式为非阻塞模式。
2.打开多路复用器并启动服务端监听线程,将ServerSocketChannel注册到Reactor线程的多路复用器Selector上,监听ACCEPT状态。
3.多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手后,与客户端建立物理链路。
2. Netty服务端创建
2.1 打开ServerSocketChannel
- ServerBootstrap b = new ServerBootstrap();
- b.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
创建一个ServerSocketChannel的过程:
- /**
- * Create a new instance
- */
- public NioServerSocketChannel() {
- this(newSocket(DEFAULT_SELECTOR_PROVIDER));
- }
调用newSocket方法:
- private static ServerSocketChannel newSocket(SelectorProvider provider) {
- try {
- /**
- * Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
- * {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
- *
- * See <a href="See https://github.com/netty/netty/issues/2308">#2308</a>.
- */
- return provider.openServerSocketChannel();
- } catch (IOException e) {
- throw new ChannelException(
- "Failed to open a server socket.", e);
- }
- }
其中的provider.openServerSocketChannel()就是java nio的实现。设置非阻塞模式包含在父类中:
- /**
- * Create a new instance
- *
- * @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
- * @param ch the underlying {@link SelectableChannel} on which it operates
- * @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
- */
- protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
- super(parent);
- this.ch = ch;
- this.readInterestOp = readInterestOp;
- try {
- ch.configureBlocking(false);
- } catch (IOException e) {
- try {
- ch.close();
- } catch (IOException e2) {
- if (logger.isWarnEnabled()) {
- logger.warn(
- "Failed to close a partially initialized socket.", e2);
- }
- }
- throw new ChannelException("Failed to enter non-blocking mode.", e);
- }
- }
2.2 打开多路复用器过程
NioEventLoop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合,相关代码如下:
- @Override
- protected void run() {
- for (;;) {
- boolean oldWakenUp = wakenUp.getAndSet(false);
- try {
- if (hasTasks()) {
- selectNow();
- } else {
- select(oldWakenUp);
- // 'wakenUp.compareAndSet(false, true)' is always evaluated
- // before calling 'selector.wakeup()' to reduce the wake-up
- // overhead. (Selector.wakeup() is an expensive operation.)
- //
- // However, there is a race condition in this approach.
- // The race condition is triggered when 'wakenUp' is set to
- // true too early.
- //
- // 'wakenUp' is set to true too early if:
- // 1) Selector is waken up between 'wakenUp.set(false)' and
- // 'selector.select(...)'. (BAD)
- // 2) Selector is waken up between 'selector.select(...)' and
- // 'if (wakenUp.get()) { ... }'. (OK)
- //
- // In the first case, 'wakenUp' is set to true and the
- // following 'selector.select(...)' will wake up immediately.
- // Until 'wakenUp' is set to false again in the next round,
- // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
- // any attempt to wake up the Selector will fail, too, causing
- // the following 'selector.select(...)' call to block
- // unnecessarily.
- //
- // To fix this problem, we wake up the selector again if wakenUp
- // is true immediately after selector.select(...).
- // It is inefficient in that it wakes up the selector for both
- // the first case (BAD - wake-up required) and the second case
- // (OK - no wake-up required).
- if (wakenUp.get()) {
- selector.wakeup();
- }
- }
- cancelledKeys = 0;
- needsToSelectAgain = false;
- final int ioRatio = this.ioRatio;
- if (ioRatio == 100) {
- processSelectedKeys();
- runAllTasks();
- } else {
- final long ioStartTime = System.nanoTime();
- processSelectedKeys();
- final long ioTime = System.nanoTime() - ioStartTime;
- runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
- }
- if (isShuttingDown()) {
- closeAll();
- if (confirmShutdown()) {
- break;
- }
- }
- } catch (Throwable t) {
- logger.warn("Unexpected exception in the selector loop.", t);
- // Prevent possible consecutive immediate failures that lead to
- // excessive CPU consumption.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- // Ignore.
- }
- }
- }
- }
2.2.1 绑定处理的key
- private void processSelectedKeys() {
- if (selectedKeys != null) {
- processSelectedKeysOptimized(selectedKeys.flip());
- } else {
- processSelectedKeysPlain(selector.selectedKeys());
- }
- }
以processSelectedKeysPlain为例:
- private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
- // check if the set is empty and if so just return to not create garbage by
- // creating a new Iterator every time even if there is nothing to process.
- // See https://github.com/netty/netty/issues/597
- if (selectedKeys.isEmpty()) {
- return;
- }
- Iterator<SelectionKey> i = selectedKeys.iterator();
- for (;;) {
- final SelectionKey k = i.next();
- final Object a = k.attachment();
- i.remove();
- if (a instanceof AbstractNioChannel) {
- processSelectedKey(k, (AbstractNioChannel) a);
- } else {
- @SuppressWarnings("unchecked")
- NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
- processSelectedKey(k, task);
- }
- if (!i.hasNext()) {
- break;
- }
- if (needsToSelectAgain) {
- selectAgain();
- selectedKeys = selector.selectedKeys();
- // Create the iterator again to avoid ConcurrentModificationException
- if (selectedKeys.isEmpty()) {
- break;
- } else {
- i = selectedKeys.iterator();
- }
- }
- }
- }
2.3 绑定端口,接收请求:
- // Bind and start to accept incoming connections.
- ChannelFuture f = b.bind(PORT).sync();
调用bind程序
- private ChannelFuture doBind(final SocketAddress localAddress) {
- final ChannelFuture regFuture = initAndRegister();
- final Channel channel = regFuture.channel();
- if (regFuture.cause() != null) {
- return regFuture;
- }
- if (regFuture.isDone()) {
- // At this point we know that the registration was complete and successful.
- ChannelPromise promise = channel.newPromise();
- doBind0(regFuture, channel, localAddress, promise);
- return promise;
- } else {
- // Registration future is almost always fulfilled already, but just in case it's not.
- final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
- regFuture.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) throws Exception {
- Throwable cause = future.cause();
- if (cause != null) {
- // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
- // IllegalStateException once we try to access the EventLoop of the Channel.
- promise.setFailure(cause);
- } else {
- // Registration was successful, so set the correct executor to use.
- // See https://github.com/netty/netty/issues/2586
- promise.executor = channel.eventLoop();
- }
- doBind0(regFuture, channel, localAddress, promise);
- }
- });
- return promise;
- }
- }
最终的绑定由dobind0来完成
- private static void doBind0(
- final ChannelFuture regFuture, final Channel channel,
- final SocketAddress localAddress, final ChannelPromise promise) {
- // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
- // the pipeline in its channelRegistered() implementation.
- channel.eventLoop().execute(new OneTimeTask() {
- @Override
- public void run() {
- if (regFuture.isSuccess()) {
- channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
- } else {
- promise.setFailure(regFuture.cause());
- }
- }
- });
- }
具体实现:
- private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
- final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
- if (!k.isValid()) {
- // close the channel if the key is not valid anymore
- unsafe.close(unsafe.voidPromise());
- return;
- }
- try {
- int readyOps = k.readyOps();
- // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
- // to a spin loop
- if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
- unsafe.read();
- if (!ch.isOpen()) {
- // Connection already closed - no need to handle write.
- return;
- }
- }
- if ((readyOps & SelectionKey.OP_WRITE) != 0) {
- // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
- ch.unsafe().forceFlush();
- }
- if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
- // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
- // See https://github.com/netty/netty/issues/924
- int ops = k.interestOps();
- ops &= ~SelectionKey.OP_CONNECT;
- k.interestOps(ops);
- unsafe.finishConnect();
- }
- } catch (CancelledKeyException ignored) {
- unsafe.close(unsafe.voidPromise());
- }
- }
参考文献
【1】http://www.infoq.com/cn/articles/netty-server-create
【2】https://github.com/mihasya/sample-java-nio-server/blob/master/src/JServer.java
源码分析netty服务器创建过程vs java nio服务器创建的更多相关文章
- Spring Ioc源码分析系列--Bean实例化过程(一)
Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- Spring源码分析之`BeanFactoryPostProcessor`调用过程
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...
- Spring Ioc源码分析系列--Bean实例化过程(二)
Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...
- 【Netty源码分析】发送数据过程
前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...
- tomcat8 源码分析 | 组件及启动过程
tomcat 8 源码分析 ,本文主要讲解tomcat拥有哪些组件,容器,又是如何启动的 推荐访问我的个人网站,排版更好看呦: https://chenmingyu.top/tomcat-source ...
- spark 源码分析之五 -- Spark内置RPC机制剖析之一创建NettyRpcEnv
在前面源码剖析介绍中,spark 源码分析之二 -- SparkContext 的初始化过程 中的SparkEnv和 spark 源码分析之四 -- TaskScheduler的创建和启动过程 中的C ...
- MyBatis 源码分析 - SQL 的执行过程
* 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析 ...
- 【Canal源码分析】parser工作过程
本文主要分析的部分是instance启动时,parser的一个启动和工作过程.主要关注的是AbstractEventParser的start()方法中的parseThread. 一.序列图 二.源码分 ...
随机推荐
- C++实现线程安全的单例模式
在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 template <class T> class sing ...
- load和initialize方法
一.load 方法什么时候调用: 在main方法还没执行的时候 就会 加载所有类,调用所有类的load方法. load方法是线程安全的,它使用了锁,我们应该避免线程阻塞在load方法. 在项目中使 ...
- 从零开始编写自己的C#框架(24)——测试
导航 1.前言 2.不堪回首的开发往事 3.测试推动开发的成长——将Bug消灭在自测中 4.关于软件测试 5.制定测试计划 6.编写测试用例 7.执行测试用例 8.发现并提交Bug 9.开发人员修复B ...
- node.js学习(二)--Node.js控制台(REPL)&&Node.js的基础和语法
1.1.2 Node.js控制台(REPL) Node.js也有自己的虚拟的运行环境:REPL. 我们可以使用它来执行任何的Node.js或者javascript代码.还可以引入模块和使用文件系统. ...
- AI人工智能系列随笔:syntaxnet 初探(1)
人工智能是 最近的一个比较火的名词,相信大家对于阿尔法狗都不陌生吧?其实我对人工智能以前也是非常抵触的,因为我认为机器人会取代人类,成为地球乃至宇宙的霸主,但是人工智能带给我的这种冲击,我个人感觉是欲 ...
- 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- SOLID 设计原则
SOLID 原则基本概念: 程序设计领域, SOLID (单一功能.开闭原则.里氏替换.接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期 引入的记忆术首字母缩略字,指代了面向对象编程和面向对象 ...
- 初识JavaScript
JavaScript ECMA-262: 变量,函数,对象,数据类型....唯独没有输入和输出. Javascript:包含 ECMA-262,核心 BOM 浏览器对象模型, DOM 文档对象模型 什 ...
- AEAI DP V3.6.0 升级说明,开源综合应用开发平台
AEAI DP综合应用开发平台是一款扩展开发工具,专门用于开发MIS类的Java Web应用,本次发版的AEAI DP_v3.6.0版本为AEAI DP _v3.5.0版本的升级版本,该产品现已开源并 ...
- Atitit 管理原理与实践attilax总结
Atitit 管理原理与实践attilax总结 1. 管理学分类1 2. 我要学的管理学科2 3. 管理学原理2 4. 管理心理学2 5. 现代管理理论与方法2 6. <领导科学与艺术4 7. ...