NIO的一些坑
来源:互联网 发布:河北省网络行政学院 编辑:程序博客网 时间:2024/06/04 19:30
转载自:http://blog.csdn.net/u013970991/article/details/52036223
public class EchoServer { public static int DEFAULT_PORT = 7777; public static void main(String[] args) throws IOException { System.out.println("Listening for connection on port " + DEFAULT_PORT); Selector selector = Selector.open(); initServer(selector); while (true) { selector.select(); for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor.hasNext();) { SelectionKey key = (SelectionKey) itor.next(); itor.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); System.out.println("Accepted connection from " + client); client.configureBlocking(false); SelectionKey clientKey = client.register(selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ); ByteBuffer buffer = ByteBuffer.allocate(100); clientKey.attach(buffer); } if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); client.read(buffer); } if (key.isWritable()) { // System.out.println("is writable..."); SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.flip(); client.write(buffer); buffer.compact(); } } catch (IOException e) { key.cancel(); try { key.channel().close(); } catch (IOException ioe) { } } } } } private static void initServer(Selector selector) throws IOException, ClosedChannelException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); ServerSocket ss = serverChannel.socket(); ss.bind(new InetSocketAddress(DEFAULT_PORT)); serverChannel.configureBlocking(false); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } }
上面的代码很典型,运行结果似乎也是正确的。但是如果top用看一下发现服务器进程CPU占用到95%以上,如果取消掉32行的注释,服务器会不断地输出”is writable…”,这是为什么呢?来分析当第一个客户端连接上时发生什么情况。
在连接之前,selector.select()处于阻塞状态。当阻塞时,内核会将这个进程调度至休眠状态,此时基本不耗CPU。当客户端发起一个连接时,服务器检测到客户端连接,selector.select()返回。selector.selectedKeys()返回已就绪的SelectionKey的集合,在这种情况下,它只包含一个key,也就是注册的SelectionKey.OP_ACCEPT。服务器开始运行if (key.isAcceptable())块的代码,server.accept()返回代码客户端连接的socket,之后在socket上注册OP_READ和OP_WRITE,表示当socket可读或者可写时就会通知selector。
接着服务器重新进入循环,尽管这时客户端还没有任何输入,但这时selector.select()不会阻塞,因为socket注册了写操作,而socket只要send buffer不满就可以写,刚开始send buffer为空,socket总是可以写,于是server.select()立即返回,包含SelectionKey.OP_WRITE。由于这个key可写,所以服务器会运行if (key.isReadable())块的代码,但是这时buffer为空,client.write(buffer)没有向socket写任何东西,立即返回0。
接着服务器又回到循环,由于客户端连接socket可以写,这时selector.select()会立即返回,然后又运行if (key.isReadable())块的代码,这样不断循环,服务器却实际没有干事情,却耗大量的CPU。
从上面的分析可以看出问题在于没有数据可写时就在socket上注册了OP_WRITE,导致服务器浪费大量CPU资源。一个简单方法就是不要在同一个socket同时注册多个操作。对于上面的EchoServer来说就是不要同时注册OP_READ和OP_WRITE,要么只注册OP_READ,要么只注册OP_WRITE。下面的EchoServer修正了以上的错误:
public static void main(String[] args) throws IOException { System.out.println("Listening for connection on port " + DEFAULT_PORT); Selector selector = Selector.open(); initServer(selector); while (true) { selector.select(); for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor.hasNext();) { SelectionKey key = (SelectionKey) itor.next(); itor.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); System.out.println("Accepted connection from " + client); client.configureBlocking(false); SelectionKey clientKey = client.register(selector, SelectionKey.OP_READ); ByteBuffer buffer = ByteBuffer.allocate(100); clientKey.attach(buffer); } else if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); int n = client.read(buffer); if (n > 0) { buffer.flip(); key.interestOps(SelectionKey.OP_WRITE); // switch to OP_WRITE } } else if (key.isWritable()) { System.out.println("is writable..."); SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); client.write(buffer); if (buffer.remaining() == 0) { // write finished, switch to OP_READ buffer.clear(); key.interestOps(SelectionKey.OP_READ); } } } catch (IOException e) { key.cancel(); try { key.channel().close(); } catch (IOException ioe) { } } } } }
上面的代码不够优雅,它将处理服务器Socket和客户连接Socket的代码搅在一起,对于简单的EchoServer这样做没什么问题,当服务器变得复杂,使用命令模式将它们分开变显得非常必要。首先创建一个接口来抽象对SelectionKey的处理。
interface Handler { void execute(Selector selector, SelectionKey key); }
再来看main函数:
public static void main(String[] args) throws IOException { System.out.println("Listening for connection on port " + DEFAULT_PORT); Selector selector = Selector.open(); initServer(selector); while (true) { selector.select(); for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor.hasNext();) { SelectionKey key = (SelectionKey) itor.next(); itor.remove(); Handler handler = (Handler) key.attachment(); handler.execute(selector, key); } } } private static void initServer(Selector selector) throws IOException, ClosedChannelException { ServerSocketChannel serverChannel = ServerSocketChannel.open(); ServerSocket ss = serverChannel.socket(); ss.bind(new InetSocketAddress(DEFAULT_PORT)); serverChannel.configureBlocking(false); SelectionKey serverKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT); serverKey.attach(new ServerHandler()); }
main函数非常简单,迭代SelectionKey,对每个key的attachment为Handler,调用它的execute的方法,不用管它是服务器Socket还是客户Socket。注意initServer方法将serverKey附加了一个ServerHandler。下面是ServerHandler的代码:
class ServerHandler implements Handler { public void execute(Selector selector, SelectionKey key) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = null; try { client = server.accept(); System.out.println("Accepted connection from " + client); } catch (IOException e) { e.printStackTrace(); return; } SelectionKey clientKey = null; try { client.configureBlocking(false); clientKey = client.register(selector, SelectionKey.OP_READ); clientKey.attach(new ClientHandler()); } catch (IOException e) { if (clientKey != null) clientKey.cancel(); try { client.close(); } catch (IOException ioe) { } } } }
ServerHandler接收连接,为每个客户Socket注册OP_READ操作,返回的clientKey附加上ClientHandler。
class ClientHandler implements Handler { private ByteBuffer buffer; public ClientHandler() { buffer = ByteBuffer.allocate(100); } public void execute(Selector selector, SelectionKey key) { try { if (key.isReadable()) { readKey(selector, key); } else if (key.isWritable()) { writeKey(selector, key); } } catch (IOException e) { key.cancel(); try { key.channel().close(); } catch (IOException ioe) { } } } private void readKey(Selector selector, SelectionKey key) throws IOException { SocketChannel client = (SocketChannel) key.channel(); int n = client.read(buffer); if (n > 0) { buffer.flip(); key.interestOps(SelectionKey.OP_WRITE); // switch to OP_WRITE } } private void writeKey(Selector selector, SelectionKey key) throws IOException { // System.out.println("is writable..."); SocketChannel client = (SocketChannel) key.channel(); client.write(buffer); if (buffer.remaining() == 0) { // write finished, switch to OP_READ buffer.clear(); key.interestOps(SelectionKey.OP_READ); } } }
- NIO的一些坑
- nio的一些相关知识。
- 说说java NIO的一些个人总结
- 关于java.nio.ByteBuffer的一些杂七杂八。
- java NIO的一些个人总结
- Java NIO 设计底层的一些东西
- 关于一些nio的问题资料
- java NIO的一些个人总结
- 说说java NIO的一些个人总结
- java NIO的一些个人总结
- Tomcat的BIO和NIO一些问题
- 关于Java的一些NIO框架的一点想法
- NIO一些例子
- Java NIO 真的能快一些吗?
- 说说对java nio的一些个人总结
- 关于nio和tomcat6的一些有用图示
- 关于nio和tomcat6的一些有用图示
- 21、输入输入(io、nio)的一些知识点小结
- #define 中 # ## ##__VA_ARGS__
- python3 [爬虫入门实战]爬虫之scrapy安装与配置教程
- 分布式消息队列RocketMQ与Kafka架构上的巨大差异之1 -- 为什么RocketMQ要去除ZK依赖?
- 【PAT】【Advanced Level】1062. Talent and Virtue (25)
- [SDUT](2144)图结构练习——最小生成树 ---最小生成树(图)
- NIO的一些坑
- httpURLConnection-网络请求的两种方式-get请求和post请求
- 摆花
- Python: 创建空的list,以及append用法
- TCP协议原理详解
- 数据离散化模板(用STL实现)
- 谈谈谷歌word2vec的原理
- python使用 UTF-8编码
- 机翼的翼型和升力