Specification: Ballerina TCP Library

Owners: @shafreenAnfar @bhashinee
Reviewers: @shafreenAnfar
Created: 2021/12/20
Updated: 2022/02/18
Edition: Swan Lake

Introduction

This is the specification for the TCP standard library of Ballerina language, which provides TCP client-server functionalities.

The TCP library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag.

If you have any feedback or suggestions about the library, start a discussion via a GitHub issue or in the Discord server. Based on the outcome of the discussion, the specification and implementation can be updated. Community feedback is always welcome. Any accepted proposal, which affects the specification is stored under /docs/proposals. Proposals under discussion can be found with the label type/proposal in GitHub.

The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug.

Contents

  1. Overview
  2. Listener
  3. Service Types
  4. Client
  5. Securing the TCP Connections
  6. Samples

1. Overview

TCP is a protocol that enables applications to exchange messages over a network. It is designed to ensure the successful delivery of data over the network. This specification elaborates on how Ballerina language provides a tested TCP client and server implementation that is compliant with the RFC 793.

2. Listener

The tcp:Listener is used to listen to the incoming socket request. It can be constructed with a port number and optionally providing other configurations. When initiating the listener it opens up the port and attaches the tcp:Service.

2.1. Configurations

When initializing the listener, following configurations can be provided,

# Provides a set of configurations for tcp listener.
#
# + localHost - The hostname
# + secureSocket - The SSL configurations for the listener
public type ListenerConfiguration record {|
   string localHost?;
   ListenerSecureSocket secureSocket?; 
|};

ListenerSecureSocket record contains configurations related to enabling SSL/TLS on the listener side. More details and examples of how to configure them can be found in a following section on Securing the TCP Connections.

# Secure Socket configuration for TCP Listener.
#
# + key - Configurations associated with `crypto:KeyStore` or combination of certificate and (PKCS8) private key of the server
# + protocol - SSL/TLS protocol related options
# + ciphers - List of ciphers to be used
#             eg: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
# + handshakeTimeout - SSL handshake time out
# + sessionTimeout - SSL session time out
public type ListenerSecureSocket record {|
    crypto:KeyStore|CertKey key;
    record {|
        Protocol name;
        string[] versions = [];
    |} protocol?;
    string[] ciphers = [];
    decimal handshakeTimeout?;
    decimal sessionTimeout?;
|};

2.2. Initialization

The TCP listener can be initialized by providing the port and optionally a ListenerConfiguration.

# Initializes the TCP listener based on the provided configurations.
# ```ballerina
#  listener Listener|error? server1 = new (8080);
# ```
# + localPort - The port number of the remote service
# + config - Configurations related to the `tcp:Listener`
public isolated function init(int localPort, *ListenerConfiguration config) returns Error? {}

3. Service Types

3.1. Service

This service has a single onConnect remote method which gets invoked when a new client is connected. The new client is represented using the tcp:Caller. The onConnect(tcp:Caller) method may return tcp:ConnectionService|tcp:Error.

3.2. Connection Service

Once the TCP connection is established, it returns a tcp:ConnectionService. This service has a fixed set of remote methods that do not have any configs. Receiving messages will get dispatched to the relevant remote method. Each remote method is explained below.

import ballerina/tcp;

service on new tcp:Listener(3000) {
    remote function onConnect(tcp:Caller caller) returns tcp:ConnectionService {
        return new EchoService();
    }
}

service class EchoService {

    *tcp:ConnectionService;
    
    remote function onBytes(readonly & byte[] data) returns byte[]|tcp:Error? {
        // echo back the data to the client
        return data;
    }
}

3.2.1. Remote methods associated with the Connection Service

onBytes

This remote method is invoked once the data is received from the client.

remote function onBytes(tcp:Caller caller, readonly & byte[] data) returns tcp:Error? {
    io:println("Received: ", string:fromBytes(data));
}
onError

This remote method is invoked in an error situation.

remote function onError(tcp:Error err) {
    io:println("An error occurred" + err.message());
}
onClose

This remote method is invoked when the connection gets closed.

remote function onClose() {
    io:println("Client left");
}

4. Client

The tcp:Client is used to connect to a socket server and interact with it. It can send the data to the server and retrieve the data from the server.

4.1. Configurations

When initializing the client, following configurations can be provided,

