Vert.x3 Core手冊 [Part 2. TCP 服務(wù)器與客戶端 ]

本文依照 知識共享許可協(xié)議(署名-非商業(yè)性使用-禁止演繹) 發(fā)布。


編寫TCP 服務(wù)器和客戶端

Vert.x讓你很輕松地編寫非阻塞的TCP 服務(wù)器和客戶端。

創(chuàng)建TCP 服務(wù)器

創(chuàng)建TCP 服務(wù)器最簡單的方式是像下面這樣,使用缺省選項:
NetServer server = vertx.createNetServer();

配置TCP 服務(wù)器

不使用缺省選項時,可以在創(chuàng)建時傳入NetServerOptions對象:

NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);

服務(wù)器啟動監(jiān)聽

選擇listen方法中的一個,可以讓服務(wù)器監(jiān)聽傳入的請求。

讓服務(wù)器監(jiān)聽選項中指定的端口和主機(jī)地址:

NetServer server = vertx.createNetServer();
server.listen();

或者在方法調(diào)用時指定端口和主機(jī),這將忽略配置項:

NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");

缺省主機(jī)地址是0.0.0.0,其意義是在所有可用的地址上進(jìn)行監(jiān)聽;缺省端口是0,這是一個特殊的值,它會指示服務(wù)器隨機(jī)尋找一個可用的本地端口來使用。

實際的綁定是異步發(fā)生的。所以有可能在listen方法調(diào)用返回之后,服務(wù)器才開始監(jiān)聽。

如果想得到監(jiān)聽開始的通知,可以在調(diào)用listen方法時提供一個handler供回調(diào)執(zhí)行:

NetServer server = vertx.createNetServer();
server.listen(1234, "localhost", res -> {
  if (res.succeeded()) {
    System.out.println("Server is now listening!");
  } else {
    System.out.println("Failed to bind!");
  }
});

隨機(jī)監(jiān)聽某個端口

如果監(jiān)聽端口被設(shè)置為0,服務(wù)器將隨機(jī)尋找一個端口。

想知道服務(wù)器實際監(jiān)聽的端口,可以調(diào)用actualPort方法。

NetServer server = vertx.createNetServer();
server.listen(0, "localhost", res -> {
  if (res.succeeded()) {
    System.out.println("Server is now listening on actual port: " + server.actualPort());
  } else {
    System.out.println("Failed to bind!");
  }
});

收到鏈接傳入的通知

想在鏈接產(chǎn)生時收到通知,需要設(shè)置connectHandler

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
  // Handle the connection in here
});

鏈接產(chǎn)生時,這個handler將被調(diào)用,參數(shù)是NetSocket的實例。

NetSocket是對實際鏈接的一個類socket的接口(socket-like interface),你可以讀寫數(shù)據(jù),也可以關(guān)閉socket。

從socket讀數(shù)據(jù)

要從socket讀數(shù)據(jù),只需在socket上設(shè)置handler

這樣每次socket收到數(shù)據(jù)時,將會給傳入一個buffer并調(diào)用handler。

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
  socket.handler(buffer -> {
    System.out.println("I received some bytes: " + buffer.length());
  });
});

往socket寫數(shù)據(jù)

write方法用來寫入數(shù)據(jù)。

Buffer buffer = Buffer.buffer().appendFloat(12.34f).appendInt(123);
socket.write(buffer);

// Write a string in UTF-8 encoding
socket.write("some data");

// Write a string using the specified encoding
socket.write("some data", "UTF-16");

寫入是異步的,有可能write方法已經(jīng)返回而數(shù)據(jù)寫入還未發(fā)生。

結(jié)束的handler(Closed handler)

要在socket關(guān)閉時得到通知,可以設(shè)置一個closeHandler

socket.closeHandler(v -> {
  System.out.println("The socket has been closed");
});

處理異常

可以設(shè)置一個exceptionHandler來捕獲socket上發(fā)生的異常。

Event bus write handler

socket會在event bus上自動注冊一個handler,這個handler會在收到buffer時將其寫入socket。

這樣你可以在不同的verticle里、甚至是不同的Vert.x實例里發(fā)送buffer到這個地址,而這些數(shù)據(jù)將被寫入socket。

