`
reasonsoft
  • 浏览: 48314 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

解读NIO Socket非阻塞模式

    博客分类:
  • java
阅读更多
   
前言:
     jdk供的无阻塞I/O(NIO)有效解决了多线程服务器存在的线程开销问题,但在使用上略显得复杂一些。在NIO中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程充分使用用多个CPU的处理能力和处理中的等待时间,达到提高服务能力的目的。
    这段时间在研究NIO,写篇博客来记住学过的东西。还是从最简单的Hello World开始,
client多线程请求server端,server接收client的名字,并返回Hello! +名字的字符格式给client。当然实际应用并不这么简单,实际可能是访问文件或者数据库获取信息返回给client。非阻塞的NIO有何神秘之处?代码:

1)server端代码
/**
 * 
 * @author Jeff
 *
 */
public class HelloWorldServer {

	static int BLOCK = 1024;
	static String name = "";
	protected Selector selector;
	protected ByteBuffer clientBuffer = ByteBuffer.allocate(BLOCK);
	protected CharsetDecoder decoder;
	static CharsetEncoder encoder = Charset.forName("GB2312").newEncoder();

	public HelloWorldServer(int port) throws IOException {
		selector = this.getSelector(port);
		Charset charset = Charset.forName("GB2312");
		decoder = charset.newDecoder();
	}

	// 获取Selector
	protected Selector getSelector(int port) throws IOException {
		ServerSocketChannel server = ServerSocketChannel.open();
		Selector sel = Selector.open();
		server.socket().bind(new InetSocketAddress(port));
		server.configureBlocking(false);
		server.register(sel, SelectionKey.OP_ACCEPT);
		return sel;
	}

	// 监听端口
	public void listen() {
		try {
			for (;;) {
				selector.select();
				Iterator iter = selector.selectedKeys().iterator();
				while (iter.hasNext()) {
					SelectionKey key = (SelectionKey) iter.next();
					iter.remove();
					process(key);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// 处理事件
	protected void process(SelectionKey key) throws IOException {
		if (key.isAcceptable()) { // 接收请求
			ServerSocketChannel server = (ServerSocketChannel) key.channel();
			SocketChannel channel = server.accept();
			//设置非阻塞模式
			channel.configureBlocking(false);
			channel.register(selector, SelectionKey.OP_READ);
		} else if (key.isReadable()) { // 读信息
			SocketChannel channel = (SocketChannel) key.channel();
			int count = channel.read(clientBuffer);
			if (count > 0) {
				clientBuffer.flip();
				CharBuffer charBuffer = decoder.decode(clientBuffer);
				name = charBuffer.toString();
				// System.out.println(name);
				SelectionKey sKey = channel.register(selector,
						SelectionKey.OP_WRITE);
				sKey.attach(name);
			} else {
				channel.close();
			}

			clientBuffer.clear();
		} else if (key.isWritable()) { // 写事件
			SocketChannel channel = (SocketChannel) key.channel();
			String name = (String) key.attachment();
			
			ByteBuffer block = encoder.encode(CharBuffer
					.wrap("Hello !" + name));
			

			channel.write(block);

			//channel.close();

		}
	}

	public static void main(String[] args) {
		int port = 8888;
		try {
			HelloWorldServer server = new HelloWorldServer(port);
			System.out.println("listening on " + port);
			
			server.listen();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


server主要是读取client发过来的信息,并返回一条信息

2)client端代码

/**
 * 
 * @author Jeff
 *
 */
public class HelloWorldClient {

	static int SIZE = 10;
	static InetSocketAddress ip = new InetSocketAddress("localhost", 8888);
	static CharsetEncoder encoder = Charset.forName("GB2312").newEncoder();

	static class Message implements Runnable {
		protected String name;
		String msg = "";

		public Message(String index) {
			this.name = index;
		}

		public void run() {
			try {
				long start = System.currentTimeMillis();
				//打开Socket通道
				SocketChannel client = SocketChannel.open();
				//设置为非阻塞模式
				client.configureBlocking(false);
				//打开选择器
				Selector selector = Selector.open();
				//注册连接服务端socket动作
				client.register(selector, SelectionKey.OP_CONNECT);
				//连接
				client.connect(ip);
				//分配内存
				ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
				int total = 0;

				_FOR: for (;;) {
					selector.select();
					Iterator iter = selector.selectedKeys().iterator();

					while (iter.hasNext()) {
						SelectionKey key = (SelectionKey) iter.next();
						iter.remove();
						if (key.isConnectable()) {
							SocketChannel channel = (SocketChannel) key
									.channel();
							if (channel.isConnectionPending())
								channel.finishConnect();
							channel
									.write(encoder
											.encode(CharBuffer.wrap(name)));

							channel.register(selector, SelectionKey.OP_READ);
						} else if (key.isReadable()) {
							SocketChannel channel = (SocketChannel) key
									.channel();
							int count = channel.read(buffer);
							if (count > 0) {
								total += count;
								buffer.flip();

								while (buffer.remaining() > 0) {
									byte b = buffer.get();
									msg += (char) b;
									
								}

								buffer.clear();
							} else {
								client.close();
								break _FOR;
							}
						}
					}
				}
				double last = (System.currentTimeMillis() - start) * 1.0 / 1000;
				System.out.println(msg + "used time :" + last + "s.");
				msg = "";
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) throws IOException {
	
		String names[] = new String[SIZE];

		for (int index = 0; index < SIZE; index++) {
			names[index] = "jeff[" + index + "]";
			new Thread(new Message(names[index])).start();
		}
	
	}
}


分享到:
评论
28 楼 lfpzln 2012-02-20  
only_java 写道
发现用户NIO发数据,收到的数据是连在一起的。比如
我发两条hello world,然后在服务端接受时则是一下将两条当作一条接受了。这种问题怎么处理呢?

27 楼 Mic_X 2011-07-27  
fly_hyp 写道
代码质量非常高,受益匪浅

break _FOR
这质量叫高?
26 楼 only_java 2009-09-03  
发现用户NIO发数据,收到的数据是连在一起的。比如
我发两条hello world,然后在服务端接受时则是一下将两条当作一条接受了。这种问题怎么处理呢?
25 楼 sandssss 2009-08-24  
<div class="quote_title">pandonix 写道</div>
<div class="quote_div">
<div class="quote_title">sdh5724 写道</div>
<div class="quote_div">
<div class="quote_title">dennis_zane 写道</div>
<div class="quote_div">
<div class="quote_title">cheaper 写道</div>
<div class="quote_div">一个channel有99.9%的几率都是可写的 </div>
<br>这种无厘头的结论不知道你是怎么得出来的</div>
<br><br><br>他是没有写的数据, 也是selector,因此有了此结论, 根本没有理解注册事件的含义。 我开始不懂的时候也碰到过, 你把可写的事件注册了, 那么selector会一直告诉你可写。 然后就是无限循环了。因此, 数据写完了, 你要把写事件从selector里删除了。 不然有的是苦头。</div>
<br><br>貌似LZ数据写完以后,就是没有将OP_WRITE注销掉。不知是否我看错了? <br><pre name="code" class="java">#     // 处理事件 
#     protected void process(SelectionKey key) throws IOException { 
#         if (key.isAcceptable()) { // 接收请求 
#             ServerSocketChannel server = (ServerSocketChannel) key.channel(); 
#             SocketChannel channel = server.accept(); 
#             //设置非阻塞模式 
#             channel.configureBlocking(false); 
#             channel.register(selector, SelectionKey.OP_READ); 
#         } else if (key.isReadable()) { // 读信息 
#             SocketChannel channel = (SocketChannel) key.channel(); 
#             int count = channel.read(clientBuffer); 
#             if (count &gt; 0) { 
#                 clientBuffer.flip(); 
#                 CharBuffer charBuffer = decoder.decode(clientBuffer); 
#                 name = charBuffer.toString(); 
#                 // System.out.println(name); 
#                 SelectionKey sKey = channel.register(selector, 
#                         SelectionKey.OP_WRITE); 
#                 sKey.attach(name); 
#             } else { 
#                 channel.close(); 
#             } 
#  
#             clientBuffer.clear(); 
#         } else if (key.isWritable()) { // 写事件 
#             SocketChannel channel = (SocketChannel) key.channel(); 
#             String name = (String) key.attachment(); 
#              
#             ByteBuffer block = encoder.encode(CharBuffer 
#                     .wrap("Hello !" + name)); 
#              
#  
#             channel.write(block); 
#  
#             //channel.close(); 
#  
#         } 
#     }  </pre>
之前的
<pre name="code" class="java">                while (iter.hasNext()) {  
                    SelectionKey key = (SelectionKey) iter.next();  
                    iter.remove();  
                    process(key);  
                } </pre>
 应该就已经把它remove掉了吧。<br>
</div>
<p> </p>
<p> </p>
24 楼 mgoann 2009-08-22  
我以前也用过NIO,对1000多个gmail帐户轮询,连接到gmail服务器,再注册到select中读事件,这边就有问题了,gmail对连接数没有限制,至少我同时连1000帐号上去没问题,可是对每个帐号的连接时间做了限制,连接超过1小时,gmail会主动断掉连接,并且没有任何效应!

这个时间会发生断掉的连接注册在select中,而且永远都不会有事件触发的机会给你,让你清掉死掉的连接!

请问这个怎么处理?
23 楼 vivaxiaohua 2009-08-14  
就是Observer模式么
22 楼 lqql 2009-06-12  
java.lang.Object 写道
maoxiaolu2000 写道
我发现这种nio在长连接的情况下的设计真的很复杂, 要处理几种情况

1. 某一用户发了一条信息, 需要服务器反回一个信息(这种最简单)
2. 某一用户发了一条信息,需要服务器广播给所有客户端
3. 某一用户发了一条信息, 需要服务器发给指定的几个客户端
4. 用户没有发信息,服务器跟据某些需要主动发给所有客户端信息
5. 用户没有发信息,服务器跟据某些需要主动发给指定的几个客户端信息
6. 用户发了一条信息,服务器只需计算不需反回。


像这种情况还是用Socket更方便一些,用NIO就很麻烦了

这个..........差不多
21 楼 java.lang.Object 2009-06-12  
maoxiaolu2000 写道
我发现这种nio在长连接的情况下的设计真的很复杂, 要处理几种情况

1. 某一用户发了一条信息, 需要服务器反回一个信息(这种最简单)
2. 某一用户发了一条信息,需要服务器广播给所有客户端
3. 某一用户发了一条信息, 需要服务器发给指定的几个客户端
4. 用户没有发信息,服务器跟据某些需要主动发给所有客户端信息
5. 用户没有发信息,服务器跟据某些需要主动发给指定的几个客户端信息
6. 用户发了一条信息,服务器只需计算不需反回。


像这种情况还是用Socket更方便一些,用NIO就很麻烦了
20 楼 lqql 2009-06-10  
楼主的只是解决的NIO方式调用,网上这样的例子很多.实际上会有多线程并发的处理.因为监听的线程不能和业务线程串在一起!
19 楼 lirenjiede 2009-06-10  
你那个服务器端一直在给客户端写啊
18 楼 lirenjiede 2009-06-10  
我跑着有问题啊
17 楼 unsid 2009-03-16  
其实之前我一直用普通IO左异步通讯,是通过客户端向服务器发送一个请求,客户端需要返回值,但是又不是立刻就需要,往往是做:客户端传递一个回调函数给发送程序,发送程序用这个回调函数单起一个线程监听,然后客户端客户做别的事情去了,但是效果不是很理想,因为这种请求一多,创建很多线程,理论上客户端可以去做其他事情,但是实际上客户会发现,点击其他功能响应极其缓慢和不可用没区别....nio是不是本质上并非引入了非阻塞的io方法,而是让开发人员在一个线程里实现非阻塞,这样就真地做到了客户端去做其他事情而没影响了,因为始终是一个线程.
16 楼 maoxiaolu2000 2009-03-09  
我发现这种nio在长连接的情况下的设计真的很复杂, 要处理几种情况

1. 某一用户发了一条信息, 需要服务器反回一个信息(这种最简单)
2. 某一用户发了一条信息,需要服务器广播给所有客户端
3. 某一用户发了一条信息, 需要服务器发给指定的几个客户端
4. 用户没有发信息,服务器跟据某些需要主动发给所有客户端信息
5. 用户没有发信息,服务器跟据某些需要主动发给指定的几个客户端信息
6. 用户发了一条信息,服务器只需计算不需反回。

15 楼 bloodrate 2009-03-06  
我以前写的非阻塞IO是传递一个回调函数进去,单起一个线程监听IO,主线程作其他的事情去....这样可以么?
14 楼 pandonix 2009-03-06  
sdh5724 写道
dennis_zane 写道
cheaper 写道
一个channel有99.9%的几率都是可写的

这种无厘头的结论不知道你是怎么得出来的



他是没有写的数据, 也是selector,因此有了此结论, 根本没有理解注册事件的含义。 我开始不懂的时候也碰到过, 你把可写的事件注册了, 那么selector会一直告诉你可写。 然后就是无限循环了。因此, 数据写完了, 你要把写事件从selector里删除了。 不然有的是苦头。


貌似LZ数据写完以后,就是没有将OP_WRITE注销掉。不知是否我看错了?
#     // 处理事件  
#     protected void process(SelectionKey key) throws IOException {  
#         if (key.isAcceptable()) { // 接收请求  
#             ServerSocketChannel server = (ServerSocketChannel) key.channel();  
#             SocketChannel channel = server.accept();  
#             //设置非阻塞模式  
#             channel.configureBlocking(false);  
#             channel.register(selector, SelectionKey.OP_READ);  
#         } else if (key.isReadable()) { // 读信息  
#             SocketChannel channel = (SocketChannel) key.channel();  
#             int count = channel.read(clientBuffer);  
#             if (count > 0) {  
#                 clientBuffer.flip();  
#                 CharBuffer charBuffer = decoder.decode(clientBuffer);  
#                 name = charBuffer.toString();  
#                 // System.out.println(name);  
#                 SelectionKey sKey = channel.register(selector,  
#                         SelectionKey.OP_WRITE);  
#                 sKey.attach(name);  
#             } else {  
#                 channel.close();  
#             }  
#   
#             clientBuffer.clear();  
#         } else if (key.isWritable()) { // 写事件  
#             SocketChannel channel = (SocketChannel) key.channel();  
#             String name = (String) key.attachment();  
#               
#             ByteBuffer block = encoder.encode(CharBuffer  
#                     .wrap("Hello !" + name));  
#               
#   
#             channel.write(block);  
#   
#             //channel.close();  
#   
#         }  
#     }  

13 楼 fjlyxx 2009-03-01  
cheaper 写道
一个channel有99.9%的几率都是可写的, 如果selecter一个可写状态会导致cpu使用率很高, 相当的恐怖.

谁叫你注册你不该关系的事件了 
12 楼 aninfeel 2009-02-24  
刚开始的时候我也很头晕的,过了一个星期才总算接受了这种模式.
11 楼 aninfeel 2009-02-24  
刚开始的时候我也很头晕的,过了一个星期才总算接受了这种模式.
10 楼 niveko 2009-02-24  
据我所知,nio一般是用在高并发的服务器上,能说说服务器的线程是怎么处理的吗?
9 楼 hbb 2009-02-24  
以前写过一个例子,不过遇到这么一个问题:
用telnet连接服务器后,直接关闭窗口,此时连接已经断开,但是服务器端无法判断,此后服务器端就出现一系列的异常。请问这个该怎么解决?

相关推荐

    java解读NIOSocket非阻塞模式宣贯.pdf

    在Java NIO中,Socket通信可以采用非阻塞模式,允许一个线程处理多个客户端连接,这对于高并发的网络应用尤其有益。 在给定的示例中,`HelloWorldServer`类展示了如何使用NIO创建一个非阻塞的服务器。以下是关键...

    java解读NIOSocket非阻塞模式.zip

    jdk供的无阻塞I/O(NIO)有效解决了多线程服务器存在的线程开销问题,但在使用上略显得复杂一些。在NIO中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务...非阻塞的NIO有何神秘之处?直接上代码!

    socket文件传输部分代码(java)

    而更高级的socket编程则会使用`java.nio.channels.SocketChannel`和`java.nio.channels.ServerSocketChannel`类,它们支持非阻塞模式和更高效的数据处理方式。 #### 二、非阻塞IO与选择器 传统的IO操作是阻塞式的...

    Netty5.0架构剖析和源码解读.PDF

    在深入探讨Netty 5.0的架构和源码之前,我们需要先理解NIO(非阻塞I/O)的概念。NIO与传统的阻塞I/O模型不同,它允许多个操作在单个线程上并发进行,提高了系统资源利用率,尤其适用于高并发、低延迟的网络应用。 ...

    西安电子科技大学-通信网络实验报告(二)Socket编程范例.docx

    - **性能优化**:针对特定场景,可以对Socket编程进行优化,如使用NIO(非阻塞I/O)提高性能。 综上所述,这份实验报告不仅涵盖了Socket编程的基础知识,还提供了具体的Java代码示例,对于学习网络编程具有一定的...

    基于SOCKET的数据传输安全技术研究——以JAVA SOCKET为例.zip

    Java的NIO(New IO)提供了一种非阻塞的I/O模型,可以提高多并发连接的效率。此外,TCP的Keep-Alive和 Nagle算法也可以根据实际情况调整,以优化网络性能。 总结,Java Socket结合SSL/TLS等安全技术,可以构建安全...

    java笔记与java核心内容解读

    NIO(New IO)提供了一种更高效、非阻塞的I/O模型。 8. **网络编程**:Java提供了Socket编程接口,可以用来创建客户端和服务器端的通信应用。 9. **反射机制**:反射是Java的一个强大特性,允许程序在运行时动态地...

    Java底层知识点、源码解读,技术栈相关原理知识点、工具解读最佳实践、功能点实战,问题排查,开发技巧等

    8. **IO与NIO**: Java IO提供基于流的输入输出操作,而NIO(非阻塞I/O)引入了通道和缓冲区,提升了高并发场景下的性能。 9. **集合框架**: 遍历HashMap、ArrayList、LinkedList、TreeSet等集合的实现原理,掌握...

    Java IO

    4. 非阻塞Socket:Java NIO中的SocketChannel和ServerSocketChannel是非阻塞网络套接字,它们允许你在没有数据可读或可写时,不会挂起当前线程。 5. MBean和JMX工具:文章提到了使用MBean和Java管理扩展(JMX)工具...

    《Java开发典型模块大全》光盘源码part4(15-21章)

    2. **网络编程**:16章可能讲解了Java的Socket编程,包括TCP和UDP通信,以及ServerSocket和Socket类的使用,还有可能涉及到NIO(非阻塞I/O)和Netty框架的应用。 3. **数据持久化**:17章可能是关于数据库操作的...

    JAVA开发实战经典PPT.rar

    5. **IO流与NIO**:讲解输入/输出流的使用,包括字节流、字符流、对象流,以及NIO(非阻塞I/O)的优势和用法,如通道、缓冲区和选择器。 6. **多线程编程**:涵盖线程的创建方式(继承Thread类与实现Runnable接口)...

    TCPIP.Sockets.in.Java.Second.Edition

    - **非阻塞Socket**:介绍如何使用NIO来实现非阻塞式的Socket编程,提高程序效率。 - **多线程Socket编程**:讨论了如何在Java中利用多线程技术处理多个客户端的并发连接请求。 ##### 4. 高级主题 - **错误处理**...

    Java网络高级编程源码人邮金勇华曲俊生

    3. **NIO(非阻塞I/O)**:Java NIO(New Input/Output)是Java 1.4引入的新特性,它提供了与传统IO不同的I/O操作方式,支持选择器(Selector)、通道(Channel)和缓冲区(Buffer)。NIO允许程序在数据准备好时进行...

    Java各类习题+项目开发文档

    4. **IO流与NIO**:这部分习题会测试你对输入输出流的理解,包括文件操作、对象序列化,以及新的NIO(非阻塞I/O)模型。 5. **多线程**:Java提供了丰富的多线程支持,习题可能包括同步机制(如synchronized关键字...

    java高级服务端阅读资料pdf1-5章

    此外,NIO(非阻塞I/O)和AIO(异步I/O)也是现代服务端开发的热点,可能会涉及到。 **第五章:JVM优化** 理解Java虚拟机(JVM)的工作原理对性能调优至关重要。本章可能包含JVM内存模型、垃圾回收机制、类加载机制...

    SimpleSocket:Netty 示例项目

    1. **Netty 框架**:Netty 是一个基于 NIO(非阻塞 I/O)的 Java 框架,提供了高度定制的事件循环(EventLoop)、通道(Channel)和管道(ChannelPipeline)机制,使得编写高性能、异步的网络应用变得简单。...

    Netty3.x 源码解析

    1. NIO基础:Netty基于Java NIO来实现非阻塞的网络通信,因此需要对Java NIO有一定的了解。 2. 事件处理:Netty采用事件驱动机制来处理各种网络事件,理解其事件处理模型是掌握Netty的关键。 3. ChannelPipeline:...

Global site tag (gtag.js) - Google Analytics