本文依照 知識共享許可協(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的實例也是ReadStream和WriteStream的實例,所以它們可以用于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ù)
客戶端可以被配置成鏈接失敗時自動重連。有兩個方法,setReconnectInterval和setReconnectAttempts。
注意:當(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沒有影響,在NetClientOptions和NetServerOptions實例上配置。
在服務(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);
NetServerOptions和NetClientOptions對象都可以指定加密算法套件。