從TCP服務器到I/O模型

學習Netty就不得不從TCP服務器和I/O模型說起,瞭解TCP服務器架構和I/O模型的演進有助於深入瞭解Netty。

TCP服務器的架構

一般地,TCP服務器有兩種套接字,監聽套接字和已連接套接字。監聽套接字用於TCP的監聽,一旦連接建立便產生已連接套接字,服務器利用已連接套接字與客戶端進行通信。

  • 迭代服務器
  • 在迭代服務器中,監聽套接字會一直阻塞直到能夠接受連接,接受連接後利用已連接套接字與客戶端通信,這些工作都是在同一個線程中完成的,示意Java代碼如下。這種模式是串行處理,很難應對併發量較大的情況。
try (ServerSocket serverSocket = new ServerSocket(port)) {
while (true) {
Socket socket = serverSocket.accept();
// ...
}
} catch (IOException e) {
e.printStackTrace();
}
  • 併發服務器
  • 在併發服務器中,監聽套接字會一直阻塞直到能夠接受連接,接受連接後,服務器會讓子線程/進程去處理已連接套接字,示意Java代碼如下。這種模式雖然是並行處理,可以不干擾服務端的監聽,但是由於每次新來一個請求就會產生一個新的線程去處理,出於資源的考慮很難應對高併發的情況。
try (ServerSocket serverSocket = new ServerSocket(port)) {
while (true) {
final Socket socket = serverSocket.accept();
new Thread(() -> {
// ...
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
  • IO多路複用(事件驅動)
  • 為了能在一個線程中處理多個連接,可以使用IO多路複用(事件驅動),典型的有Linux C中的select、poll和epoll,Java的Selector類等。以Selector為例,調用者在選擇器上為不同的連接註冊自己感興趣的事件(可讀/可寫/可接受/可連接),然後阻塞在select上,當事件發生時調用者便會得到通知,並且知道是哪個連接觸發了事件,以便可以進一步處理。
  • Selector實現的HTTP服務器示意如下:
 public class NioServer {
private static final int BUFFER_SIZE = 512;
private static final String HTTP_RESPONSE_BODY = "Hello wolrd\n";
private static final String HTTP_RESPONSE_HEADER = "HTTP/1.1 200\r\n" +
"Content-Type: text/html\r\n" + "Content-Length: " + HTTP_RESPONSE_BODY.length() + "\r\n\r\n";
private static final String HTTP_RESPONSE = HTTP_RESPONSE_HEADER + HTTP_RESPONSE_BODY;

// IO多路複用
public void selector(int port) {
try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open()) {
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(port));

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
Set<selectionkey> selectedKeys = selector.selectedKeys();
Iterator<selectionkey> iter = selectedKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
if (key.isAcceptable()) {
SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
System.out.println("accept: " + channel);
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("read: " + channel);
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int bytesRead = channel.read(buffer);
while (bytesRead > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
bytesRead = channel.read(buffer);
}
ByteBuffer writeBuf = ByteBuffer.wrap(HTTP_RESPONSE.getBytes());
while (writeBuf.hasRemaining()) {
channel.write(writeBuf);
}
channel.close();
}
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/<selectionkey>/<selectionkey>

I/O模型

一個輸入操作通常包括兩個不同的階段[1][2]

  1. 等待數據準備好
  2. 從內核向進程複製數據

(1) 阻塞式I/O模型

Netty學習 - 從TCP服務器到I/O模型

(2) 非阻塞式I/O模型

Netty學習 - 從TCP服務器到I/O模型

(3) I/O複用模型

Netty學習 - 從TCP服務器到I/O模型

(4) 信號驅動式I/O模型

Netty學習 - 從TCP服務器到I/O模型

(5) 異步I/O模型

Netty學習 - 從TCP服務器到I/O模型

同步I/O和異步I/O對比

POSIX把這兩個術語定義如下:

  • 同步I/O操作導致請求進程阻塞,直至I/O操作完成;
  • 異步I/O操作不導致請求進程阻塞。

根據上述定義,前4種模型——阻塞式I/O模型、非阻塞式I/O模型、I/O複用模型和信號驅動式I/O模型都是同步I/O模型,因為其中真正的I/O操作(recvfrom)將阻塞進程。只有異步I/O模型與POSIX定義的異步I/O相匹配。

Netty

Netty是一款異步的事件驅動的網絡應用編程框架,支持快速地開發可維護的高性能的面向協議的服務器和客戶端。與使用阻塞I/O來處理大量事件相比,使用非阻塞I/O來處理更快速、更經濟,Netty使用了Reactor模式將業務和網絡邏輯解耦,實現關注點分離[3]

Reactor模式

Reactor模式(反應堆模式)是一種處理一個或多個客戶端併發交付服務請求的事件設計模式。當請求抵達後,服務處理程序使用I/O多路複用策略,然後同步地派發這些請求至相關的請求處理程序[4]

Reactor模式中的角色:

  • Reactor:監聽端口,響應與分發事件;
  • Acceptor:當Accept事件到來時Reactor將Accept事件分發給Acceptor,Acceptor將已連接套接字的通道註冊到Reactor上;
  • Handler:已連接套接字做業務處理。

單Reactor單線程

在這種模式中,Reactor、Acceptor和Handler都運行在一個線程中。

Netty學習 - 從TCP服務器到I/O模型

單Reactor多線程

在這種模式中,Reactor和Acceptor運行在同一個線程,而Handler只有在讀和寫階段與Reactor和Acceptor運行在同一個線程,讀寫之間對數據的處理會被Reactor分發到線程池中。

Netty學習 - 從TCP服務器到I/O模型

多Reactor多線程

在這種模式中,主Reactor負責監聽,與Acceptor運行在同一個線程,Acceptor會將已連接套接字的通道註冊到從Reactor上,從Reactor負責響應和分發事件,起到類似多線程Reactor的作用。Netty服務端使用了該種模式。

Netty學習 - 從TCP服務器到I/O模型


分享到:


相關文章: