论坛首页 Java企业应用论坛

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

浏览 11457 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-04-19  
CshBBrain 写道
如果你只关心无法区分接收数据的链接和发送数据的连接是否为同一个连接的话,这个问题应该好弄;创建连接的时候创建一个标识连接的对象,然后把这个对象通过 SelectionKey 的这个方法 attach(obj)附带在SelectionKey 中,发送数据时调用attch()方法就可以取到创建连接时所创建的标识连接的对象了。用NIO来写好一个非阻塞式的服务器所面临的问题远不止这个......


恩,我关心的是怎么把异步的技术用于实践,因为我的需求过程,打个比方

---------------isReadable---------
request 1
request 2
读取两个请求并且计算响应response 1, response 2
---------------isWriteable--------
按照读写异步,这时是另外一个线程了,那么这个线程要去取读线程(或者说可能是来自独立的计算线程的结果),然后再发送,那么取结果的时候就需要判断这个结果是1还是2

通过附加标示符是可以解决问题,请问有没有示例的代码呢
0 请登录后投票
   发表时间:2012-04-19  
baiweiyll 写道
NIO是非阻塞,但和异步IO还是不一样的。jdk7已经引入了异步IO(AIO),可以看一下。
你说那个连接状态是为了连接复用吗?,如果服务端返回的响应能带上请求标识,那么客服端则可采用连接复用的方式,即每个SocketChannel在发送消息后,不用等响应即可继续发送其他消息。



恩,我知道NIO是非阻塞,我的问题就是由于非阻塞带来的读写异步的具体实现的问题
0 请登录后投票
   发表时间:2012-04-19  
须等待 写道
baiweiyll 写道
NIO是非阻塞,但和异步IO还是不一样的。jdk7已经引入了异步IO(AIO),可以看一下。
你说那个连接状态是为了连接复用吗?,如果服务端返回的响应能带上请求标识,那么客服端则可采用连接复用的方式,即每个SocketChannel在发送消息后,不用等响应即可继续发送其他消息。



恩,我知道NIO是非阻塞,我的问题就是由于非阻塞带来的读写异步的具体实现的问题

NIO也可以写成阻塞的
0 请登录后投票
   发表时间:2012-04-19  
须等待 写道
CshBBrain 写道
如果你只关心无法区分接收数据的链接和发送数据的连接是否为同一个连接的话,这个问题应该好弄;创建连接的时候创建一个标识连接的对象,然后把这个对象通过 SelectionKey 的这个方法 attach(obj)附带在SelectionKey 中,发送数据时调用attch()方法就可以取到创建连接时所创建的标识连接的对象了。用NIO来写好一个非阻塞式的服务器所面临的问题远不止这个......


恩,我关心的是怎么把异步的技术用于实践,因为我的需求过程,打个比方

---------------isReadable---------
request 1
request 2
读取两个请求并且计算响应response 1, response 2
---------------isWriteable--------
按照读写异步,这时是另外一个线程了,那么这个线程要去取读线程(或者说可能是来自独立的计算线程的结果),然后再发送,那么取结果的时候就需要判断这个结果是1还是2

通过附加标示符是可以解决问题,请问有没有示例的代码呢

 

  1. if (selectionKey.isAcceptable()) {  
  2.             System.out.println("Ready to accecpt a request ------------2");  
  3.             // 返回为之创建此键的通道。  
  4.             server = (ServerSocketChannel) selectionKey.channel();  
  5.             // 接受到此通道套接字的连接。  
  6.             // 此方法返回的套接字通道(如果有)将处于阻塞模式。  
  7.             client = server.accept();  
  8.             // 配置为非阻塞  
  9.             client.configureBlocking(false);  
  10.             // 注册到selector,等待连接  
  11.             client.register(selector, SelectionKey.OP_READ);  
  12.     
  13.     YourClass yc = new YourClass();// 这个类中可以定义一个AtomicInteger变量来记录是客户端发送的第几次请求,假如叫questNo
  14.     selectionKey.attach(yc);
  15.         } else if (selectionKey.isReadable()) {  
  16.             // 返回为之创建此键的通道。  
  17.             client = (SocketChannel) selectionKey.channel();  
  18.             System.out.println("Ready to read ----------------3");  
  19.             // 将缓冲区清空以备下次读取  
  20.             receivebuffer.clear();  
  21.             // 读取服务器发送来的数据到缓冲区中  
  22.             count = client.read(receivebuffer);  
  23.             if (count > 0) {  
  24.                 receiveText = new String(receivebuffer.array(), 0, count);  
  25.                 System.out.println("服务器端接受客户端数据--:" + receiveText);  
  26.                 client.register(selector, SelectionKey.OP_WRITE);  
  27.             } 
  28.    
  29.            ((YourClass)selectionKey.attach()).getQuestNo().getAndIncrement();//生成请求序号数,将这个值附到请求内容上,在生成响应信息的时候附到响应信息上(这个你自己设计下就可以了)
  30.         } else if (selectionKey.isWritable()) {  
  31.    //从响应信息中获取请求序号数,做处理(这个你自己设计下就可以了)
  32.             // 将缓冲区清空以备下次写入  
  33.             sendbuffer.clear();  
  34.             // 返回为之创建此键的通道。  
  35.             client = (SocketChannel) selectionKey.channel();  
  36.             System.out.println("Ready to write data ---------------------4");  
  37.             sendText = "message from server--";  
  38.             // 向缓冲区中输入数据  
  39.             sendbuffer.put(sendText.getBytes());  
  40.             // 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位  
  41.             sendbuffer.flip();  
  42.             // 输出到通道  
  43.             client.write(sendbuffer);  
  44.             System.out.println("服务器端向客户端发送数据--:" + sendText);  
  45.             client.register(selector, SelectionKey.OP_READ);  
  46.         }
0 请登录后投票
   发表时间:2012-04-19  
置个标记,request带个自增id,response里面也带上这个id。或者做个Map容器,request之前记下key,null,异步返回完成是把response写进去。取结果从容器里取。
0 请登录后投票
   发表时间:2012-04-19  
按照LZ的描述
应该是一个连接多次请求,返回响应的顺序问题吧。
跟NIO没什么关系,普通的socket用多线程处理iostream读到的多个包,也有这个问题啊。
这是业务层面的事情。
看业务场景。如果要求先发生的请求先处理先返回,那就用队列处理。
如果是先发送的请求可以后响应,那就统一由客户端自己去判断是哪次的请求。
比如聊天的业务场景:
A给自己发消息,这个只能用队列处理,不可能A后发的一条消息服务端先转发给A自己了,那就乱套了。
如果A给自己发条消息后又同时给服务端上传了自己的头像。这时候服务器可以先更新A的头像再返回A的消息,本身协议不一样,客户端自然知道如何区分。

搞不懂这个跟key附加的标识对象有什么关系,一个客户端线程跟服务端就只有一个通道,多线程客户端跟服务端那就是不同通道,本来就是不同的channel。
难道是有了结果不知道是哪个channel?channel是当做参数传入读写线程的啊,不可能不知道啊。
0 请登录后投票
   发表时间:2012-04-19  
CshBBrain 写道
须等待 写道
CshBBrain 写道
如果你只关心无法区分接收数据的链接和发送数据的连接是否为同一个连接的话,这个问题应该好弄;创建连接的时候创建一个标识连接的对象,然后把这个对象通过 SelectionKey 的这个方法 attach(obj)附带在SelectionKey 中,发送数据时调用attch()方法就可以取到创建连接时所创建的标识连接的对象了。用NIO来写好一个非阻塞式的服务器所面临的问题远不止这个......


恩,我关心的是怎么把异步的技术用于实践,因为我的需求过程,打个比方

---------------isReadable---------
request 1
request 2
读取两个请求并且计算响应response 1, response 2
---------------isWriteable--------
按照读写异步,这时是另外一个线程了,那么这个线程要去取读线程(或者说可能是来自独立的计算线程的结果),然后再发送,那么取结果的时候就需要判断这个结果是1还是2

通过附加标示符是可以解决问题,请问有没有示例的代码呢

 

 

  1. if (selectionKey.isAcceptable()) {  
  2.             System.out.println("Ready to accecpt a request ------------2");  
  3.             // 返回为之创建此键的通道。  
  4.             server = (ServerSocketChannel) selectionKey.channel();  
  5.             // 接受到此通道套接字的连接。  
  6.             // 此方法返回的套接字通道(如果有)将处于阻塞模式。  
  7.             client = server.accept();  
  8.             // 配置为非阻塞  
  9.             client.configureBlocking(false);  
  10.             // 注册到selector,等待连接  
  11.             client.register(selector, SelectionKey.OP_READ);  
  12.     
  13.     YourClass yc = new YourClass();// 这个类中可以定义一个AtomicInteger变量来记录是客户端发送的第几次请求,假如叫questNo
  14.     selectionKey.attach(yc);
  15.         } else if (selectionKey.isReadable()) {  
  16.             // 返回为之创建此键的通道。  
  17.             client = (SocketChannel) selectionKey.channel();  
  18.             System.out.println("Ready to read ----------------3");  
  19.             // 将缓冲区清空以备下次读取  
  20.             receivebuffer.clear();  
  21.             // 读取服务器发送来的数据到缓冲区中  
  22.             count = client.read(receivebuffer);  
  23.             if (count > 0) {  
  24.                 receiveText = new String(receivebuffer.array(), 0, count);  
  25.                 System.out.println("服务器端接受客户端数据--:" + receiveText);  
  26.                 client.register(selector, SelectionKey.OP_WRITE);  
  27.             } 
  28.    
  29.            ((YourClass)selectionKey.attach()).getQuestNo().getAndIncrement();//生成请求序号数,将这个值附到请求内容上,在生成响应信息的时候附到响应信息上(这个你自己设计下就可以了)
  30.         } else if (selectionKey.isWritable()) {  
  31.    //从响应信息中获取请求序号数,做处理(这个你自己设计下就可以了)
  32.             // 将缓冲区清空以备下次写入  
  33.             sendbuffer.clear();  
  34.             // 返回为之创建此键的通道。  
  35.             client = (SocketChannel) selectionKey.channel();  
  36.             System.out.println("Ready to write data ---------------------4");  
  37.             sendText = "message from server--";  
  38.             // 向缓冲区中输入数据  
  39.             sendbuffer.put(sendText.getBytes());  
  40.             // 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位  
  41.             sendbuffer.flip();  
  42.             // 输出到通道  
  43.             client.write(sendbuffer);  
  44.             System.out.println("服务器端向客户端发送数据--:" + sendText);  
  45.             client.register(selector, SelectionKey.OP_READ);  
  46.         }

 

太棒了,如果我理解的没有问题,每个SelectionKey是对应的一个链接对吧?

0 请登录后投票
   发表时间:2012-04-19  
aronlulu 写道
按照LZ的描述
应该是一个连接多次请求,返回响应的顺序问题吧。
跟NIO没什么关系,普通的socket用多线程处理iostream读到的多个包,也有这个问题啊。
这是业务层面的事情。
看业务场景。如果要求先发生的请求先处理先返回,那就用队列处理。
如果是先发送的请求可以后响应,那就统一由客户端自己去判断是哪次的请求。
比如聊天的业务场景:
A给自己发消息,这个只能用队列处理,不可能A后发的一条消息服务端先转发给A自己了,那就乱套了。
如果A给自己发条消息后又同时给服务端上传了自己的头像。这时候服务器可以先更新A的头像再返回A的消息,本身协议不一样,客户端自然知道如何区分。

搞不懂这个跟key附加的标识对象有什么关系,一个客户端线程跟服务端就只有一个通道,多线程客户端跟服务端那就是不同通道,本来就是不同的channel。
难道是有了结果不知道是哪个channel?channel是当做参数传入读写线程的啊,不可能不知道啊。



其中有返回顺序的问题,但是更多的问题是来自并发时如果标识不同的response的问题
0 请登录后投票
   发表时间:2012-04-19   最后修改:2012-04-19
干嘛要标识不同的response?一个连接就是一个channel,channel始终是读写线程的成员变量。并发时无非就是N多个channel而已。channel有了直接回写response不就行了。
标识符一般是用来传递这个连接的包装信息的。如BufferSize,TrafficClass等。
还有上面的程序都是阻塞程序,三个if分支任何一个堵塞了就玩不起来了。
             if (key.isWritable())
                {
                    if (iter.hasNext())
                    {
                        server.submit(new WriteTask(iter.next(), channel));
                        channel.register(selector, SelectionKey.OP_READ);
                    }
                    else
                    {
                        break;
                    }

                    Thread.sleep(50);
                }
                if (key.isReadable())
                {
                    server.submit(new ReadTask(channel));
                    channel.register(selector, SelectionKey.OP_WRITE);
                }
0 请登录后投票
   发表时间:2012-04-19  
aronlulu 写道
干嘛要标识不同的response?一个连接就是一个channel,channel始终是读写线程的成员变量。并发时无非就是N多个channel而已。channel有了直接回写response不就行了。
标识符一般是用来传递这个连接的包装信息的。如BufferSize,TrafficClass等。
还有上面的程序都是阻塞程序,三个if分支任何一个堵塞了就玩不起来了。
             if (key.isWritable())
                {
                    if (iter.hasNext())
                    {
                        server.submit(new WriteTask(iter.next(), channel));
                        channel.register(selector, SelectionKey.OP_READ);
                    }
                    else
                    {
                        break;
                    }

                    Thread.sleep(50);
                }
                if (key.isReadable())
                {
                    server.submit(new ReadTask(channel));
                    channel.register(selector, SelectionKey.OP_WRITE);
                }


因为服务器是并发的环境,同时会有很多个channel,要返回不同的response,不区分的话怎么分别返回呢
0 请登录后投票
论坛首页 Java企业应用版

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