简体   繁体   English

非阻塞套接字

[英]Non-blocking sockets

What's the best way to implement a non-blocking socket in Java? 在Java中实现非阻塞套接字的最佳方法是什么?

Or is there such a thing? 还是有这样的事情? I have a program that communicates with a server through socket but I don't want the socket call to block/cause delay if there is a problem with the data/connection. 我有一个通过套接字与服务器通信的程序,但是如果数据/连接出现问题,我不希望套接字调用阻塞/导致延迟。

Java non-blocking socket , introduced in Java 2 Standard Edition 1.4, allow net communication between applications without blocking the processes using the sockets. Java 2 Standard Edition 1.4中引入的Java 非阻塞套接字允许应用程序之间的网络通信,而不会阻止使用套接字的进程。 But what a non-blocking socket is, in which contexts it can be useful, and how it works? 但是什么是非阻塞套接字,它在哪些上下文中有用,以及它是如何工作的?

What a non-blocking socket is? 什么是非阻塞套接字?

A non-blocking socket allows I/O operation on a channel without blocking the processes using it. 非阻塞套接字允许在通道上进行I / O操作,而不会阻止使用它的进程。 This means, we can use a single thread to handle multiple concurrent connections and gain an "asynchronous high-performance" read/write operations (some people may not agreed with that) 这意味着,我们可以使用单个线程来处理多个并发连接并获得“异步高性能”读/写操作(有些人可能不同意)

Ok, in which contexts it can be useful? 好的, 在哪些情况下它可能有用?

Suppose you would like to implement a server accepting diverse client connections. 假设您希望实现接受不同客户端连接的服务器。 Suppose, as well, that you would like the server to be able to process multiple requests simultaneously. 同时假设您希望服务器能够同时处理多个请求。 Using the traditional way you have two choices to develop such a server: 使用传统方式,您有两种选择来开发这样的服务器:

  • Implement a multi-thread server that manually handles a thread for each connection. 实现一个多线程服务器,为每个连接手动处理一个线程。
  • Using an external third-party module. 使用外部第三方模块。

Both solutions work, but adopting the first one you have to develop the whole thread-management solution, with related concurrency and conflict troubles. 这两种解决方案都有效,但是采用第一种解决方案来开发整个线程管理解决方案,具有相关的并发性和冲突问题。 The second solution makes the application dependent on a non-JDK external module and probably you have to adapt the library to your necessities. 第二种解决方案使应用程序依赖于非JDK外部模块,可能您必须使库适应您的需求。 By means of the non-blocking socket, you can implement a non-blocking server without directly managing threads or resorting to external modules. 通过非阻塞套接字,您可以实现非阻塞服务器,而无需直接管理线程或使用外部模块。

How it works? 这个怎么运作?

Before going into details, there are few terms that you need to understand: 在详细介绍之前,您需要了解的术语很少:

  • In NIO based implementations, instead of writing data onto output streams and reading data from input streams, we read and write data from buffers . 在基于NIO的实现中,我们不是将数据写入输出流并从输入流中读取数据,而是从缓冲区读取和写入数据。 A buffer can be defined as a temporary storage. 缓冲区可以定义为临时存储。
  • Channel transports bulk of data into and out of buffers . 通道将大量数据传入和传出缓冲区 Also, it can be viewed as an endpoint for communication. 此外,它可以被视为通信的端点。
  • Readiness Selection is a concept that refers to “the ability to choose a socket that will not block when data is read or written.” 准备选择是一个概念,它指的是“选择在读取或写入数据时不会阻塞的套接字的能力”。

