论坛首页 Java企业应用论坛

关于NIO中异步读写的实践讨论

浏览 11313 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-04-17  
最近研究NIO的用法,在服务器的实现中,NIO可以有效的减少线程的数量,基于Selector的设计可以让服务器真正准备好读写后再去创建线程完成工作,这样确实可以减少线程的数量,提高效率,但是我实践的时候发现异步的读写有协调的问题。
if (selectionKey.isAcceptable()) {
			System.out.println("Ready to accecpt a request ------------2");
			// 返回为之创建此键的通道。
			server = (ServerSocketChannel) selectionKey.channel();
			// 接受到此通道套接字的连接。
			// 此方法返回的套接字通道(如果有)将处于阻塞模式。
			client = server.accept();
			// 配置为非阻塞
			client.configureBlocking(false);
			// 注册到selector,等待连接
			client.register(selector, SelectionKey.OP_READ);
		} else if (selectionKey.isReadable()) {
			// 返回为之创建此键的通道。
			client = (SocketChannel) selectionKey.channel();
			System.out.println("Ready to read ----------------3");
			// 将缓冲区清空以备下次读取
			receivebuffer.clear();
			// 读取服务器发送来的数据到缓冲区中
			count = client.read(receivebuffer);
			if (count > 0) {
				receiveText = new String(receivebuffer.array(), 0, count);
				System.out.println("服务器端接受客户端数据--:" + receiveText);
				client.register(selector, SelectionKey.OP_WRITE);
			}
		} else if (selectionKey.isWritable()) {
			// 将缓冲区清空以备下次写入
			sendbuffer.clear();
			// 返回为之创建此键的通道。
			client = (SocketChannel) selectionKey.channel();
			System.out.println("Ready to write data ---------------------4");
			sendText = "message from server--";
			// 向缓冲区中输入数据
			sendbuffer.put(sendText.getBytes());
			// 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
			sendbuffer.flip();
			// 输出到通道
			client.write(sendbuffer);
			System.out.println("服务器端向客户端发送数据--:" + sendText);
			client.register(selector, SelectionKey.OP_READ);
		}

在以上这个实现里,读的过程和写的过程是分开的,这样符合异步的思想,但是我不知道读写如何协作,比如一个请求A被发送到服务器,读写异步,那么我必须把响应A存储,然后异步的写,但是服务器的是并发的时候,写的时候可能有另一个响应B也要被发送,那么怎么能区分A和B,并正确的发送出去呢?

