1.背景

通过JavaNio实现一个简单的网络模块,有点像Netty的线程模型,一个线程(AcceptThread)建立新连接,把新连接绑定到某个SelectorThread,SelectorThread处理读/写。

  • AcceptThread:拥有一个Selector,上面只注册了一个ServerSocketChannel,监听客户端新连接,当接收到新连接时,把新连接注册到SelectorThread上。
  • SelectorThread:拥有一个Selector,用于注册客户端连接,并且处理读/写事件。

2.代码

public class NonblockingServer {
/** 处理客户端读写 */
private SelectorThread[] selectorThreads;
/** 建立新连接 */
private AcceptThread acceptThread;
/** 选择器 */
private SelectNextSelectorThread nextSelectorThread;
/** 默认selectorThread数量 */
private static int defaultSelectorNum = 3; public NonblockingServer() {
this(defaultSelectorNum);
} public NonblockingServer(int selectorThreadNum) {
if (selectorThreadNum < 1) {
throw new IllegalArgumentException("SelectorThread线程数量不能低于1");
}
selectorThreads = new SelectorThread[selectorThreadNum];
try {
for (int i = 0; i < selectorThreads.length; i++) {
selectorThreads[i] = new SelectorThread("selector-thread-" + i);
}
acceptThread = new AcceptThread("accept-thread");
nextSelectorThread = new SelectNextSelectorThread(Arrays.asList(selectorThreads));
} catch (Exception e) {
throw new RuntimeException(e);
}
} public void listen(String host, int port) {
try {
acceptThread.listen(host, port);
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* 处理连接事件,把新连接注册到一个SelectorThread上
*/
class AcceptThread extends Thread {
private Selector selector;
private ServerSocketChannel serverChannel; AcceptThread(String name) throws IOException {
super(name);
selector = Selector.open();
} public void listen(String host, int port) throws IOException {
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
serverChannel.bind(new InetSocketAddress(host, port));
this.start();
} @Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
int select = selector.select();
if (select == 0) {
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
handAccept();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} private void handAccept() throws IOException {
SocketChannel clientChannel = serverChannel.accept();
SelectorThread selectorThread = nextSelectorThread.nextThread();
// 懒加载
if (!selectorThread.isStart()) {
selectorThread.start();
}
selectorThread.register(clientChannel);
}
} /**
* 处理客户端连接的读/写事件
*/
class SelectorThread extends Thread {
private Selector selector;
private volatile boolean start = false; SelectorThread(String name) throws IOException {
super(name);
selector = Selector.open();
} @Override
public void run() {
while (start) {
try {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (start && iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
SocketChannel channel = (SocketChannel) key.channel();
if (key.isReadable()) {
handRead(channel);
} else if (key.isWritable()) {
handWrite(channel);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* 这里简单的把输出打印出来
*/
private void handRead(SocketChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if (read == -1) {
System.out.println(channel.getRemoteAddress() + " 断开连接");
channel.close();
} else {
byte[] bytes = new byte[read];
buffer.flip();
buffer.get(bytes);
System.out.println(new String(bytes));
buffer.clear();
}
} private void handWrite(SocketChannel channel) {
// nothing
} /**
* 把新连接注册到本线程
*/
public void register(SocketChannel clientChannel) throws IOException {
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} @Override
public synchronized void start() {
start = true;
super.start();
} public void shutdown() {
this.start = false;
this.interrupt();
} public boolean isStart() {
return start;
}
} static class SelectNextSelectorThread {
private final Collection<? extends SelectorThread> threads;
private Iterator<? extends SelectorThread> iterator; public <T extends SelectorThread> SelectNextSelectorThread(Collection<T> threads) {
this.threads = threads;
iterator = this.threads.iterator();
} /**
* 选择下一个SelectorThread,这里为轮询
*
* @return SelectorThread
*/
public SelectorThread nextThread() {
if (!iterator.hasNext()) {
iterator = threads.iterator();
}
return iterator.next();
}
}
}

NonblockingServer

3.问题

当用客户端连接几次后,发现AcceptThread被卡死了!新连接无法建立。
用jvisualVM可以看到AcceptThread状态为监视,说明此时的AcceptThread在等待某个锁。


通过IDEA的dump threads也可以看到:

观察SelectThread线程:

SelectorImpl.lockAndDoSelect代码:

    private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
if (!this.isOpen()) {
throw new ClosedSelectorException();
} else {
int var10000;
synchronized(this.publicKeys) {
synchronized(this.publicSelectedKeys) {
var10000 = this.doSelect(var1);
}
} return var10000;
}
}
}

SelectorImpl.register代码:

    protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
if (!(var1 instanceof SelChImpl)) {
throw new IllegalSelectorException();
} else {
SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
var4.attach(var3);
synchronized(this.publicKeys) {
this.implRegister(var4);
} var4.interestOps(var2);
return var4;
}
}

这两个方法都需要获取this.publicKeys的锁,经过调试发现,SeletorThread在获取Selector的产生的事件时selector.select(),一直在不断的循环执行SelectorImpl.lockAndDoSelect方法,一直在不停的获取this.publicKeys的锁,所以这里的register方法很难有机会拿到this.publicKeys的锁,AcceptThread就卡死在获取锁的过程中了。

4.解决方法

Netty在注册新连接时,是把这个注册过程封装成一个任务,交给SelectorThread执行的,就不会发生线程冲突。

Thrift是把新连接放到SelecttorThread中的BlockingQueue中,也是由SelectorThread执行的。

5.修正后的代码

public class NonblockingServer {
/** 处理客户端读写 */
private SelectorThread[] selectorThreads;
/** 建立新连接 */
private AcceptThread acceptThread;
/** 选择器 */
private SelectNextSelectorThread nextSelectorThread;
/** 默认selectorThread数量 */
private static int defaultSelectorNum = 3; public NonblockingServer() {
this(defaultSelectorNum);
} public NonblockingServer(int selectorThreadNum) {
if (selectorThreadNum < 1) {
throw new IllegalArgumentException("SelectorThread线程数量不能低于1");
}
selectorThreads = new SelectorThread[selectorThreadNum];
try {
for (int i = 0; i < selectorThreads.length; i++) {
selectorThreads[i] = new SelectorThread("selector-thread-" + i);
}
acceptThread = new AcceptThread("accept-thread");
nextSelectorThread = new SelectNextSelectorThread(Arrays.asList(selectorThreads));
} catch (Exception e) {
throw new RuntimeException(e);
}
} public void listen(String host, int port) {
try {
acceptThread.listen(host, port);
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* 处理连接事件,把新连接注册到一个SelectorThread上
*/
class AcceptThread extends Thread {
private Selector selector;
private ServerSocketChannel serverChannel; AcceptThread(String name) throws IOException {
super(name);
selector = Selector.open();
} public void listen(String host, int port) throws IOException {
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
serverChannel.bind(new InetSocketAddress(host, port));
this.start();
} @Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
int select = selector.select();
if (select == 0) {
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
handAccept();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} private void handAccept() throws IOException {
SocketChannel clientChannel = serverChannel.accept();
SelectorThread selectorThread = nextSelectorThread.nextThread();
// 懒加载
if (!selectorThread.isStart()) {
selectorThread.start();
}
selectorThread.register(clientChannel);
}
} /**
* 处理客户端连接的读/写事件
*/
class SelectorThread extends Thread {
private Selector selector;
private BlockingQueue<Runnable> tasks;
private volatile boolean start = false; SelectorThread(String name) throws IOException {
super(name);
selector = Selector.open();
tasks = new LinkedBlockingQueue<>();
} @Override
public void run() {
while (start) {
try {
selector.select();
handTask();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (start && iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
SocketChannel channel = (SocketChannel) key.channel();
if (key.isReadable()) {
handRead(channel);
} else if (key.isWritable()) {
handWrite(channel);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} private void handTask() {
Runnable task;
while (start && (task = tasks.poll()) != null) {
task.run();
}
} /**
* 这里简单的把输出打印出来
*/
private void handRead(SocketChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if (read == -1) {
System.out.println(channel.getRemoteAddress() + " 断开连接");
channel.close();
} else {
byte[] bytes = new byte[read];
buffer.flip();
buffer.get(bytes);
System.out.println(new String(bytes));
buffer.clear();
}
} private void handWrite(SocketChannel channel) {
// nothing
} /**
* 把新连接注册到本线程
*/
public void register(SocketChannel clientChannel) {
submit(() -> {
try {
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
} catch (Exception e) {
try {
clientChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
selector.wakeup();
} public void submit(Runnable task) {
tasks.offer(task);
} @Override
public synchronized void start() {
start = true;
super.start();
} public void shutdown() {
this.start = false;
this.interrupt();
} public boolean isStart() {
return start;
}
} static class SelectNextSelectorThread {
private final Collection<? extends SelectorThread> threads;
private Iterator<? extends SelectorThread> iterator; public <T extends SelectorThread> SelectNextSelectorThread(Collection<T> threads) {
this.threads = threads;
iterator = this.threads.iterator();
} /**
* 选择下一个SelectorThread,这里为轮询
*
* @return SelectorThread
*/
public SelectorThread nextThread() {
if (!iterator.hasNext()) {
iterator = threads.iterator();
}
return iterator.next();
}
}
}

NonblockingServer

【记录】Java NIO实现网络模块遇到的BUG的更多相关文章

  1. JAVA NIO学习记录1-buffer和channel

    什么是NIO? Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO与原来的IO有同样的作用和目的,但是使用的方式完全不 ...

  2. Java NIO学习与记录(八): Reactor两种多线程模型的实现

    Reactor两种多线程模型的实现 注:本篇文章例子基于上一篇进行:Java NIO学习与记录(七): Reactor单线程模型的实现 紧接着上篇Reactor单线程模型的例子来,假设Handler的 ...

  3. java nio的一个严重BUG

    java nio的一个严重BUG Posted on 2009-09-28 19:27 dennis 阅读(4588) 评论(5)  编辑  收藏 所属分类: java .源码解读      这个BU ...

  4. Java nio 空轮询bug到底是什么

    编者注:Java nio 空轮询bug也就是Java nio在Linux系统下的epoll空轮询问题. epoll机制是Linux下一种高效的IO复用方式,相较于select和poll机制来说.其高效 ...

  5. JAVA NIO学习记录2-非阻塞式网络通信

    一.阻塞与非阻塞 传统的IO 流都是阻塞式的.也就是说,当一个线程调用read() 或write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务.因此,在完成网络通信 ...

  6. Java NIO学习与记录(五): 操作系统的I/O模型

    操作系统的I/O模型 在开始介绍NIO Reactor模式之前,先来介绍下操作系统的五种I/O模型,了解了这些模型,对理解java nio会有不小的帮助. 先来看下一个服务端处理一次网络请求的流程图: ...

  7. Java NIO 入门

    本文主要记录 Java 中  NIO 相关的基础知识点,以及基本的使用方式. 一.回顾传统的 I/O 刚接触 Java 中的 I/O 时,使用的传统的 BIO 的 API.由于 BIO 设计的类实在太 ...

  8. 源码分析netty服务器创建过程vs java nio服务器创建

    1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...

  9. java nio系列文章

    java nio系列教程 基于NIO的Client/Server程序实践 (推荐) java nio与并发编程相关电子书籍   (访问密码 48dd) 理解NIO nio学习记录 图解ByteBuff ...

随机推荐

  1. boost timer

    Boost.Timer provides clocks to measure code performance. At first, it may seem like this library com ...

  2. boost location-independent times

    The class boost::posix_time::ptime defindes a location-independent time. It uses the type boost::gre ...

  3. spring-boot的helloWorld详解

    1.运行环境 开发工具:intellij idea JDK版本:1.8 项目管理工具:Maven 3.2.5 2.Maven Plugin管理 pom.xml配置代码: <project xml ...

  4. php chr()函数 语法

    php chr()函数 语法 作用:从指定的 ASCII 值返回字符.直线电机选型 语法:chr(ascii) 参数: 参数 描述 ascii  必须,指定ASCII值 说明:chr() 函数从指定的 ...

  5. LUOGU P3380 【模板】二逼平衡树(树套树)

    传送门 解题思路 这里写的是常数巨大的线段树套\(splay\),卡了半天常才过.首先线段树每个节点挂一个\(splay\),\(splay\)中的元素即为线段树管辖的区间中的数.对于操作\(1\), ...

  6. HTTP返回码中200,302,304,404,500得意思

    状态码的职责是当客户端向服务器端发送请求时,描述返回请求结果.借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了什么错误. 2开头的,响应成功,客户端请求服务器正常响应处理了. 3开头的,响 ...

  7. QTP笔记--检查点、ChildObjects方法

    一.自带检查点函数 '最后一个参数为timeout,单位是毫秒 Browser( "SAP NetWeaver Portal" ).Page( "SAP NetWeave ...

  8. django-redis-cache缓存使用

    1. redis安装配置 (1)到redis目录 [root@localhost redis-2.8.17]# ls 00-RELEASENOTES CONTRIBUTING deps Makefil ...

  9. Cocos2d-x之UI控件简介

    |   版权声明:本文为博主原创文章,未经博主允许不得转载. 在Cocos2d-x中提供了一种UI控件,UI控件是指用户交互界面的操作部件.UI控件可以是Button(按钮),CheckBox(选择框 ...

  10. Cocos2d-x之Log输出机制

    |   版权声明:本文为博主原创文章,未经博主允许不得转载. 在cocos2d-x中,我们使用log这个函数进行输出,log可以输出很多参数,它的使用方式就和使用c语言中的printf的使用方式差不多 ...