本文依照 知识共享许可协议(署名-非商业性使用-禁止演绎) 发布。
编写TCP 服务器和客户端
Vert.x让你很轻松地编写非阻塞的TCP 服务器和客户端。
创建TCP 服务器
创建TCP 服务器最简单的方式是像下面这样,使用缺省选项:
NetServer server = vertx.createNetServer();
配置TCP 服务器
不使用缺省选项时,可以在创建时传入NetServerOptions对象:
NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);
服务器启动监听
选择listen方法中的一个,可以让服务器监听传入的请求。
让服务器监听选项中指定的端口和主机地址:
NetServer server = vertx.createNetServer();
server.listen();
或者在方法调用时指定端口和主机,这将忽略配置项:
NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");
缺省主机地址是0.0.0.0
,其意义是在所有可用的地址上进行监听;缺省端口是0
,这是一个特殊的值,它会指示服务器随机寻找一个可用的本地端口来使用。
实际的绑定是异步发生的。所以有可能在listen方法调用返回之后,服务器才开始监听。
如果想得到监听开始的通知,可以在调用listen方法时提供一个handler供回调执行:
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!");
}
});
随机监听某个端口
如果监听端口被设置为0,服务器将随机寻找一个端口。
想知道服务器实际监听的端口,可以调用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!");
}
});
收到链接传入的通知
想在链接产生时收到通知,需要设置connectHandler:
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
// Handle the connection in here
});
链接产生时,这个handler将被调用,参数是NetSocket的实例。
NetSocket是对实际链接的一个类socket的接口(socket-like interface),你可以读写数据,也可以关闭socket。
从socket读数据
要从socket读数据,只需在socket上设置handler。
这样每次socket收到数据时,将会给传入一个buffer并调用handler。
NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
socket.handler(buffer -> {
System.out.println("I received some bytes: " + buffer.length());
});
});
往socket写数据
write方法用来写入数据。
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方法已经返回而数据写入还未发生。
结束的handler(Closed handler)
要在socket关闭时得到通知,可以设置一个closeHandler:
socket.closeHandler(v -> {
System.out.println("The socket has been closed");
});
处理异常
可以设置一个exceptionHandler来捕获socket上发生的异常。
Event bus write handler
socket会在event bus上自动注册一个handler,这个handler会在收到buffer时将其写入socket。
这样你可以在不同的verticle里、甚至是不同的Vert.x实例里发送buffer到这个地址,而这些数据将被写入socket。
这个handler的地址可以用writeHandlerID方法获得。
本地和远程地址
NetSocket的本地地址用localAddress方法获取。
远程地址(即,链接另一端的地址),可以用remoteAddress方法获取。
发送文件或类路径里的资源
sendFile方法可以将文件或类路径里的资源直接写入socket。因为其可以借助操作系统内核支持的操作来完成,所以这是一种很有效的发送文件的方式。
请参阅本章,可以了解关于serving files from the classpath 的限制或者关闭这个特性。
socket.sendFile("myfile.dat");
Streaming sockets
NetSocket的实例也是ReadStream和WriteStream的实例,所以它们可以用于pump data(指将数据转发出去)或从其他流读写数据。
更多细节请参阅 streams and pumps。
升级链接到 SSL/TLS
可以使用upgradeToSsl方法将一个非SSL/TLS的链接升级。
要使这个特性正常工作,服务器/客户端需要配置安全选项。请参阅SSL/TLS这一节获取更多细节。
关闭TCP 服务器
调用close方法可以关闭服务器。关闭服务器时,将会关闭所有打开的链接,并释放所有的服务器资源。
关闭也是异步的,所以close方法返回时关闭可能还没结束。若要在关闭完成时得到通知,需传入一个handler。
server.close(res -> {
if (res.succeeded()) {
System.out.println("Server is now closed");
} else {
System.out.println("close failed");
}
});
verticles的自动清理
如果你是在verticle内部创建的TCP 服务器或客户端,那当verticle被卸载时,它们将被自动关闭。
扩展-共享(Scaling - sharing) TCP 服务器
TCP 服务器的handlers将一直在同一个event loop线程上执行。
这意味着如果在一个多核机器上运行时,只有一个实例被部署,你无法从多核中获得任何多余的好处。
为了利用到机器上的更多cpu核心,你需要部署你的TCP 服务器的多个实例。
可以通过编程的方式实例化多个实例:
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,只需要部署你的服务器verticle的多个实例即可。可以在命令行里加上-instance
选项:
vertx run com.mycompany.MyVerticle -instances 10
再或者,以编程方式部署你的verticle:
DeploymentOptions options = new DeploymentOptions().setInstances(10);
vertx.deployVerticle("com.mycompany.MyVerticle", options);
一旦你这么做了,你会发现服务器功能如以前一样没变,但它利用上了多核资源,可以做更多事。
这时候,你也许会问:“一台主机的确定端口,怎么能有多个服务器监听呢?部署多个实例难道不会造成端口冲突吗?”
Vert.x在这里耍了点小花招。*
当你在同一个端口部署另一个服务器时,其实并没有真的创建一个新服务器监听这个端口。
相反,内部其实只维护了一个服务器,但是链接传入时,会用轮询的方式将链接分发给任意的connect handler。
因此,Vert.x的TCP 服务器可以在单线程的情况下,扩展到多个可用的cpu核心。
创建TCP 客户端
创建TCP客户端也和服务器类似:
NetClient client = vertx.createNetClient();
配置TCP 客户端
类似于服务器:
NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);
创建链接
指定了服务器的port和host后,可以使用connect方法创建到服务器的链接。之后handler将被调用,当链接成功创建,传入的参数会包含一个NetSocket;如果创建失败,传入的参数将包含失败对象。
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());
}
});
配置尝试连接的次数
客户端可以被配置成链接失败时自动重连。有两个方法,setReconnectInterval和setReconnectAttempts。
注意:当前Vert.x不会尝试重连,这两个特性仅仅在链接创建时可用。
NetClientOptions options = new NetClientOptions().
setReconnectAttempts(10).
setReconnectInterval(500);
NetClient client = vertx.createNetClient(options);
缺省情况下,创建多个链接是被禁止的。
配置服务器和客户端使用 SSL/TLS
TCP客户端/服务器通过配置可以使用Transport Layer Security(前身是大名鼎鼎的SSL)。
是否使用SSL/TLS 对API没有影响,在NetClientOptions和NetServerOptions实例上配置。
在服务端启用 SSL/TLS
通过设置ssl可以启用 SSL/TLS。
为服务端指定 key/certificate
为了客户端能校验SSL/TLS 服务的合法性,服务端通常会提供证书给客户端。
有好几种方式可以配置服务端的Certificates/keys 。
第一种方式是指定一个Java key-store的地址,这其中应该包含证书(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格式进行配置:
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);
请谨记以pem 配置时,私钥是未加密的。
Specifying trust for the server
为了验证客户端的身份,SSL/TLS 服务器可以使用证书授权(a certificate authority)。
有多种途径可为服务器配置证书授权。
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格式的证书授权(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);
对应的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与使用标准socket时极其相似。
调用setSSL(true)
方法即可。
Client trust configuration
如果在客户端将trustAll 设置为true,客户端将会信任所有的服务器证书。这种情况下,链接仍然会被加密,不过容易受到‘中间人攻击’。换言之,其实你没法确定连上的是谁,这点需要加以注意。缺省值是false。
NetClientOptions options = new NetClientOptions().
setSsl(true).
setTrustAll(true);
NetClient client = vertx.createNetClient(options);
如果未曾设置trustAll ,那么必须配置客户端的trust store,其中应该包含客户端信任的服务器证书。
与服务端的配置类似,客户端的trust也有下面几种途径:
一是指定包含证书授权的Java trust-store的位置。
这是一个标准的Java key store,与服务端的一样。客户端trust store位置的设置通过JksOptions对象的path方法完成。客户端发起连接时,如果服务器的证书不在客户端的 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
如果服务端要验证客户端的身份,那么在发起连接时,客户端需要向服务端提交自己的证书。下面几种方式可以配置客户端:
其一是指定包含密钥和证书的Java key-store的位置。同样这也是一个常规的Java key store。仍然通过JksOptions对象的path方法设置。
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);
请谨记pem 配置中,私钥是未加密的。
撤销证书授权(Revoking certificate authorities)
被撤销的证书不应再被信任,这可以使用证书撤销列表( 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对象都可以指定加密算法套件。