Java NIO 非阻塞:如何拒绝传入连接?

4

我正在尝试使用基于Java NIO(非阻塞)的服务器端代码,源自于“The Rox Java NIO Tutorial”。有很多传入的套接字连接,我只想接受100个。所以如果有100个活动连接,则应拒绝新的连接。但是要如何做到这一点呢?只有ServerSocketChannel.accept()方法可以返回SocketChannel对象。使用该对象,我可以调用socketChannel.socket().close()方法,但是连接已经打开了。以下是代码的一部分:

@Override
public void run() {
    while (true) {
        try {
            // Wait for an event one of the registered channels
            this.selector.select();

            // Iterate over the set of keys for which events are available
            Iterator selectedKeys = this.selector.selectedKeys().iterator();
            while (selectedKeys.hasNext()) {
                SelectionKey key = (SelectionKey) selectedKeys.next();
                selectedKeys.remove();

                if (!key.isValid()) {
                    continue;
                }

                // Check what event is available and deal with it
                if (key.isAcceptable()) {
                    this.accept(key);
                } else if (key.isReadable()) {
                    this.read(key);
                } else if (key.isWritable()) {
                    this.write(key);
                }
            }
        } catch (Exception e) {
            logger.warn("Reading data", e);
        }
    }
}

并接受(accept())方法:

 private void accept(SelectionKey key) throws IOException {
    // For an accept to be pending the channel must be a server socket channel.
    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

    // Accept the connection and make it non-blocking        
    if (noOfConnections < MAX_CONNECTIONS) {
        SocketChannel socketChannel = serverSocketChannel.accept();
        Socket socket = socketChannel.socket();
        socket.setKeepAlive(true);
        socketChannel.configureBlocking(false);
        // Register the new SocketChannel with our Selector, indicating
        // we'd like to be notified when there's data waiting to be read
        socketChannel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//listener for incoming data: READ from client, WRITE to client
        noOfConnections++;
        logger.info("Accepted: " + socket.getRemoteSocketAddress().toString());
    } else {

        // REJECT INCOMING CONNECTION, but how?

        logger.warn("Server is full: " + noOfConnections + " / " + MAX_CONNECTIONS);
    }
}

如果连接未被接受,则会一遍又一遍地调用accept()方法。
感谢您的帮助!

1
为什么?为什么不让TCP backlog队列按设计方式工作呢? - user207421
待处理队列是指那些尚未被接受的连接请求列表。你不能使用待处理队列来限制实际已接受(已连接)套接字的数量。 - jarnbjo
@jambjo 你误解了。在待处理队列中的所有内容都是已经完成的连接(SYN-SYN/ACK-ACK),只是还没有被 accept() 返回而已。 - user207421
@Fidor 不,当被 accepted() 后,套接字会从队列中移除。我写过那本书,它绝对没有说明任何相反的内容。我知道 OP 所要求的内容。我在问 为什么,以及让后备队列自己工作有什么问题。我并没有暗示它们是一回事。 - user207421
1
@Maceij 当你达到最大值时,只需关闭监听套接字即可。 - user207421
显示剩余7条评论
3个回答

2

没有办法实现这一点,但我怀疑这不是你真正想要的,或者至少不是你真正应该做的。

如果你想停止接受连接,请将服务器套接字通道选择键中的interestOps更改为零,并在准备好再次接受时将其更改回OP_ACCEPT。在此期间,isAcceptable()永远不会为true,因此您描述的问题不会发生。

然而,这不会导致进一步的连接被拒绝:它只会将它们留在积压队列中,在我的意见和TCP设计人员的意见中,它们属于那里。如果积压队列填满,将会有另一种故障行为:在客户端中,其效果取决于系统:连接拒绝和/或超时。


1
好的,谢谢!我已经更改了代码,看起来它能够工作:private void accept(SelectionKey key){ ... if (noOfConnections >= MAX_CONNECTIONS) { key.interestOps(0); }}当连接数达到限制时,accept方法将不再被调用。但是客户端存在一些问题:如果selectionKey.isConnectable()返回true,我会调用socketChannel.finishConnect()方法,即使连接还没有在服务器上被接受。这种情况发生在服务器侦听被禁用后的前50个连接左右。之后一切正常,在客户端会出现ConnectException异常。 - Maciej
@Maciej 请再次阅读上面的评论。有一个监听等待队列。它包含已经被TCP堆栈接受但尚未调用accept()的连接。这意味着对于在等待队列中的连接,finishConnect()将返回true。这种情况发生50次表明等待队列的大小为50个元素。你无法改变这个事实。这就是我回答的内容。你无法获得你描述的功能。我在上面的第一句话中已经说过了。 - user207421
这样怎么样:如果接受的连接达到最大值,它会重定向到另一个处理程序,该处理程序仅接受和关闭传入连接,可能在关闭之前发送“抱歉,我已经满了,请稍后再试”的消息。虽然不完全符合OP的要求,但客户端会立即知道服务器已经拥堵。 - Fildor
我已经尝试过了,但问题在于客户端上首先'socketChannel.finishConnect()'返回true并显示消息“已连接”。而紧接着就会出现“对不起,我满了”的消息,这可能会让用户感到困惑。好吧,当用户按下像“加入游戏”这样的按钮时,他将看到消息“正在加载数据...”,服务器不会有任何响应,所以我想用户会知道存在连接问题。 - Maciej
@Fidor 为什么?这样做并不能实现他拒绝连接的目的。为什么不将它们保持为挂起状态? - user207421

0

嗯,我用以下方式解决了这个问题: 套接字上的挂起状态连接处于“中间状态”,这意味着您无法控制/拒绝它们。 特定虚拟机可能会以不同的方式使用/忽略/处理后备套接字参数。 这意味着您必须接受特定连接以接收相关对象并对其进行操作。

使用一个线程来接受连接,将已接受的连接传递给第二个线程进行处理。 创建一些变量以记录活动连接数。 现在,当活动连接数少于所需的最大值时,接受连接,将数量增加1,并传递给第二个线程进行处理。 否则,接受连接并立即关闭。

此外,在连接处理线程中完成后,将活动连接数减少1,以指示有一个更多的空闲通道可用。

EDT:刚刚为Java.Net NIO的服务器机制创建了“存根”。 可以根据OP的需求进行调整:

package servertest;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;


public class Servertest extends Thread {
    final int MAXIMUM_CONNECTIONS = 3;
    int connectionnumber = 0;


    /**
     * @param args the command line arguments
     * @throws java.io.IOException
     */
    public static void main(String[] args){

        new Servertest().start();
    }

    @Override
    public void run() {
        try {
            ServerSocket sc = new ServerSocket(33000, 50, InetAddress.getLoopbackAddress());
          while (sc.isBound()) {
            Socket connection = sc.accept();
            if(connectionnumber<=MAXIMUM_CONNECTIONS){
                new ClientConnection(connection).start();
                connectionnumber++;
            } else {
                //Optionally write some error response to client
                connection.close();
            }
          }
        } catch (IOException ex) {
            Logger.getLogger(Servertest.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private class ClientConnection extends Thread{
        private Socket connection;
        public ClientConnection(Socket connection) {
            this.connection=connection;
        }

        @Override
        public void run() {
            try {
                //make user interaction

                connection.close();


            } catch (IOException ex) {
                Logger.getLogger(Servertest.class.getName()).log(Level.SEVERE, null, ex);
            }

            connectionnumber--;

        }


    }

}

0

我认为调整后备队列几乎永远不会是一个好的解决方案。但是也许你可以考虑停止监听。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接