Skip to content

Latest commit

 

History

History
571 lines (393 loc) · 20 KB

File metadata and controls

571 lines (393 loc) · 20 KB

Writing TCP servers and clients

Vert.x allows you to easily write non-blocking TCP clients and servers.

Creating a TCP server

The simplest way to create a TCP server, using all default options is as follows:

{@link examples.TcpExamples#defaultTcpServer}

Configuring a TCP server

If you don’t want the default, a server can be configured by passing in a {@link io.vertx.core.net.TcpServerConfig} instance when creating it:

{@link examples.TcpExamples#configurationOfATcpServer}

Start the Server Listening

To tell the server to listen for incoming requests you use one of the {@link io.vertx.core.net.NetServer#listen} alternatives.

To tell the server to listen at the host and port as specified in the options:

{@link examples.TcpExamples#startingATcpServer}

Or to specify the host and port in the call to listen, ignoring what is configured in the options:

{@link examples.TcpExamples#startingATcpServerWithHostAndPort}

The default host is 0.0.0.0 which means 'listen on all available addresses' and the default port is 0, which is a special value that instructs the server to find a random unused local port and use that.

The actual bind is asynchronous, so the server might not actually be listening until some time after the call to listen has returned.

If you want to be notified when the server is actually listening you can provide a handler to the listen call. For example:

{@link examples.TcpExamples#gettingNotifiedWhenStartingATcpServer}

Listening on a random port

If 0 is used as the listening port, the server will find an unused random port to listen on.

To find out the real port the server is listening on you can call {@link io.vertx.core.net.NetServer#actualPort()}.

{@link examples.TcpExamples#startingATcpServerOnARandomPort}

Listening to Unix domain sockets

When running on JDK 16+, or using a native transport, a server can listen to Unix domain sockets:

{@link examples.TcpExamples#startingAUnixDomainSocketSServer}

Getting notified of incoming connections

To be notified when a connection is made you need to set a {@link io.vertx.core.net.NetServer#connectHandler(io.vertx.core.Handler)}:

{@link examples.TcpExamples#gettingNotifiedOfIncomingConnections}

When a connection is made the handler will be called with an instance of {@link io.vertx.core.net.NetSocket}.

This is a socket-like interface to the actual connection, and allows you to read and write data as well as do various other things like close the socket.

Reading data from the socket

To read data from the socket you set the {@link io.vertx.core.net.NetSocket#handler(io.vertx.core.Handler)} on the socket.

This handler will be called with an instance of {@link io.vertx.core.buffer.Buffer} every time data is received on the socket.

{@link examples.TcpExamples#readingDataFromASocket}

Writing data to a socket

You write to a socket using one of {@link io.vertx.core.net.NetSocket#write}.

{@link examples.TcpExamples#writingDataToASocket}

Write operations are asynchronous and may not occur until some time after the call to write has returned.

Closed handler

If you want to be notified when a socket is closed, you can set a {@link io.vertx.core.net.NetSocket#closeHandler(io.vertx.core.Handler)} on it:

{@link examples.TcpExamples#gettingNotifiedOnSocketClose}

Handling exceptions

You can set an {@link io.vertx.core.net.NetSocket#exceptionHandler(io.vertx.core.Handler)} to receive any exceptions that happen on the socket.

You can set an {@link io.vertx.core.net.NetServer#exceptionHandler(io.vertx.core.Handler)} to receive any exceptions that happens before the connection is passed to the {@link io.vertx.core.net.NetServer#connectHandler(io.vertx.core.Handler)} , e.g during the TLS handshake.

Local and remote addresses

The local address of a {@link io.vertx.core.net.NetSocket} can be retrieved using {@link io.vertx.core.net.NetSocket#localAddress()}.

The remote address, (i.e. the address of the other end of the connection) of a {@link io.vertx.core.net.NetSocket} can be retrieved using {@link io.vertx.core.net.NetSocket#remoteAddress()}.

Sending files or resources from the classpath

Files and classpath resources can be written to the socket directly using {@link io.vertx.core.net.NetSocket#sendFile}. This can be a very efficient way to send files, as it can be handled by the OS kernel directly where supported by the operating system.

Please see the chapter about serving files from the classpath for restrictions of the classpath resolution or disabling it.

{@link examples.TcpExamples#sendingAFile}

Streaming sockets

Instances of {@link io.vertx.core.net.NetSocket} are also {@link io.vertx.core.streams.ReadStream} and {@link io.vertx.core.streams.WriteStream} instances, so they can be used to pipe data to or from other read and write streams.

See the chapter on streams for more information.

TCP graceful shut down

You can shut down a {@link io.vertx.core.net.NetServer#shutdown() server} or {@link io.vertx.core.net.NetClient#shutdown() client}.

Calling shutdown initiates the shut-down phase whereby the server or client are given the opportunity to perform clean-up actions and handle shutdown at the protocol level.

{@link examples.TcpExamples#gracefullyShuttingDownAServer}

Shut-down waits until all sockets are closed or the shut-down timeout fires. When the timeout fires, all sockets are forcibly closed.

Each opened socket is notified with a shutdown event, allowing to perform a protocol level close before the actual socket close.

{@link examples.TcpExamples#gettingNotifiedOnSocketShutdown}

Any socket without a shutdown handler is closed immediately

The default shut-down timeout is 30 seconds, you can override the amount of time

{@link examples.TcpExamples#gracefullyShuttingDownAServerWithTimeout}

TCP close

You can close a {@link io.vertx.core.net.NetServer#close() server} or {@link io.vertx.core.net.NetClient#close() client} to immediately close all open connections and releases all resources. Unlike shutdown there is not grace period.

The close is actually asynchronous and might not complete until some time after the call has returned. You can use the returned future to be notified when the actual close has completed.

This future is completed when the close has fully completed.

{@link examples.TcpExamples#closingAServer}

Automatic clean-up in verticles

If you’re creating TCP servers and clients from inside verticles, those servers and clients will be automatically closed when the verticle is undeployed.

Scaling - sharing TCP servers

The handlers of any TCP server are always executed on the same event loop thread.

This means that if you are running on a server with a lot of cores, and you only have this one instance deployed then you will have at most one core utilised on your server.

In order to utilise more cores of your server you will need to deploy more instances of the server.

You can instantiate more instances programmatically in your code:

{@link examples.TcpExamples#scalingATcpServerWithLoadBalancing}

Once you do this you will find the echo server works functionally identically to before, but all your cores on your server can be utilised and more work can be handled.

At this point you might be asking yourself 'How can you have more than one server listening on the same host and port? Surely you will get port conflicts as soon as you try and deploy more than one instance?'

Vert.x does a little magic here.*

When you deploy another server on the same host and port as an existing server it doesn’t actually try and create a new server listening on the same host/port.

Instead it internally maintains just a single server, and, as incoming connections arrive it distributes them in a round-robin fashion to any of the connect handlers.

Consequently Vert.x TCP servers can scale over available cores while each instance remains single threaded.

Creating a TCP client

The simplest way to create a TCP client, using all default options is as follows:

{@link examples.TcpExamples#defaultTcpClient}

Configuring a TCP client

If you don’t want the default, a client can be configured by passing in a {@link io.vertx.core.net.TcpClientConfig} instance when creating it:

{@link examples.TcpExamples#configurationOfATcpClient}

Making connections

To make a connection to a server you use {@link io.vertx.core.net.NetClient#connect(int,java.lang.String)}, specifying the port and host of the server, returning a future completed with the {@link io.vertx.core.net.NetSocket}

{@link examples.TcpExamples#connectingToAServer}

{@link io.vertx.core.net.ConnectOptions} gives more control over the connection, e.g. the local address the connection is bound to before connecting, when the operating system default is not desired:

{@link examples.TcpExamples#connectingToAServerFromALocalAddress}

Making connections to Unix domain sockets

When running on JDK 16+, or using a native transport, a client can connect to Unix domain sockets:

{@link examples.CoreExamples#tcpClientWithDomainSockets}

Configuring connection attempts

A client can be configured to automatically retry connecting to the server in the event that it cannot connect. This is configured with {@link io.vertx.core.net.TcpClientConfig#setReconnectInterval(java.time.Duration)} and {@link io.vertx.core.net.TcpClientConfig#setReconnectAttempts(int)}.

Note
Currently, Vert.x will not attempt to reconnect if a connection fails, reconnect attempts and interval only apply to creating initial connections.
{@link examples.TcpExamples#configurationOfTcpClientReconnect}

By default, multiple connection attempts are disabled.

Logging network activity

For debugging purposes, network activity can be logged:

{@link examples.TcpExamples#configurationOfTcpServerLogging}

Here is the output of a simple HTTP server

id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] READ: 78B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a |GET / HTTP/1.1..|
|00000010| 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a |Host: localhost:|
|00000020| 38 30 38 30 0d 0a 55 73 65 72 2d 41 67 65 6e 74 |8080..User-Agent|
|00000030| 3a 20 63 75 72 6c 2f 37 2e 36 34 2e 31 0d 0a 41 |: curl/7.64.1..A|
|00000040| 63 63 65 70 74 3a 20 2a 2f 2a 0d 0a 0d 0a       |ccept: */*....  |
+--------+-------------------------------------------------+----------------+
[id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] WRITE: 50B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
|00000010| 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a |.content-length:|
|00000020| 20 31 31 0d 0a 0d 0a 48 65 6c 6c 6f 20 57 6f 72 | 11....Hello Wor|
|00000030| 6c 64                                           |ld              |
+--------+-------------------------------------------------+----------------+
[id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] READ COMPLETE
[id: 0x359e3df6, L:/127.0.0.1:8080 - R:/127.0.0.1:65351] FLUSH

By default, binary data is logged in hex format.

You can reduce the data format verbosity to only print the buffer length instead of the entire data by setting the log data fomat.

{@link examples.TcpExamples#configurationOfTcpServerLoggingFormat}

Here is the same output with simple buffer format

[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] READ: 78B
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] WRITE: 50B
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] READ COMPLETE
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] FLUSH
[id: 0xda8d41dc, L:/127.0.0.1:8080 - R:/127.0.0.1:65399] READ COMPLETE
[id: 0xda8d41dc, L:/127.0.0.1:8080 ! R:/127.0.0.1:65399] INACTIVE
[id: 0xda8d41dc, L:/127.0.0.1:8080 ! R:/127.0.0.1:65399] UNREGISTERED

Clients can also log network activity

{@link examples.TcpExamples#configurationOfTcpClientLogging}

Network activity is logged by Netty with the DEBUG level and with the io.netty.handler.logging.LoggingHandler name. When using network activity logging there are a few things to keep in mind:

  • logging is not performed by Vert.x logging but by Netty

  • this is not a production feature

You should read the [netty-logging] section.

Throttling inbound and outbound bandwidth of TCP connections

TCP server can be configured with traffic shaping options to enable bandwidth limiting. Both inbound and outbound bandwidth can be limited through {@link io.vertx.core.net.TrafficShapingOptions}. You can set traffic shaping options through {@link io.vertx.core.net.TcpServerConfig}.

{@link examples.TcpExamples#configurationOfTcpServerTrafficShaping}

These traffic shaping options can also be dynamically updated after server start.

{@link examples.TcpExamples#updateOfTcpServerTrafficShaping}

Configuring servers and clients to work with SSL/TLS

TCP clients and servers can be configured to use Transport Layer Security - earlier versions of TLS were known as SSL.

The APIs of the servers and clients are identical whether SSL/TLS is used, and it’s enabled by configuring the {@link io.vertx.core.net.TcpClientConfig} or {@link io.vertx.core.net.TcpServerConfig} instances used to create the servers or clients.

Enabling SSL/TLS on the server

Server SSL/TLS is enabled with the TcpServerConfig {@link io.vertx.core.net.TcpServerConfig#setSsl(boolean) ssl} setting.

By default, it is disabled.

{@link examples.TcpExamples#configurationOfAnSslTcpServer}

You can read more about SSL server configuration

Enabling SSL/TLS on the client

Client SSL/TLS is enabled with the TcpClientConfig {@link io.vertx.core.net.TcpClientConfig#setSsl(boolean) ssl} property or {@link io.vertx.core.net.ConnectOptions#setSsl(boolean) ssl} property.

The former defines the default client behavior.

When enabled, the client performs an SSL/TLS handshake with the provided SSL configuration.

{@link examples.TcpExamples#configurationOfAnSslTcpClient}

The latter provides a fine-grained per socket configuration

{@link examples.TcpExamples#enablingSslOnATcpSocket}

You can also set {@link io.vertx.core.net.ClientSSLOptions} at connect time.

{@link examples.TcpExamples#configurationOfAnSslClientSocket}

You can read more about SSL client configuration.

Client Server Name Indication (SNI)

The client implicitly sends the connecting host as an SNI server name for Fully Qualified Domain Name (FQDN).

You can provide an explicit server name when connecting a socket

{@link examples.TcpExamples#useSniInClient}

It can be used for different purposes:

  • present a server name different than the server host

  • present a server name while connecting to an IP

  • force to present a server name when using shortname

Client host verification

By default, host verification is not configured on the client. This verifies the CN portion of the server certificate against the server hostname to avoid Man-in-the-middle attacks.

You must configure it explicitly on your client

  • "" (empty string) disables host verification

  • "HTTPS" enables HTTP over TLS verification

  • LDAPS enables LDAP v3 extension for TLS verification

{@link examples.TcpExamples#configurationOfClientHostVerification}
Note
the Vert.x HTTP client uses the TCP client and configures with "HTTPS" the verification algorithm.

Upgrading connections to SSL/TLS

A non SSL/TLS connection can be upgraded to SSL/TLS using {@link io.vertx.core.net.NetSocket#upgradeToSsl()}.

{@link examples.TcpExamples#upgradeASocketToTls}
Note
usually client and server perform the handshake upgrade simultaneously, this is usually known as StartTLS and it used in protocols that start a conversation in plain text and decide to upgrade the connection with a TLS handshake

Updating SSL/TLS configuration

You can use the updateSSLOptions method to update the key/certifications or trust on a TCP server or client (e.g. to implement certificate rotation).

{@link examples.TcpExamples#updateSslOptionsOfATcpServer}

When the update succeeds the new SSL configuration is used, otherwise the previous configuration is preserved.

Note
The options object is compared (using equals) against the existing options to prevent an update when the objects are equals since loading options can be costly. When object are equals, you can use the force parameter to force the update.

Using a proxy for client connections

The {@link io.vertx.core.net.NetClient} supports either an HTTP/1.x CONNECT, an HTTPS (HTTP CONNECT over SSL/TLS), SOCKS4a or SOCKS5 proxy.

The proxy can be configured in the {@link io.vertx.core.net.TcpClientConfig} by setting a {@link io.vertx.core.net.ProxyOptions} object containing proxy type, hostname, port and optionally username and password.

To reach the proxy itself over SSL/TLS, use the {@link io.vertx.core.net.ProxyType#HTTPS} proxy type and configure the SSL options for the proxy connection with {@link io.vertx.core.net.ProxyOptions#setSslOptions(io.vertx.core.net.ClientSSLOptions)}. These options (trust store, optional client certificate, hostname verification) apply to the connection to the proxy and are independent of the options used for the target server. Hostname verification of the proxy certificate is enabled by default. Note that {@link io.vertx.core.net.ProxyType#HTTPS} denotes a proxy that is itself reached over SSL/TLS, which is distinct from the https_proxy environment-variable convention of a proxy used for https traffic.

Here’s an example:

{@link examples.TcpExamples#configurationOfTcpClientProxy}

The DNS resolution is always done on the proxy server, to achieve the functionality of a SOCKS4 client, it is necessary to resolve the DNS address locally.

You can use {@link io.vertx.core.net.TcpClientConfig#setNonProxyHosts} to configure a list of host bypassing the proxy. The lists accepts * wildcard for matching domains:

{@link examples.TcpExamples#nonProxyHosts}

Using HA PROXY protocol

HA PROXY protocol provides a convenient way to safely transport connection information such as a client’s address across multiple layers of NAT or TCP proxies.

HA PROXY protocol can be enabled by setting the option {@link io.vertx.core.net.TcpServerConfig#setUseProxyProtocol(boolean)} and adding the following dependency in your classpath:

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-codec-haproxy</artifactId>
  <!--<version>Should align with netty version that Vert.x uses</version>-->
</dependency>
{@link examples.TcpExamples#configurationOfTcpServerHAProxy}