如何让socket通道密钥在java nio中更改的选择器

时间:2011-11-12 21:52:31

标签: java queue nio

我对使用java nio有疑问,希望有很多java nio知识的人可以帮我澄清一些误解。

我正在使用java nio socket。可以使用socketchannel.write()填充写缓冲区。在这种情况下,剩余的缓冲区排队,密钥更改为OP_WRITE。我的一个场景是队列长度很长。每次调用selector.select()之前,我都会从另一个名为pendingRequest的队列中将键更改为OP_WRITE。但我发现由于读取速度很慢,在发送处理完成后,有许多消息未写入,它们仍然在队列中。如何处理这个问题?

在我的代码中,我有两个写作地点。一个是来自发生器:当它有发布消息时,它直接写入通道。如果缓冲区已满,则数据将排队。第二个位置在调度程序中:当密钥可写时,它会回调write()来写入排队的数据。我想这两部分可以争夺写作。我只觉得我的代码缺乏一些合作两次写入的处理。

有没有解决方案来解决我上面提到的问题?我在我的代码中发现许多排队的数据无法写出来。当密钥可写时,生成器可以再次写入数据,这导致排队的数据具有较少的写入变化。如何使这部分正确?感谢

//在WriteListener()中,编写代码分为以下三部分

   public synchronized int writeData(EventObject source) {      
    int n = 0; 
    int count = 0;

    SocketChannel socket = (SocketChannel)source.getSource();       
    ByteBuffer buffer = ((WriteEvent)source).getBuffer();   
    try {
        write(socket);
    } catch (IOException e1) {          
        e1.printStackTrace();
    }       

    while (buffer.position()>0) {   
        try {           
                buffer.flip();  
                n = socket.write(buffer);                                   
                if(n == 0) {
                        key.interestOps(SelectionKey.OP_WRITE);                         synchronized (this.pendingData) {  
                            List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingData.get(socket); 
                            if(queue == null) {
                                queue = new ArrayList<ByteBuffer>();
                                this.pendingData.put(socket, queue); 
                        }
                        queue.add(buffer);

                        logger.logInfo("queue length:" + queue.size());
                    }                                               
                    break;
                }               
                count += n; 

        } catch (IOException e) {               
            e.printStackTrace();
        }   finally {                       
            buffer.compact();              
        }
    }   

    if(buffer.position()==0) {                      
        key.interestOps(SelectionKey.OP_READ);                  
    }
            return count;   

}   

// ====此write方法用于写入排队缓冲区

  public synchronized int write(SocketChannel sc, ByteBuffer wbuf) {        
    int n = 0; 
    int count = 0;

    SelectionKey key = sc.keyFor(this.dispatcher.getDemultiplexer().getDemux());                
    while (wbuf.position()>0) {     
        try {           
            wbuf.flip();        

            n = sc.write(wbuf);             

            if(n == 0) {    
                   key.interestOps(SelectionKey.OP_WRITE);                                  
                    synchronized (this.pendingData) {  
                        List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingData.get(sc); 
                        if(queue == null) {
                                queue = new ArrayList<ByteBuffer>();
                                this.pendingData.put(sc, queue); 
                        }
                        queue.add(wbuf);
                    }

                    break;
                }               
                count += n; 

        } catch (IOException e) {               
            e.printStackTrace();
        }   finally {               

            wbuf.compact();                
        }
    }   

    if(wbuf.position()==0) {    
        wbuf.clear();               
        key.interestOps(SelectionKey.OP_READ);          
    }

return n;       
}   

// ====当key.isWritable()为true时,此方法是Dispatch的回调

public void write(SocketChannel socketChannel) throws IOException {         
   SelectionKey key = socketChannel.keyFor(this.dispatcher.getDemultiplexer().getDemux());     
    synchronized (this.pendingData) {             
        List<ByteBuffer> queue = (List<ByteBuffer>) this.pendingData.get(socketChannel);              
        if(queue == null || queue.isEmpty()) {                 
            // We wrote away all data, so we're no longer interested                 
            // in writing on this socket. Switch back to waiting for  data.                 
            try {                     
                if (key!=null)                         
                    key.interestOps(SelectionKey.OP_READ);                 
            } catch(Exception ex) {                     
                if (key!=null)                         
                    key.cancel();                 
                }             
        }           

        // Write until there's not more data ...    
        int n = 0;
        while (queue != null && !queue.isEmpty()) {                 
            ByteBuffer buf = (ByteBuffer) queue.get(0);   
            // zero length write, break the loop and wait for next writable time 
            n = write(socketChannel, buf);

            logger.logInfo("queue length:" + queue.size() + " used time: " + (t2-t1) + " ms.");

            if(n==0)  {             
                break;
            }
                      queue.remove(0); 

        }        

 }   

2 个答案:

答案 0 :(得分:0)

如果您的消费者太慢,唯一的选择可能是断开它们以保护您的服务器。您不希望一个不良消费者影响您的其他客户。

我通常会将发送缓冲区大小增加到填充的位置,然后关闭连接。这样可以避免在Java代码中处理未写入数据的复杂性,因为您所做的只是将缓冲区扩展一点。如果增加发送缓冲区大小,则会透明地执行此操作。您可能甚至不需要使用发送缓冲区大小,默认值通常约为64 KB。

答案 1 :(得分:0)

  1. 您必须确保在已经待处理的数据之后将新数据排入队列。

  2. 如果行为仍然存在,那么您实际上只有两个选择:根据不当行为断开客户端连接,或者在积压清除之前停止为其生成输出。可能两者都有。

  3. 您可以通过熟练使用long的select()超时来实现第一个。如果select()返回零,则表示在超时期间没有注册的通道或者没有发生任何任何通道,在这种情况下,您可能需要考虑与所有客户端断开连接。如果你有很多并发客户端过于繁琐而无法工作,那么你必须跟踪每个频道最后一次被选中的时间,并断开上次活动时间过早的任何频道。

    在超时期限内,您可能 想要在阅读速度慢的情况下停止为该家伙制作输出。

    “longish”的精确定义留给读者作为练习,但作为第一个近似,我想到了十分钟。