這個handler的地址可以用writeHandlerID方法獲得。

本地和遠(yuǎn)程地址

NetSocket的本地地址用localAddress方法獲取。

遠(yuǎn)程地址(即,鏈接另一端的地址),可以用remoteAddress方法獲取。

發(fā)送文件或類路徑里的資源

sendFile方法可以將文件或類路徑里的資源直接寫入socket。因為其可以借助操作系統(tǒng)內(nèi)核支持的操作來完成,所以這是一種很有效的發(fā)送文件的方式。

請參閱本章,可以了解關(guān)于serving files from the classpath 的限制或者關(guān)閉這個特性。
socket.sendFile("myfile.dat");

Streaming sockets

NetSocket的實例也是ReadStreamWriteStream的實例,所以它們可以用于pump data(指將數(shù)據(jù)轉(zhuǎn)發(fā)出去)或從其他流讀寫數(shù)據(jù)。

更多細(xì)節(jié)請參閱 streams and pumps

升級鏈接到 SSL/TLS

可以使用upgradeToSsl方法將一個非SSL/TLS的鏈接升級。

要使這個特性正常工作,服務(wù)器/客戶端需要配置安全選項。請參閱SSL/TLS這一節(jié)獲取更多細(xì)節(jié)。

關(guān)閉TCP 服務(wù)器

調(diào)用close方法可以關(guān)閉服務(wù)器。關(guān)閉服務(wù)器時,將會關(guān)閉所有打開的鏈接,并釋放所有的服務(wù)器資源。

關(guān)閉也是異步的,所以close方法返回時關(guān)閉可能還沒結(jié)束。若要在關(guān)閉完成時得到通知,需傳入一個handler。

server.close(res -> {
  if (res.succeeded()) {
    System.out.println("Server is now closed");
  } else {
    System.out.println("close failed");
  }
});

verticles的自動清理

如果你是在verticle內(nèi)部創(chuàng)建的TCP 服務(wù)器或客戶端,那當(dāng)verticle被卸載時,它們將被自動關(guān)閉。

擴(kuò)展-共享(Scaling - sharing) TCP 服務(wù)器

TCP 服務(wù)器的handlers將一直在同一個event loop線程上執(zhí)行。

這意味著如果在一個多核機(jī)器上運行時,只有一個實例被部署,你無法從多核中獲得任何多余的好處。

為了利用到機(jī)器上的更多cpu核心,你需要部署你的TCP 服務(wù)器的多個實例。

可以通過編程的方式實例化多個實例:

for (int i = 0; i < 10; i++) {
  NetServer server = vertx.createNetServer();
  server.connectHandler(socket -> {
    socket.handler(buffer -> {
      // Just echo back the data
      socket.write(buffer);
    });
  });
  server.listen(1234, "localhost");
}

或者,如果你使用了verticle,只需要部署你的服務(wù)器verticle的多個實例即可。可以在命令行里加上-instance選項:
vertx run com.mycompany.MyVerticle -instances 10

再或者,以編程方式部署你的verticle:

DeploymentOptions options = new DeploymentOptions().setInstances(10);
vertx.deployVerticle("com.mycompany.MyVerticle", options);

一旦你這么做了,你會發(fā)現(xiàn)服務(wù)器功能如以前一樣沒變,但它利用上了多核資源,可以做更多事。

這時候,你也許會問:“一臺主機(jī)的確定端口,怎么能有多個服務(wù)器監(jiān)聽呢?部署多個實例難道不會造成端口沖突嗎?”

Vert.x在這里耍了點小花招。*

當(dāng)你在同一個端口部署另一個服務(wù)器時,其實并沒有真的創(chuàng)建一個新服務(wù)器監(jiān)聽這個端口。

相反,內(nèi)部其實只維護(hù)了一個服務(wù)器,但是鏈接傳入時,會用輪詢的方式將鏈接分發(fā)給任意的connect handler。

因此,Vert.x的TCP 服務(wù)器可以在單線程的情況下,擴(kuò)展到多個可用的cpu核心。

創(chuàng)建TCP 客戶端

創(chuàng)建TCP客戶端也和服務(wù)器類似:
NetClient client = vertx.createNetClient();

配置TCP 客戶端

類似于服務(wù)器:

NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);

創(chuàng)建鏈接

指定了服務(wù)器的port和host后,可以使用connect方法創(chuàng)建到服務(wù)器的鏈接。之后handler將被調(diào)用,當(dāng)鏈接成功創(chuàng)建,傳入的參數(shù)會包含一個NetSocket;如果創(chuàng)建失敗,傳入的參數(shù)將包含失敗對象。

NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
client.connect(4321, "localhost", res -> {
  if (res.succeeded()) {
    System.out.println("Connected!");
    NetSocket socket = res.result();
  } else {
    System.out.println("Failed to connect: " + res.cause().getMessage());
  }
});

配置嘗試連接的次數(shù)

客戶端可以被配置成鏈接失敗時自動重連。有兩個方法,setReconnectIntervalsetReconnectAttempts

注意:當(dāng)前Vert.x不會嘗試重連,這兩個特性僅僅在鏈接創(chuàng)建時可用。

NetClientOptions options = new NetClientOptions().
    setReconnectAttempts(10).
    setReconnectInterval(500);

NetClient client = vertx.createNetClient(options);

缺省情況下,創(chuàng)建多個鏈接是被禁止的。

配置服務(wù)器和客戶端使用 SSL/TLS

TCP客戶端/服務(wù)器通過配置可以使用Transport Layer Security(前身是大名鼎鼎的SSL)。

是否使用SSL/TLS 對API沒有影響,在NetClientOptionsNetServerOptions實例上配置。

在服務(wù)端啟用 SSL/TLS

通過設(shè)置ssl可以啟用 SSL/TLS。

為服務(wù)端指定 key/certificate

為了客戶端能校驗SSL/TLS 服務(wù)的合法性,服務(wù)端通常會提供證書給客戶端。

有好幾種方式可以配置服務(wù)端的Certificates/keys 。

第一種方式是指定一個Java key-store的地址,這其中應(yīng)該包含證書(the certificate)和私鑰(private key)。

JDK中有一個實用的程序keytool可以管理Java key stores。

key store的密碼也是必要的:

NetServerOptions options = new NetServerOptions().setSsl(true).setKeyStoreOptions(
    new JksOptions().
        setPath("/path/to/your/server-keystore.jks").
        setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);

其二,你可以讀取key store并將之存入buffer直接提供:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.jks");
JksOptions jksOptions = new JksOptions().
    setValue(myKeyStoreAsABuffer).
    setPassword("password-of-your-keystore");
NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setKeyStoreOptions(jksOptions);
NetServer server = vertx.createNetServer(options);

PKCS#12 格式的Key/certificate,通常文件后綴名是.pfx.p12,它們的載入方式類同于Java key store:

NetServerOptions options = new NetServerOptions().setSsl(true).setPfxKeyCertOptions(
    new PfxOptions().
        setPath("/path/to/your/server-keystore.pfx").
        setPassword("password-of-your-keystore")
);
NetServer server = vertx.createNetServer(options);

支持以buffer格式進(jìn)行配置:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-keystore.pfx");
PfxOptions pfxOptions = new PfxOptions().
    setValue(myKeyStoreAsABuffer).
    setPassword("password-of-your-keystore");
NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setPfxKeyCertOptions(pfxOptions);
NetServer server = vertx.createNetServer(options);

還有一種分別提供私鑰和證書的方式用到.pem文件。

NetServerOptions options = new NetServerOptions().setSsl(true).setPemKeyCertOptions(
    new PemKeyCertOptions().
        setKeyPath("/path/to/your/server-key.pem").
        setCertPath("/path/to/your/server-cert.pem")
);
NetServer server = vertx.createNetServer(options);

同樣支持buffer:

Buffer myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-key.pem");
Buffer myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-cert.pem");
PemKeyCertOptions pemOptions = new PemKeyCertOptions().
    setKeyValue(myKeyAsABuffer).
    setCertValue(myCertAsABuffer);
NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setPemKeyCertOptions(pemOptions);
NetServer server = vertx.createNetServer(options);

請謹(jǐn)記以pem 配置時,私鑰是未加密的。

Specifying trust for the server

