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服务器创建的更多相关文章

  1. Spring Ioc源码分析系列--Bean实例化过程(一)

    Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...

  2. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  3. Spring源码分析之`BeanFactoryPostProcessor`调用过程

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...

  4. Spring Ioc源码分析系列--Bean实例化过程(二)

    Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...

  5. 【Netty源码分析】发送数据过程

    前面两篇博客[Netty源码分析]Netty服务端bind端口过程和[Netty源码分析]客户端connect服务端过程中我们分别介绍了服务端绑定端口和客户端连接到服务端的过程,接下来我们分析一下数据 ...

  6. tomcat8 源码分析 | 组件及启动过程

    tomcat 8 源码分析 ,本文主要讲解tomcat拥有哪些组件,容器,又是如何启动的 推荐访问我的个人网站,排版更好看呦: https://chenmingyu.top/tomcat-source ...

  7. spark 源码分析之五 -- Spark内置RPC机制剖析之一创建NettyRpcEnv

    在前面源码剖析介绍中,spark 源码分析之二 -- SparkContext 的初始化过程 中的SparkEnv和 spark 源码分析之四 -- TaskScheduler的创建和启动过程 中的C ...

  8. MyBatis 源码分析 - SQL 的执行过程

    * 本文速览 本篇文章较为详细的介绍了 MyBatis 执行 SQL 的过程.该过程本身比较复杂,牵涉到的技术点比较多.包括但不限于 Mapper 接口代理类的生成.接口方法的解析.SQL 语句的解析 ...

  9. 【Canal源码分析】parser工作过程

    本文主要分析的部分是instance启动时,parser的一个启动和工作过程.主要关注的是AbstractEventParser的start()方法中的parseThread. 一.序列图 二.源码分 ...

随机推荐

  1. 网站定位之---根据IP获得区域

    记得以前做一个培训机构网站时候需要定位,那时候用的搜狐的api,不是很精准. demo:https://github.com/dunitian/LoTCodeBase/tree/master/NetC ...

  2. 参考bootstrap中的popover.js的css画消息弹框

    前段时间小颖的大学同学给小颖发了一张截图,图片类似下面这张图: 小颖当时大概的给她说了下,其实小颖也不知道上面那个三角形怎么画嘻嘻,给她说了DOM结构,具体的css让她自己百度,今天小颖自己参考boo ...

  3. JS继承之寄生类继承

    原型式继承 其原理就是借助原型,可以基于已有的对象创建新对象.节省了创建自定义类型这一步(虽然觉得这样没什么意义). 模型 function object(o){ function W(){ } W. ...

  4. 以项目谈WebGIS中Web制图的设计和实现

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景介绍 一般WebGIS项目中,前端展示数据的流程基本是先做数据入 ...

  5. php实现的分页类

    php分页类文件: <?php /** file: page.class.php 完美分页类 Page */ class Page { private $total; //数据表中总记录数 pr ...

  6. MFC中成员变量的声明顺序与析构顺序

    第一次用博客,第一篇随笔,就写今天遇到的一个问题吧. 在VS2008的MFC对话框程序,窗口成员变量的声明顺序与其析构顺序相反,即,先声明的变量后析构,后声明的变量先析构.未在其他模式下测试. cla ...

  7. Unable to create the selected property page. An error occurred while automatically activating bundle net.sourceforge.pmd

    解决方案: 在命令行到eclipse目录下使用 eclipse.exe -clean

  8. 信息安全-1:python之playfair密码算法详解[原创]

    转发注明出处: http://www.cnblogs.com/0zcl/p/6105825.html 一.基本概念 古典密码是基于字符替换的密码.加密技术有:Caesar(恺撒)密码.Vigenere ...

  9. Oracle中的commit详解

    本文转自 : http://blog.csdn.net/hzhsan/article/details/9719307 它执行的时候,你不会有什么感觉.commit在数据库编程的时候很常用,当你执行DM ...

  10. ASP.Net MVC 5 in Xamarin Studio 5.2

    Xamarin Studio 是一个Mono的跨平台 IDE(Integrated Development Environment),支持Wiindow和Mac,最新发布的5.2 版本支持ASP.NE ...