以下的这个实现解决了这个问题。
public void handleRead(SelectionKey key) throws IOException {
ByteBuffer byteBuffer =ByteBuffer.allocate(512);
SocketChannel socketChannel =(SocketChannel)key.channel();
while(true) {
intreadBytes = socketChannel.read(byteBuffer);
if(readBytes>0) {
log.info("Server: readBytes = " +readBytes);
log.info("Server: data = " + new String(byteBuffer.array(), 0, readBytes));
byteBuffer.flip();
socketChannel.write(byteBuffer);
break;
}


但是在这种实现方式里,读写变成了一个同步的过程,那么就偏离了NIO非阻塞的思想

大家来讨论一下,如何保持非阻塞的思想,又能处理读写异步的问题呢
   发表时间:2012-04-17  
另外,如果使用NIO如何保持一个连接的状态呢?

比如一次读写以后服务器发送响应回客户端,下一轮客户端再一次请求的时候服务器怎么识别这个是上次处理过的客户端,使其能使用上次缓存的数据而为一个客户端连续服务呢?
0 请登录后投票
   发表时间:2012-04-17   最后修改:2012-04-17
应该这么写: 在外面无限循环等待客户端的请求到来。其实是单线程跑server,模拟多线程的客户端请求
 
// 注册到selector,等待连接  
client.register(selector, SelectionKey.OP_ACCEPT);  
    while (true) { 
                   selector.select(); 
 
                   Iterator keys = selector.selectedKeys().iterator(); 
 
                   while (keys.hasNext()) { 
                          SelectionKey key = (SelectionKey) keys.next(); 
                         
                          keys.remove(); 
 
                          if (!key.isValid()) { 
                              continue; 
                          } 
 
                          if (key.isAcceptable()) { 
                              System.out.println("Ready to accecpt a request ------------2");  
            // 返回为之创建此键的通道。  
            server = (ServerSocketChannel) selectionKey.channel();  
            // 接受到此通道套接字的连接。  
            // 此方法返回的套接字通道(如果有)将处于阻塞模式。  
            client = server.accept();  
            // 配置为非阻塞  
            client.configureBlocking(false);  
            
           
                          } else if (key.isReadable()) { 
                              //读操作
                          } else if (key.isWritable()) { 
                              //写操作
                          } 
                   } 
            }
0 请登录后投票
   发表时间:2012-04-17  
须等待 写道
另外,如果使用NIO如何保持一个连接的状态呢?

比如一次读写以后服务器发送响应回客户端,下一轮客户端再一次请求的时候服务器怎么识别这个是上次处理过的客户端,使其能使用上次缓存的数据而为一个客户端连续服务呢?


客户端每次跟服务器端连接的时候,都是通过ipaddress+port标识的,这个你不用担心,每当server accept之后返回的SocketChannel就唯一标识了那个请求的客户端
0 请登录后投票
   发表时间:2012-04-18   最后修改:2012-04-18
如果你只关心无法区分接收数据的链接和发送数据的连接是否为同一个连接的话,这个问题应该好弄;创建连接的时候创建一个标识连接的对象,然后把这个对象通过 SelectionKey 的这个方法 attach(obj)附带在SelectionKey 中,发送数据时调用attch()方法就可以取到创建连接时所创建的标识连接的对象了。用NIO来写好一个非阻塞式的服务器所面临的问题远不止这个......
0 请登录后投票
   发表时间:2012-04-18  
handleRead应该另起线程处理。
A跟B本来就是不同的channel,什么叫怎么区分。发送给B之前在B的通道注册写事件就行了啊。
你程序里的if分支里都是应该另起线程池管理IO读写线程。
另外你甚至可以针对read跟write事件另起selector来增强并发性。
0 请登录后投票
   发表时间:2012-04-18  
jobar 写道
须等待 写道
另外,如果使用NIO如何保持一个连接的状态呢?

比如一次读写以后服务器发送响应回客户端,下一轮客户端再一次请求的时候服务器怎么识别这个是上次处理过的客户端,使其能使用上次缓存的数据而为一个客户端连续服务呢?


客户端每次跟服务器端连接的时候,都是通过ipaddress+port标识的,这个你不用担心,每当server accept之后返回的SocketChannel就唯一标识了那个请求的客户端


我不知道楼主的意思是不是:
1.比如客户端发送一个请求,但是这个包并不能一次全部读取完,下一次重新轮询到接着读,如何才能保证数据的完整性。2.是通过ipaddress+port标识吗?如果一个客户端并发了多个请求哪
0 请登录后投票
   发表时间:2012-04-18  
引用
如果一个客户端并发了多个请求哪


并发了多个请求,也是只有一个客户端发出来的,服务器肯定可以知道是那个客户端发来的。
0 请登录后投票
   发表时间:2012-04-18  
NIO是非阻塞,但和异步IO还是不一样的。jdk7已经引入了异步IO(AIO),可以看一下。
你说那个连接状态是为了连接复用吗?,如果服务端返回的响应能带上请求标识,那么客服端则可采用连接复用的方式,即每个SocketChannel在发送消息后,不用等响应即可继续发送其他消息。
0 请登录后投票
   发表时间:2012-04-19  
别惹Java 写道
jobar 写道
须等待 写道
另外,如果使用NIO如何保持一个连接的状态呢?

比如一次读写以后服务器发送响应回客户端,下一轮客户端再一次请求的时候服务器怎么识别这个是上次处理过的客户端,使其能使用上次缓存的数据而为一个客户端连续服务呢?


客户端每次跟服务器端连接的时候,都是通过ipaddress+port标识的,这个你不用担心,每当server accept之后返回的SocketChannel就唯一标识了那个请求的客户端


我不知道楼主的意思是不是:
1.比如客户端发送一个请求,但是这个包并不能一次全部读取完,下一次重新轮询到接着读,如何才能保证数据的完整性。2.是通过ipaddress+port标识吗?如果一个客户端并发了多个请求哪



LZ的问题是这样的:假设客户端和服务器的通信过程并不是只有一轮,而是
request A
response A
request B
response AB(代表这个响应过程需要使用上一轮的结果)

由于这个过程是异步的,所以在request B到达的时候服务器无法判断这个request是不是来自上一次请求的客户端,那么在计算响应的结果的时候就不知道应该引用哪个一轮计算的结果了
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics