Java nio服务器始终停留在selector.select上

时间:2014-05-25 00:53:44

标签: java nio

我正在尝试使用NIO编写一个简单的客户端服务器模型(以了解它是如何工作的)。有两种类型的消息可以发送Connection(服务器在连接时发送给客户端的消息,提供与客户端ID类似的信息)和Ping。

如果客户未被按下>的时间段。 4秒钟服务器将ping客户端,客户端将以乒乓响应。

当我初始化我的服务器时,我这样做:

    try{
        selector = Selector.open();
        selector.wakeup();
        serverChannel = selector.provider().openServerSocketChannel();
        serverChannel.socket().bind(new InetSocketAddress(port));
        serverChannel.configureBlocking(false);

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }catch (IOException e){
        log.Error(e);
        throw new RuntimeException("Error opening selector.", e);
    }

发生这种情况后,我们进入我们的服务器循环,它将找到已选择的键并在键上执行所选操作

int select = 0;

    while (running){ 
        try{
           select = selector.select(timeout);

           if(select == 0){
               continue;
           }
           Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();

           while(selectedKeys.hasNext()){
               SelectionKey key = selectedKeys.next();
               selectedKeys.remove();

               Connection conn = (Connection)key.attachment();

               if(conn != null){
                   if(key.isValid() && key.isReadable()){
                                  //handle key reads
                   }
                   if(key.isValid() && key.isWritable()){
                          //handle key writes
                   }

                   continue;
               }

               if(key.isValid() && key.isAcceptable()){
                   Connection newConn = new Connection(transport, packetFactory, nextConnectionId);

                   try{
                       SelectionKey selectionKey = newConn.getConnection().handleAccept(selector, serverChannel.accept());
                       selectionKey.attach(newConn);

                            //send the client a connection message

                       nextConnectionId++;
                   }catch(IOException e){
                       newConn.close();
                       log.Error(e);
                   }
               }
           }

           long time = System.nanoTime();
           for(Connection c : connections){
               if(c.getConnection().isTimedOut(time)){
                   c.close();
                }

                if(c.needsPing(time)) {
                    c.sendPing();
                }
           }
        }catch(IOException e){
            log.Error(e);
        }
    }

handleAccept()方法看起来像这样

public SelectionKey handleAccept(Selector selector, SocketChannel ch) {

    try{
        socketChannel = ch;
        socketChannel.configureBlocking(false);
        lastReadTime = System.nanoTime();
        selectionKey = ch.register(selector, SelectionKey.OP_READ);

        return selectionKey;
    }catch(IOException e){
        close();
    }
}

handleAccept()方法是连接对象的一部分,它保存诸如连接socketChannel,selectionkey之类的信息。每当连接被读/写时,它们都会更新如下:

public void handleWrite(SelectionKey key) throws IOException {
    socketChannel = (SocketChannel) key.channel();
    this.selectionKey = key;

    //do other stuff
 }

最后,当我们需要向客户端写东西时,我们告诉socketChannel我们有兴趣写作,一旦我们完成写作,我们告诉它我们想要读。这可以通过访问连接选择键并将其interestOps()设置为正确的字段来完成。

当我运行此代码时,服务器执行以下操作:

[2014/05/25 01:49:23] [INFO] [com.adammflax.net.ServerEndPoint] Started Server on port 9001
[2014/05/25 01:49:26] [INFO] [com.adammflax.net.ServerEndPoint] Added the connection com.adammflax.net.Connection@4838ddcc to the list of connections on the server
[2014/05/25 01:49:26] [INFO] [com.adammflax.net.ServerEndPoint] Client com.adammflax.net.Connection@4838ddcc connected to the server given a Id of 0
30
[CLIENT RECEIVES CONNECTION PACKET]
[2014/05/25 01:49:26] [DEBUG] [com.adammflax.net.ServerEndPoint] Received ping from client now replying
[CLIENT RECEIVES Ping PACKET]
然后

然后服务器卡在selector.select()上(它总是超时并返回0)。任何人都可以向我解释为什么会这样。我知道这很多代码需要深入研究(缺少一些代码来尝试和浓缩这些代码)但是对此问题的任何帮助都会很惊人。

编辑 添加更多代码以显示我们如何检测ping是否需要发生(属于连接对象)

public boolean needsPing(long time) {
    if(!isConnected()){
        return false;
    }

    if(pingTime <= 0){
        return false;
    }

    if(time <= lastPingSentTime + pingTime){
        return false;
    }

    lastPingSentTime = System.nanoTime();

    return true;
}

send ping将pingPacket对象转换为字节并调用send方法,该方法将字节添加到名为messages的messageQueue中。

public void send(byte[] message) {
    messages.add(message);
    selectionKey.interestOps(SelectionKey.OP_WRITE);
}

因为我们现在在OP_WRITE handleWrite中最终会被调用

public void handleWrite(SelectionKey key) throws IOException {
    socketChannel = (SocketChannel) key.channel();

    if(socketChannel == null){
        key.cancel();
        throw new SocketException("Socket connection has closed!");
    }

    while(!messages.isEmpty()){
        try{
            writeBuffer = ByteBuffer.wrap(messages.peek());


            if(socketChannel.write(writeBuffer) == 0){
                break;
            }

            if (writeBuffer.remaining() > 0) {
                break;
            }

            writeBuffer.compact();
            messages.poll(); //everything went well so remove head
        }catch(IOException e){
            log.Error("failed to write : " + messages.peek() + " at " +
                    this.socketChannel.getRemoteAddress());
        }
    }   

    selectionKey.interestOps(SelectionKey.OP_READ);
}

0 个答案:

没有答案