# Configurations for the connection-oriented TCP client.
# 
# + localHost - Local binding of the interface
# + timeout - The socket reading timeout value to be used in seconds. If this is not set, the default value
#             of 300 seconds(5 minutes) will be used
# + writeTimeout - The socket write timeout value to be used in seconds. If this is not set, the default value
#             of 300 seconds(5 minutes) will be used
# + secureSocket - The `secureSocket` configuration
public type ClientConfiguration record {|
    string localHost?;
    decimal timeout = 300;
    decimal writeTimeout = 300;
    ClientSecureSocket secureSocket?;
|};

4.2. Initialization

A client can be initialized by providing the remoteHost and the remotePort and optionally the ClientConfiguration.

# Initializes the TCP client based on the provided configurations.
# ```ballerina
# tcp:Client|tcp:Error? socketClient = new("www.remote.com", 80,
#                              localHost = "localHost");
# ```
# + remoteHost - The hostname or the IP address of the remote host
# + remotePort - The port number of the remote host
# + config - Connection-oriented client-related configurations
public isolated function init(string remoteHost, int remotePort, *ClientConfiguration config) returns Error? {}

4.3. Send and receive data

writeBytes

writeBytes API can be used to send data to the remote host.

# Sends the given data to the connected remote host.
# ```ballerina
# tcp:Error? result = socketClient->writeBytes("msg".toBytes());
# ```
#
# + data - The data that need to be sent to the connected remote host
# + return - `()` or else a `tcp:Error` if the given data cannot be sent
remote function writeBytes(byte[] data) returns Error? = @java:Method {}

readBytes

readBytes API can be used to read data receiving from the remote host.

# Reads data only from the connected remote host. 
# ```ballerina
# (readonly & byte[])|tcp:Error result = socketClient->readBytes();
# ```
#
# + return - The `readonly & byte[]` or else a `tcp:Error` if the data
#            cannot be read from the remote host
remote function readBytes() returns (readonly & byte[])|Error = @java:Method {}

close

close API can be used to close the connection established with the remote host.

# Frees up the occupied socket.
# ```ballerina
# tcp:Error? closeResult = socketClient->close();
# ```
#
# + return - A `tcp:Error` if it cannot close the connection or else `()`
isolated remote function close() returns Error? = @java:Method {}

5. Securing the TCP Connections

Ballerina provides inbuilt support for securing TCP connections with SSL/TLS protocol.

5.1 Using the TLS protocol

This expects a secure socket to be set in the connection configuration as shown below.

5.1.1 Configuring TLS in server side

tcp:ListenerSecureSocket listenerSecureSocket = {
    key: {
        certFile: "../resource/path/to/public.crt",
        keyFile: "../resource/path/to/private.key"
    }
};

service on new tcp:Listener(9002, secureSocket = listenerSecureSocket) {
    isolated remote function onConnect(tcp:Caller caller) returns tcp:ConnectionService {
        return new EchoService();
    }
}

5.1.2 Configuring TLS in client side

tcp:Client socketClient = check new ("localhost", 9002, secureSocket = {
    cert: "../resource/path/to/public.crt",
});

6. Samples

Listener

import ballerina/io;
import ballerina/log;
import ballerina/tcp;

service on new tcp:Listener(3000) {
    remote function onConnect(tcp:Caller caller) returns tcp:ConnectionService {
        io:println("Client connected to echo server: ", caller.remotePort);
        return new EchoService();
    }
}

service class EchoService {

    *tcp:ConnectionService;
    
    remote function onBytes(tcp:Caller caller, readonly & byte[] data) returns tcp:Error? {
        io:println("Echo: ", string:fromBytes(data));
        return caller->writeBytes(data);
    }

    remote function onError(tcp:Error err) {
        log:printError("An error occurred", 'error = err);
    }

    remote function onClose() {
        io:println("Client left");
    }
}

Client

import ballerina/io;
import ballerina/tcp;

public function main() returns error? {
    tcp:Client socketClient = check new ("localhost", 3000);

    string msg = "Hello Ballerina Echo from client";
    byte[] msgByteArray = msg.toBytes();
    check socketClient->writeBytes(msgByteArray);

    readonly & byte[] receivedData = check socketClient->readBytes();
    io:println("Received: ", string:fromBytes(receivedData));

    return socketClient->close();
}