Fortran 2018 interface bindings to the lightweight messaging library NNG

Greetings,

I’d like to announce the release of fortran-nng, a collection of Fortran 2018 interface bindings to the open-source messaging library NNG:

NNG, like its predecessors nanomsg (and to some extent ZeroMQ), is a lightweight, broker-less library, offering a simple API to solve common recurring messaging problems, such as publish/subscribe, RPC-style request/reply, or service discovery. The API frees the programmer from worrying about details like connection management, retries, and other common considerations, so that they can focus on the application instead of the plumbing.

The bindings are unopinionated and stay close to the C API. NNG supports a range of communication patterns, for instance:

  • Pipeline: one-way pipe for producer/consumer problems, including load-balancing.
  • Request/Reply: reliable synchronous communication for RPC and load balancing (I ask, you answer).
  • Pair: one-to-one peer connection (two-way radio).
  • Pub/Sub: allows a single broadcaster to publish messages to many subscribers (topics & broadcast).
  • Survey: for timed surveys (everybody votes) and service discovery.
  • Bus: for routing and fully interconnected mesh networks.

Basic demonstrations ported from C to Fortran are provided in the repository. In the following example, a server routine uses the surveyor pattern to query connected clients for a time stamp. (Error handling has been omitted.)

subroutine server(url)
    character(*), intent(in) :: url

    character(80), target :: buffer
    integer               :: rc
    integer(c_size_t)     :: sz
    type(nng_listener)    :: listener
    type(nng_socket)      :: socket

    rc = nng_surveyor0_open(socket)                    ! Open socket.
    rc = nng_listen(socket, f_c_str(url), listener, 0) ! Listen for connections.

    do
        buffer = 'DATE'
        print '("SERVER: SENDING ", a, " SURVEY REQUEST")', trim(buffer)

        ! Send request.
        sz = len_trim(buffer, c_size_t)
        rc = nng_send(socket, c_loc(buffer), sz, 0)

        do
            ! Collect responses.
            buffer = ' '
            sz = len(buffer, c_size_t)
            rc = nng_recv(socket, c_loc(buffer), sz, 0)
            if (rc == NNG_ETIMEDOUT) exit
            print '("SERVER: RECEIVED ", a, " SURVEY RESPONSE")', trim(buffer)
        end do

        print '("SERVER: SURVEY COMPLETE")'
    end do

    rc = nng_socket_close(socket)
end subroutine server

The clients connect to the server and wait for the survey to start, then respond with a character string of the current date and time in ISO 8601 format:

subroutine client(url, name)
    character(*), intent(in) :: url
    character(*), intent(in) :: name

    character(80), target :: buffer
    integer               :: rc
    integer(c_size_t)     :: sz
    type(nng_dialer)      :: dialer
    type(nng_socket)      :: socket

    rc = nng_respondent0_open(socket)                              ! Open socket.
    rc = nng_dial(socket, f_c_str(url), dialer, NNG_FLAG_NONBLOCK) ! Connect.

    do
        ! Wait for survey request.
        buffer = ' '
        sz = len(buffer, c_size_t)
        rc = nng_recv(socket, c_loc(buffer), sz, 0)

        print '("CLIENT (", a, "): RECEIVED ", a, " SURVEY REQUEST")', trim(name), trim(buffer)
        print '("CLIENT (", a, "): SENDING DATE SURVEY RESPONSE")', trim(name)

        ! Send date and time in ISO 8601 as survey response.
        buffer = iso8601()
        sz = len_trim(buffer, c_size_t)
        rc = nng_send(socket, c_loc(buffer), sz, 0)
    end do

    rc = nng_socket_close(socket)
end subroutine client

Compiling the example program:

$ flang -I/opt/include/libfortran-nng -o survey survey.f90 /opt/lib/libfortran-nng.a -lnng

Starting the server and three clients:

./survey server ipc:///tmp/survey.ipc & server=$!
./survey client ipc:///tmp/survey.ipc client0 & client0=$!
./survey client ipc:///tmp/survey.ipc client1 & client1=$!
./survey client ipc:///tmp/survey.ipc client2 & client2=$!
sleep 10
kill $server $client0 $client1 $client2

Output:

SERVER: SENDING DATE SURVEY REQUEST
CLIENT (client0): RECEIVED DATE SURVEY REQUEST
CLIENT (client2): RECEIVED DATE SURVEY REQUEST
CLIENT (client1): RECEIVED DATE SURVEY REQUEST
CLIENT (client1): SENDING DATE SURVEY RESPONSE
CLIENT (client2): SENDING DATE SURVEY RESPONSE
CLIENT (client0): SENDING DATE SURVEY RESPONSE
SERVER: RECEIVED 2026-01-03T17:51:33.590+01:00 SURVEY RESPONSE
SERVER: RECEIVED 2026-01-03T17:51:33.590+01:00 SURVEY RESPONSE
SERVER: RECEIVED 2026-01-03T17:51:33.590+01:00 SURVEY RESPONSE
SERVER: SURVEY COMPLETE
7 Likes