為了驗證客戶端的身份,SSL/TLS 服務(wù)器可以使用證書授權(quán)(a certificate authority)。

有多種途徑可為服務(wù)器配置證書授權(quán)。

Java trust store同樣可以用keytool管理。

同樣需要提供密碼:

NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setClientAuth(ClientAuth.REQUIRED).
    setTrustStoreOptions(
        new JksOptions().
            setPath("/path/to/your/truststore.jks").
            setPassword("password-of-your-truststore")
    );
NetServer server = vertx.createNetServer(options);

同樣可以讀入buffer再提供:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setClientAuth(ClientAuth.REQUIRED).
    setTrustStoreOptions(
        new JksOptions().
            setValue(myTrustStoreAsABuffer).
            setPassword("password-of-your-truststore")
    );
NetServer server = vertx.createNetServer(options);

PKCS#12格式的證書授權(quán)(Certificate authority )同樣可用:

NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setClientAuth(ClientAuth.REQUIRED).
    setPfxTrustOptions(
        new PfxOptions().
            setPath("/path/to/your/truststore.pfx").
            setPassword("password-of-your-truststore")
    );
NetServer server = vertx.createNetServer(options);

buffer格式的:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setClientAuth(ClientAuth.REQUIRED).
    setPfxTrustOptions(
        new PfxOptions().
            setValue(myTrustStoreAsABuffer).
            setPassword("password-of-your-truststore")
    );
NetServer server = vertx.createNetServer(options);

.pem文件也可用:

NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setClientAuth(ClientAuth.REQUIRED).
    setPemTrustOptions(
        new PemTrustOptions().
            addCertPath("/path/to/your/server-ca.pem")
    );
NetServer server = vertx.createNetServer(options);

對應(yīng)的buffer格式的:

Buffer myCaAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/server-ca.pfx");
NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setClientAuth(ClientAuth.REQUIRED).
    setPemTrustOptions(
        new PemTrustOptions().
            addCertValue(myCaAsABuffer)
    );
NetServer server = vertx.createNetServer(options);

在客戶端啟用 SSL/TLS

要讓客戶端用上SSL,配置也很容易。這里的API與使用標(biāo)準(zhǔn)socket時極其相似。

調(diào)用setSSL(true)方法即可。

Client trust configuration

如果在客戶端將trustAll 設(shè)置為true,客戶端將會信任所有的服務(wù)器證書。這種情況下,鏈接仍然會被加密,不過容易受到‘中間人攻擊’。換言之,其實你沒法確定連上的是誰,這點需要加以注意。缺省值是false。

NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setTrustAll(true);
NetClient client = vertx.createNetClient(options);

如果未曾設(shè)置trustAll ,那么必須配置客戶端的trust store,其中應(yīng)該包含客戶端信任的服務(wù)器證書。

與服務(wù)端的配置類似,客戶端的trust也有下面幾種途徑:

一是指定包含證書授權(quán)的Java trust-store的位置。

這是一個標(biāo)準(zhǔn)的Java key store,與服務(wù)端的一樣。客戶端trust store位置的設(shè)置通過JksOptions對象的path方法完成。客戶端發(fā)起連接時,如果服務(wù)器的證書不在客戶端的 trust store 里,則連接請求不會成功。

NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setTrustStoreOptions(
        new JksOptions().
            setPath("/path/to/your/truststore.jks").
            setPassword("password-of-your-truststore")
    );
NetClient client = vertx.createNetClient(options);

buffer支持:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks");
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setTrustStoreOptions(
        new JksOptions().
            setValue(myTrustStoreAsABuffer).
            setPassword("password-of-your-truststore")
    );
NetClient client = vertx.createNetClient(options);

PKCS#12也類似:

NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setPfxTrustOptions(
        new PfxOptions().
            setPath("/path/to/your/truststore.pfx").
            setPassword("password-of-your-truststore")
    );
NetClient client = vertx.createNetClient(options);

buffer支持:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx");
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setPfxTrustOptions(
        new PfxOptions().
            setValue(myTrustStoreAsABuffer).
            setPassword("password-of-your-truststore")
    );
NetClient client = vertx.createNetClient(options);

.pem文件也可以:

NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setPemTrustOptions(
        new PemTrustOptions().
            addCertPath("/path/to/your/ca-cert.pem")
    );
NetClient client = vertx.createNetClient(options);

同樣還有buffer支持:

Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/ca-cert.pem");
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setPemTrustOptions(
        new PemTrustOptions().
            addCertValue(myTrustStoreAsABuffer)
    );
NetClient client = vertx.createNetClient(options);

為客戶端指定key/certificate

如果服務(wù)端要驗證客戶端的身份,那么在發(fā)起連接時,客戶端需要向服務(wù)端提交自己的證書。下面幾種方式可以配置客戶端:

其一是指定包含密鑰和證書的Java key-store的位置。同樣這也是一個常規(guī)的Java key store。仍然通過JksOptions對象的path方法設(shè)置。

NetClientOptions options = new NetClientOptions().setSsl(true).setKeyStoreOptions(
    new JksOptions().
        setPath("/path/to/your/client-keystore.jks").
        setPassword("password-of-your-keystore")
);
NetClient client = vertx.createNetClient(options);

buffer支持:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.jks");
JksOptions jksOptions = new JksOptions().
    setValue(myKeyStoreAsABuffer).
    setPassword("password-of-your-keystore");
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setKeyStoreOptions(jksOptions);
NetClient client = vertx.createNetClient(options);

PKCS#12格式的密鑰/證書:

NetClientOptions options = new NetClientOptions().setSsl(true).setPfxKeyCertOptions(
    new PfxOptions().
        setPath("/path/to/your/client-keystore.pfx").
        setPassword("password-of-your-keystore")
);
NetClient client = vertx.createNetClient(options);

buffer支持:

Buffer myKeyStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-keystore.pfx");
PfxOptions pfxOptions = new PfxOptions().
    setValue(myKeyStoreAsABuffer).
    setPassword("password-of-your-keystore");
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setPfxKeyCertOptions(pfxOptions);
NetClient client = vertx.createNetClient(options);

.pem文件支持:

NetClientOptions options = new NetClientOptions().setSsl(true).setPemKeyCertOptions(
    new PemKeyCertOptions().
        setKeyPath("/path/to/your/client-key.pem").
        setCertPath("/path/to/your/client-cert.pem")
);
NetClient client = vertx.createNetClient(options);

buffer支持:

Buffer myKeyAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-key.pem");
Buffer myCertAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/client-cert.pem");
PemKeyCertOptions pemOptions = new PemKeyCertOptions().
    setKeyValue(myKeyAsABuffer).
    setCertValue(myCertAsABuffer);
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setPemKeyCertOptions(pemOptions);
NetClient client = vertx.createNetClient(options);

請謹(jǐn)記pem 配置中,私鑰是未加密的。

撤銷證書授權(quán)(Revoking certificate authorities)

被撤銷的證書不應(yīng)再被信任,這可以使用證書撤銷列表( a certificate revocation list (CRL))來配置。crlPath用來配置crl列表:

NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setTrustStoreOptions(trustOptions).
    addCrlPath("/path/to/your/crl.pem");
NetClient client = vertx.createNetClient(options);

buffer支持:

Buffer myCrlAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/crl.pem");
NetClientOptions options = new NetClientOptions().
    setSsl(true).
    setTrustStoreOptions(trustOptions).
    addCrlValue(myCrlAsABuffer);
NetClient client = vertx.createNetClient(options);

配置加密算法套件( the Cipher suite )

缺省情況下,TLS 配置使用的是運行Vert.x的JVM自帶的加密算法套件。它也可以用一組已啟用的加密算法來配置:

NetServerOptions options = new NetServerOptions().
    setSsl(true).
    setKeyStoreOptions(keyStoreOptions).
    addEnabledCipherSuite("ECDHE-RSA-AES128-GCM-SHA256").
    addEnabledCipherSuite("ECDHE-ECDSA-AES128-GCM-SHA256").
    addEnabledCipherSuite("ECDHE-RSA-AES256-GCM-SHA384").
    addEnabledCipherSuite("CDHE-ECDSA-AES256-GCM-SHA384");
NetServer server = vertx.createNetServer(options);

NetServerOptionsNetClientOptions對象都可以指定加密算法套件。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容