[英]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?
但是什么是非阻塞套接字,它在哪些上下文中有用,以及它是如何工作的?
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:
使用传统方式,您有两种选择来开发这样的服务器:
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.
通过非阻塞套接字,您可以实现非阻塞服务器,而无需直接管理线程或使用外部模块。
Before going into details, there are few terms that you need to understand: 在详细介绍之前,您需要了解的术语很少:
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所示。
Figure 1: Structure diagram
图1:结构图
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: 密钥有四种可能的类型:
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.. 在此之后,当接受的客户端读取或写入某些内容时,选择器将为该客户端创建
readable
或writeable
密钥。
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 您可以在此处找到实施源
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.