Java NIO has a class called Selector that allows a single thread to examine I/O events on multiple channels. Java NIO有一个名为Selector的类,它允许单个线程检查多个通道上的I / O事件。 How is this possible? 这怎么可能? Well, the selector can check the "readiness" of a channel for events such as a client attempting a connection, or a read/write operation. 好吧, selector可以检查通道的“准备就绪” ,例如客户端尝试连接或读/写操作。 This is, each instance of Selector can monitor more socket channels and thus more connections. 也就是说, Selector每个实例都可以监视更多的套接字通道 ,从而监视更多的连接。 Now, when something happens on the channel (an event occurs), the selector informs the application to process the request . 现在,当通道上发生某些事件(发生事件)时, selector通知应用程序处理请求 The selector does it by creating event keys (or selection keys), which are instances of the SelectionKey class. selector通过创建事件键 (或选择键)来完成它,它们是SelectionKey类的实例。 Each key holds information about who is making the request and what type of the request is , as shown in the Figure 1. 每个key包含有关发出请求的人以及请求的 类型的信息 ,如图1所示。

图1:结构图 Figure 1: Structure diagram 图1:结构图

A basic implementation 一个基本的实现

A server implementation consists of an infinite loop in which the selector waits for events and creates the event keys. 服务器实现由无限循环组成,其中selector等待事件并创建事件密钥。 There are four possible types for a key: 密钥有四种可能的类型:

  • Acceptable: the associated client requests a connection. 可接受:关联的客户端请求连接。
  • Connectable: the server accepted the connection. 可连接:服务器接受连接。
  • Readable: the server can read. 可读:服务器可以读取。
  • Writeable: the server can write. 可写:服务器可以写。

Usually acceptable keys are created on the server side. 通常在服务器端创建acceptable密钥。 In fact, this kind of key simply informs the server that a client required a connection, then the server individuates the socket channel and associates this to the selector for read/write operations. 实际上,这种密钥只是简单地通知服务器客户端需要连接,然后服务器将套接字通道个性化并将其与选择器相关联以进行读/写操作。 After this, when the accepted client reads or writes something, the selector will create readable or writeable keys for that client.. 在此之后,当接受的客户端读取或写入某些内容时,选择器将为该客户端创建readablewriteable密钥。

Now you are ready to write the server in Java, following the proposed algorithm. 现在,您已准备好按照提议的算法用Java编写服务器。 The creation of the socket channel, the selector , and the socket-selector registration can be made in this way: 可以通过以下方式创建套接字通道, selector和套接字选择器注册:

final String HOSTNAME = "127.0.0.1";
final int PORT = 8511;

// This is how you open a ServerSocketChannel
serverChannel = ServerSocketChannel.open();
// You MUST configure as non-blocking or else you cannot register the serverChannel to the Selector.
serverChannel.configureBlocking(false);
// bind to the address that you will use to Serve.
serverChannel.socket().bind(new InetSocketAddress(HOSTNAME, PORT));

// This is how you open a Selector
selector = Selector.open();
/*
 * Here you are registering the serverSocketChannel to accept connection, thus the OP_ACCEPT.
 * This means that you just told your selector that this channel will be used to accept connections.
 * We can change this operation later to read/write, more on this later.
 */
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

First we create an instance of SocketChannel with ServerSocketChannel.open() method. 首先,我们使用ServerSocketChannel.open()方法创建一个SocketChannel实例。 Next, configureBlocking(false) invocation sets this channel as nonblocking . 接下来, configureBlocking(false)调用将此channel设置为非阻塞 The connection to the server is made by serverChannel.socket().bind() method. 与服务器的连接由serverChannel.socket().bind()方法完成。 The HOSTNAME represents the IP address of the server, and PORT is the communication port. HOSTNAME表示服务器的IP地址, PORT是通信端口。 Finally, invoke Selector.open() method to create a selector instance and register it to the channel and registration type. 最后,调用Selector.open()方法创建一个selector实例并将其注册到channel和注册类型。 In this example, the registration type is OP_ACCEPT , which means the selector merely reports that a client attempts a connection to the server. 在此示例中,注册类型为OP_ACCEPT ,这意味着选择器仅报告客户端尝试连接到服务器。 Other possible options are: OP_CONNECT , which will be used by the client; 其他可能的选项是: OP_CONNECT ,将由客户端使用; OP_READ ; OP_READ ; and OP_WRITE . OP_WRITE

