Netty plugin
Overview
The Netty plugin enables load testing and performance testing for network protocols including TCP, UDP, and MQTT.
- Technologies addressed
-
-
TCP (Transmission Control Protocol): https://datatracker.ietf.org/doc/html/rfc793
-
UDP (User Datagram Protocol): https://datatracker.ietf.org/doc/html/rfc768
-
MQTT 3.1.0 / 3.1.1: https://mqtt.org/
-
- Dependency
-
io.qalipsis.plugin:qalipsis-plugin-netty - Namespace in scenario
-
netty() - Client library
-
Netty: refer to the Netty project.
Supported steps
TCP steps
The TCP step model allows establishing connections to TCP servers and exchanging byte-level data. Two main patterns are supported: simple connections with built-in lifecycle management, and connection reuse across multiple steps.
TCP step (simple connection)
The tcp step creates a TCP connection, sends a request, and either maintains or closes the connection based on configuration.
- Ancestor
-
Scenario or Step
- Functionality
-
-
Establishes a TCP connection to the specified remote address.
-
Sends the payload generated by the
requestlambda. -
Receives a response from the server.
-
If
keepConnectionAliveisfalse, closes the connection after receiving the response. -
If
keepConnectionAliveistrue, the connection persists for reuse viatcpWithsteps.
-
When connections are kept open without a pool, use a closeTcp step at the end of the workflow to properly close the connection and avoid system resource saturation.
- Example
-
.start() .netty() .tcp { (1) name = "my-tcp" (2) connect { address("remote-server.acme.com", 9000) (3) noDelay = true (4) keepConnectionAlive = true (5) } request { stepContext, input -> (6) "My TCP request".toByteArray(StandardCharsets.UTF_8) } .verify { (7) assertThat(it.meters.timeToSuccessfulConnect) .isNotNull() .isLessThan(Duration.ofSeconds(1)) }1 Enter the Netty plugin namespace and start a TCP step. 2 Name the step for later reference by tcpWithorcloseTcp.3 Set the remote server address and port. 4 Disable Nagle Algorithm for reduced latency. 5 Keep the connection open across multiple steps for reuse. 6 Define the payload to send; returns a ByteArrayfrom the input or context.7 Verify that the connection was established within 1 second.
- Reference Documentation
-
Refer to the Netty documentation for advanced channel configuration and tuning options.
TCP WITH step (connection reuse)
The tcpWith step reuses an existing TCP connection created by a preceding tcp step, avoiding the overhead of repeated connection establishment.
- Ancestor
-
Step
- Functionality
-
-
Requires a preceding
tcpstep with the name specified intcpWith. -
Reuses the exact connection configuration (address, TLS, proxy settings) from the
tcpstep. -
Sends a new request payload on the existing connection.
-
Does not establish a new connection or renegotiate TLS.
-
- Example
-
.netty().tcp { (1) name = "my-tcp" connect { address("remote-server.acme.com", 9000) noDelay = true keepConnectionAlive = true } request { stepContext, input -> "First request".toByteArray() } } .netty().tcpWith("my-tcp") { (2) name = "reuse-tcp" request { stepContext, input -> "Second request".toByteArray() } (3) iterate(100) (4) }1 Establish a TCP connection named "my-tcp"withkeepConnectionAlive = true.2 Reuse the connection from "my-tcp"for a second request.3 Send a different payload on the same connection. 4 Repeat this request 100 times on the same connection.
- Tips
-
-
tcpWithconnects to the same remote address as its companiontcpstep. -
All connection settings (TLS, proxy, timeouts) are inherited from the
tcpstep. -
Use
iterate()to send multiple requests on the same connection. -
Chain multiple
tcpWithsteps to build workflows across one persistent connection.
-
- Reference Documentation
-
Refer to the Netty documentation for advanced connection patterns.
CLOSE TCP step
The closeTcp step closes a TCP connection that was established with keepConnectionAlive = true, allowing graceful shutdown and resource cleanup.
- Ancestor
-
Step
- Functionality
-
-
Closes the connection associated with the named
tcpstep. -
Respects the
shutdownTimeoutsetting for graceful closure. -
Simply forwards the input to the next step(s) without modification.
-
It is best practice to use closeTcp when reusing a TCP connection across multiple steps without a connection pool.
- Example
-
.netty().tcp { name = "my-tcp" connect { address("remote-server.acme.com", 9000) keepConnectionAlive = true } request { stepContext, input -> "Hello".toByteArray() } } .netty().tcpWith("my-tcp") { request { stepContext, input -> "Goodbye".toByteArray() } } .closeTcp("my-tcp") (1)1 Closes the TCP connection associated with "my-tcp".
- Reference Documentation
-
Refer to the Netty documentation for connection lifecycle management.
TCP with connection pooling
TCP connection pooling maintains a pool of reusable connections, automatically balancing load across minions.
- Pattern
-
.start() .netty() .tcp { (1) name = "pooled-tcp" connect { address("remote-server.acme.com", 9000) noDelay = true pool { size = 50 } (2) } request {stepContext, input -> "Request via pool".toByteArray() } } .netty().tcpWith("pooled-tcp") { (3) request { stepContext, input -> "Another request via pool".toByteArray() } iterate(50) (4) }1 Create a TCP step with connection pooling enabled. 2 Set the pool to maintain 50 concurrent connections, distributed among minions. 3 Reuse a pooled connection for the next request. 4 Send 50 additional requests, each pulling from the pool.
- Tips
-
-
Pooling is ideal for high-concurrency scenarios with many minions.
-
No
closeTcpis needed when using pooling; connections are managed automatically. -
Tune
pool.sizebased on server capacity and target concurrency. -
Enable
pool.checkHealthBeforeUseto detect and refresh stale connections.
-
UDP step
The UDP step sends datagrams to a UDP endpoint and receives responses.
- Ancestor
-
Scenario or Step
- Functionality
-
-
Creates UDP datagrams and sends them to the specified endpoint.
-
Receives responses back from the endpoint (if available).
-
- Example
-
.start() .netty() .udp { (1) iterations = 5 (2) connect { address("remote-server.acme.com", 5000) (3) connectTimeout = Duration.ofSeconds(2) (4) } request { context, input -> (5) "My UDP datagram".toByteArray(StandardCharsets.UTF_8) } }1 Enter the Netty plugin namespace and start a UDP step. 2 Each minion sends 5 datagrams to the endpoint. 3 Specify the remote endpoint address and port. 4 Set the maximum time allowed to establish the UDP channel. 5 Generates the datagram payload from the input or context.
- Tips
-
-
Increase
iterationsto send multiple datagrams per minion. -
UDP is ideal for stateless, request-response scenarios (DNS, metrics, etc.).
-
Response timeouts can be controlled via
readTimeout.
-
- Reference Documentation
-
Refer to the Netty documentation for UDP protocol details.
MQTT steps
MQTT provides publish-subscribe messaging through a broker. Two step models support independent publish and subscribe operations.
MQTT Publish step
The mqttPublish step sends records (messages) to MQTT topics on a broker.
- Ancestor
-
Step
- Functionality
-
-
Connects to an MQTT broker as a publisher client.
-
Sends one or more messages per execution via the
recordslambda. -
Supports configurable Quality of Service (QoS) and message retention.
-
Each
MqttPublishRecordcan specify topic, payload, QoS, and retention independently.
-
- Example
-
.start() .netty() .mqttPublish { (1) connect { host = "mqtt-broker.acme.com" (2) port = 1883 (3) } protocol(MqttVersion.MQTT_3_1_1) (4) clientName("publisher-client") (5) records { context, input -> (6) listOf( MqttPublishRecord( value = "Sensor reading: 23.5°C", (7) topicName = "devices/sensors/temperature", (8) qoS = MqttQoS.EXACTLY_ONCE, (9) retainedMessage = true (10) ) ) } }1 Enter the Netty plugin namespace and start an MQTT publish step. 2 Specify the MQTT broker hostname. 3 Set the MQTT broker port (standard is 1883, 8883 for TLS). 4 Select the MQTT protocol version for compatibility with the broker. 5 Set the client identifier for the publisher on the broker. 6 Factory lambda to generate a list of records to publish from the input/context. 7 Message payload (typically JSON or text). 8 MQTT topic to publish to. 9 QoS level: AT_MOST_ONCE(0),AT_LEAST_ONCE(1), orEXACTLY_ONCE(2).10 If true, broker retains the last message on this topic for new subscribers.
- Tips
-
-
Generate multiple
MqttPublishRecordobjects in therecordslambda to publish several topics in one execution. -
Use
EXACTLY_ONCEQoS for critical messages;AT_LEAST_ONCEfor typical scenarios;AT_MOST_ONCEfor best-effort. -
Enable
retainedMessageto preserve state across subscribers (e.g., for device status). -
Reuse minion input/context values in topic names or payloads for dynamic scenarios.
-
- Reference Documentation
-
Refer to the MQTT specification and your broker’s documentation for topic design and QoS implications.
MQTT Subscribe step
The mqttSubscribe step consumes messages from MQTT topics on a broker.
- Ancestor
-
Scenario or Step
- Functionality
-
-
Connects to an MQTT broker as a subscriber client.
-
Subscribes to one or more topics using filters (supporting
+and#wildcards). -
Receives messages from the broker in real-time.
-
Output can be deserialized to typed objects or strings via
.deserialize(). -
Concurrency controls how many subscriber tasks process messages in parallel.
-
- Example
-
.start() .netty() .mqttSubscribe { (1) connect { host = "mqtt-broker.acme.com" (2) port = 1883 (3) } protocol(MqttVersion.MQTT_3_1_1) (4) clientName("subscriber-client") (5) concurrency(2) (6) topicFilter("devices/sensors/+/temperature") (7) qoS(MqttQoS.AT_LEAST_ONCE) (8) } .deserialize(MessageStringDeserializer::class) (9) .onEach { receivedMessage -> (10) println("Received: ${receivedMessage.value}") }1 Enter the Netty plugin namespace and start an MQTT subscribe step. 2 Specify the MQTT broker hostname. 3 Set the MQTT broker port. 4 Select the MQTT protocol version. 5 Unique client identifier for the subscriber on the broker. 6 Number of concurrent subscriber tasks (threads) processing messages. 7 Topic filter with wildcard: +matches a single level,#matches multiple levels. Here matchesdevices/sensors/*/temperature.8 QoS for subscriptions: guarantees matching the server’s capability. 9 Deserialize raw MQTT messages to strings (or JSON, byte arrays, etc.). 10 Processes each deserialized message in the workflow.
- Tips
-
-
Combine multiple
mqttSubscribesteps with different topic filters in a single scenario for multi-topic monitoring. -
Use topic wildcards (
+,#) to subscribe to many topics with a single filter. -
Increase
concurrencyfor high-throughput scenarios, but monitor latency impact. -
Match QoS between publisher and subscriber for optimal performance.
-
- Reference Documentation
-
Refer to the MQTT specification for topic filter syntax and QoS semantics.
Configuration
DSL parameters
Available parameters are described in the table below, organized by protocol.
Common parameters
The following parameters are applicable to all protocol types.
| Parameter | Description |
|---|---|
|
Configures the connection to the remote endpoint. Example (TCP)
Example (UDP)
Example (MQTT)
|
|
Maximum duration to establish a connection before timing out. Example
|
|
Maximum duration to wait for a response after sending a request before timing out. Example
|
|
Maximum duration to gracefully close a connection. Example
|
|
Size of the buffer used to prepare data for transmission. When omitted, the operating system default is used. Example
|
|
Size of the buffer used to receive incoming data. When omitted, the operating system default is used. Example
|
|
Adds a single Netty channel option for advanced tuning. Example
|
|
Adds multiple Netty channel options for advanced tuning. Example
|
TCP-specific parameters
The following parameters apply only to TCP connections.
| Parameter | Description |
|---|---|
|
The remote host and port to connect to. Example
|
|
Disables the Nagle Algorithm for reduced latency at the cost of potential throughput reduction. When Example
|
|
Keeps the connection open across multiple steps instead of closing after each request. When Example
|
|
Enables SSL/TLS encryption for the TCP connection. Example
|
|
Disables SSL/TLS certificate verification. Example
|
|
List of allowed SSL/TLS protocol versions. Example
|
|
List of supported cipher suites for SSL/TLS negotiation. Defaults to Netty/JDK defaults when not specified. Example
|
|
Configuration for routing connections through a proxy server (SOCKS4, SOCKS5). Example
|
|
Type of proxy protocol to use. Example
|
|
The proxy server host and port. Example
|
|
Optional credentials for proxy authentication. Example
|
|
Configures connection pooling to reuse existing connections across multiple requests. Reduces overhead of repeated TCP handshakes. Example
|
|
Maximum number of concurrent connections maintained in the pool. Example
|
|
Attempts a health check on a pooled connection before reusing it. Helps detect stale connections. Example
|
|
Factory lambda to create the TCP payload from the step context and input. Returns a Example
|
UDP-specific parameters
The following parameters apply only to UDP connections.
| Parameter | Description |
|---|---|
|
The remote host and port to send datagrams to. Example
|
|
Factory lambda to create the UDP datagram payload from the step context and input. Example
|
|
Number of UDP datagrams to send from each minion. Example
|
MQTT-specific parameters
The following parameters apply only to MQTT connections.
| Parameter | Description |
|---|---|
|
The hostname or IP address of the MQTT broker. Example
|
|
The port number of the MQTT broker. Standard MQTT port is 1883; MQTT over TLS uses 8883. Example
|
|
Controls whether the MQTT client should try to reconnect when the connection is closed. Example
|
|
MQTT protocol version to use. Specifies compatibility level with the broker. Example
|
|
Unique identifier for the MQTT client on the broker. Used for connection authentication and session tracking. Example
|
|
Configures username and password authentication for the MQTT client. Example
|
|
Username used to authenticate against the MQTT broker. Example
|
|
Password used to authenticate against the MQTT broker. Example
|
|
Factory lambda to create a list of Example
|
|
Payload value of the Example
|
|
The MQTT topic to publish messages to. Can contain MQTT topic wildcards at the application level. Example
|
|
Quality of Service level to apply to each published MQTT record: Example
|
|
MQTT properties to attach to the published record. Example
|
|
If Example
|
|
MQTT topic filter pattern for subscription. Supports wildcards: Example
|
|
Quality of Service level for the MQTT subscription: Example
|
|
Number of concurrent subscriber threads/tasks for this step. Example
|
Shared defaults for Netty steps
You can define defaults once in the scenario section or just after, and let all following Netty steps inherit them.
scenario {
netty().defaults { (1)
tcpConnection {
address("remote-server.acme.com", 9000)
}
httpConnection {
url("https://api.example.com")
}
udpConnection {
address("remote-server.acme.com", 5000)
}
mqttConnection {
host("mqtt.acme.com")
port(1883)
}
monitoring {
events = true
meters = true
}
}
}
| 1 | Defaults are applied to subsequent Netty steps in the same scenario. Individual steps can still override values. |