Now we need to handle this requests using an infinite loop. 现在我们需要使用无限循环来处理这些请求。 A simple way is the following: 一个简单的方法如下:

// Run the server as long as the thread is not interrupted.
while (!Thread.currentThread().isInterrupted()) {
    /*
     * selector.select(TIMEOUT) is waiting for an OPERATION to be ready and is a blocking call.
     * For example, if a client connects right this second, then it will break from the select()
     * call and run the code below it. The TIMEOUT is not needed, but its just so it doesn't
     * block undefinable.
     */
    selector.select(TIMEOUT);

    /*
     * If we are here, it is because an operation happened (or the TIMEOUT expired).
     * We need to get the SelectionKeys from the selector to see what operations are available.
     * We use an iterator for this.
     */
    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

    while (keys.hasNext()) {
        SelectionKey key = keys.next();
        // remove the key so that we don't process this OPERATION again.
        keys.remove();

        // key could be invalid if for example, the client closed the connection.
        if (!key.isValid()) {
            continue;
        }
        /*
         * In the server, we start by listening to the OP_ACCEPT when we register with the Selector.
         * If the key from the keyset is Acceptable, then we must get ready to accept the client
         * connection and do something with it. Go read the comments in the accept method.
         */
        if (key.isAcceptable()) {
            System.out.println("Accepting connection");
            accept(key);
        }
        /*
         * If you already read the comments in the accept() method, then you know we changed
         * the OPERATION to OP_WRITE. This means that one of these keys in the iterator will return
         * a channel that is writable (key.isWritable()). The write() method will explain further.
         */
        if (key.isWritable()) {
            System.out.println("Writing...");
            write(key);
        }
        /*
         * If you already read the comments in the write method then you understand that we registered
         * the OPERATION OP_READ. That means that on the next Selector.select(), there is probably a key
         * that is ready to read (key.isReadable()). The read() method will explain further.
         */
        if (key.isReadable()) {
            System.out.println("Reading connection");
            read(key);
        }
    }
}

You can find the implementation source here 您可以在此处找到实施源

NOTE: Asynchronous Server 注意:异步服务器

An alternative to the the Non-blocking implementation we can deploy an Asynchronous Server. 作为非阻塞实现的替代方案,我们可以部署异步服务器。 For instance, you can use the AsynchronousServerSocketChannel class, which provides an asynchronous channel for stream-oriented listening sockets. 例如,您可以使用AsynchronousServerSocketChannel类,它为面向流的侦听套接字提供异步通道。

To use it, first execute its static open() method and then bind() it to a specific port . 要使用它,首先执行其静态open()方法,然后bind()到特定端口 Next, you'll execute its accept() method, passing to it a class that implements the CompletionHandler interface. 接下来,您将执行其accept()方法,并向其传递一个实现CompletionHandler接口的类。 Most often, you'll find that handler created as an anonymous inner class . 通常,您会发现将处理程序创建为匿名内部类

From this AsynchronousServerSocketChannel object, you invoke accept() to tell it to start listening for connections, passing to it a custom CompletionHandler instance. 从这个AsynchronousServerSocketChannel对象,你调用accept()告诉它开始侦听连接,并向它传递一个自定义的CompletionHandler实例。 When we invoke accept() , it returns immediately. 当我们调用accept() ,它会立即返回。 Note that this is different from the traditional blocking approach; 请注意,这与传统的阻塞方法不同; whereas the accept() method blocked until a client connected to it , the AsynchronousServerSocketChannel accept() method handles it for you. accept()方法被阻塞,直到客户端连接到它AsynchronousServerSocketChannel accept()方法为您处理它。

Here you have an example: 这里有一个例子:

public class NioSocketServer
{
    public NioSocketServer()
    {
        try {
            // Create an AsynchronousServerSocketChannel that will listen on port 5000
            final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel
                    .open()
                    .bind(new InetSocketAddress(5000));

            // Listen for a new request
            listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>()
            {
                @Override
                public void completed(AsynchronousSocketChannel ch, Void att)
                {
                    // Accept the next connection
                    listener.accept(null, this);

                    // Greet the client
                    ch.write(ByteBuffer.wrap("Hello, I am Echo Server 2020, let's have an engaging conversation!\n".getBytes()));

                    // Allocate a byte buffer (4K) to read from the client
                    ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
                    try {
                        // Read the first line
                        int bytesRead = ch.read(byteBuffer).get(20, TimeUnit.SECONDS);

                        boolean running = true;
                        while (bytesRead != -1 && running) {
                            System.out.println("bytes read: " + bytesRead);

                            // Make sure that we have data to read
                            if (byteBuffer.position() > 2) {
                                // Make the buffer ready to read
                                byteBuffer.flip();

                                // Convert the buffer into a line
                                byte[] lineBytes = new byte[bytesRead];
                                byteBuffer.get(lineBytes, 0, bytesRead);
                                String line = new String(lineBytes);

                                // Debug
                                System.out.println("Message: " + line);

                                // Echo back to the caller
                                ch.write(ByteBuffer.wrap(line.getBytes()));

                                // Make the buffer ready to write
                                byteBuffer.clear();

                                // Read the next line
                                bytesRead = ch.read(byteBuffer).get(20, TimeUnit.SECONDS);
                            } else {
                                // An empty line signifies the end of the conversation in our protocol
                                running = false;
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        // The user exceeded the 20 second timeout, so close the connection
                        ch.write(ByteBuffer.wrap("Good Bye\n".getBytes()));
                        System.out.println("Connection timed out, closing connection");
                    }

                    System.out.println("End of conversation");
                    try {
                        // Close the connection if we need to
                        if (ch.isOpen()) {
                            ch.close();
                        }
                    } catch (I/OException e1)
                    {
                        e1.printStackTrace();
                    }
                }

                @Override
                public void failed(Throwable exc, Void att)
                {
                    ///...
                }
            });
        } catch (I/OException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        NioSocketServer server = new NioSocketServer();
        try {
            Thread.sleep(60000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You can find the full code here 你可以在这里找到完整的代码

What's the best way to implement a non-blocking socket in Java? 在Java中实现非阻塞套接字的最佳方法是什么?

There is only one way. 只有一种方法。 SocketChannel.configureBlocking(false) . SocketChannel.configureBlocking(false)

Note that several of these answers are incorrect. 请注意,其中一些答案不正确。 SocketChannel.configureBlocking(false) puts it into non-blocking mode. SocketChannel.configureBlocking(false)将其置于非阻塞模式。 You don't need a Selector to do that. 您不需要Selector来执行此操作。 You only need a Selector to implement timeouts or multiplexed I/O with non-blocking sockets. 您只需要一个Selector来实现超时或带有非阻塞套接字的多路复用 I / O.

Apart from using non blocking IO, you might find it is much simpler to have a writing thread for your connection. 除了使用非阻塞IO之外,您可能会发现为连接创建一个写入线程要简单得多。

Note: if you only need a few thousand connections, one to two threads per connection is simpler. 注意:如果您只需要几千个连接,则每个连接一到两个线程更简单。 If you have around ten thousand or more connections per server you need NIO with Selectors. 如果每台服务器有大约一万或更多的连接,则需要使用选择器的NIO。

java.nio包提供了Selector的工作方式,就像在C中一样。

I just wrote this code . 我刚写了这段代码。 It works well . 它运作良好。 This is an example of the Java NIO as mentioned in the above answers but here i post the code . 这是上面答案中提到的Java NIO的一个例子,但在这里我发布了代码。

ServerSocketChannel ssc = null;
try {
    ssc = ServerSocketChannel.open();
    ssc.socket().bind(new InetSocketAddress(port));
    ssc.configureBlocking(false);
    while (true) {
        SocketChannel sc = ssc.accept();
        if (sc == null) {
            // No connections came .
        } else {
            // You got a connection. Do something
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM