Server/Client I/O

Often you will want to call one model from another like a functions. This would required sending the input variable(s) to the model being called and then sending the output variables(s) back to the calling model. We refer to this as a Remote Procedure Call (RPC). The model being called can be considered a server providing its calculations as a service to the client (the calling model). The cis_interface provides options for treating models as server and clients.

One Server, One Client

In the example below, the “server” model computes the nth number in the Fibonacci sequence and the “client” model calls the server to get a certain portion of the Fibonacci sequences and then writes it to a log. The server will continue processing requests until all clients connected to it have disconnected.

Model Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import numpy as np
from cis_interface.interface.CisInterface import CisRpcServer


def get_fibonacci(n):
    r"""Compute the nth number of the Fibonacci sequence.

    Args:
        n (int): Index of the Fibonacci number in the Fibonacci sequence that
            should be returned.

    Returns:
        int: The nth Fibonacci number.

    """
    pprev = 0
    prev = 1
    result = 1
    fib_no = 1
    while fib_no < n:
        result = prev + pprev
        pprev = prev
        prev = result
        fib_no = fib_no + 1
    return result


def main():
    r"""Function to execute server communication and computation in a loop."""

    print('Hello from Python server!')

    # Create server-side rpc conneciton using model name
    rpc = CisRpcServer("server", "%d", "%d")

    # Continue receiving requests until the connection is closed when all
    # clients have disconnected.
    while True:
        print('server: receiving...')
        retval, rpc_in = rpc.recv()
        if not retval:
            print('server: end of input')
            break

        # Compute fibonacci number
        n = rpc_in[0]
        print('server: Received request for Fibonacci number %d' % n)
        result = get_fibonacci(n)
        print('server: Sending response for Fibonacci number %d: %d' % (n, result))

        # Send response back
        flag = rpc.send(np.int32(result))
        if not flag:
            raise RuntimeError('server: ERROR sending')

    print('Goodbye from Python server')

    
if __name__ == '__main__':
    main()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import sys
import numpy as np
from cis_interface.interface.CisInterface import (
    CisRpcClient, CisOutput)


def main(iterations):
    r"""Function to execute client communication with server that computes
    numbers in the Fibonacci sequence.

    Args:
        iterations (int): The number of Fibonacci numbers to log.

    """

    print('Hello from Python client: iterations = %d ' % iterations)

    # Set up connections matching yaml
    # RPC client-side connection will be $(server_name)_$(client_name)
    rpc = CisRpcClient("server_client", "%d", "%d")
    log = CisOutput("output_log", 'fib(%-2d) = %-2d\n')

    # Iterate over Fibonacci sequence
    for i in range(1, iterations + 1):
        
        # Call the server and receive response
        print('client(Python): Calling fib(%d)' % i)
        ret, result = rpc.call(np.int32(i))
        if not ret:
            raise RuntimeError('client(Python): RPC CALL ERROR')
        fib = result[0]
        print('client(Python): Response fib(%d) = %d' % (i, fib))

        # Log result by sending it to the log connection
        ret = log.send(np.int32(i), fib)
        if not ret:
            raise RuntimeError('client(Python): SEND ERROR')

    print('Goodbye from Python client')

    
if __name__ == '__main__':
    # Take number of iterations from the first argument
    main(int(sys.argv[1]))

(Example in other languages)

The interface server-side API call (CisRpcServer for Python), requires 3 input variables: the name of the server channel (this will be the name of the server model), a format string for input to the server model, and a format string for output from the server model. The client-side API call (CisRpcClient for Python), also requires 3 input variables: the name of the client channel (this is the name of the server model joined with the name of the client model by an underscore, <server>_<client>, a format string for input to the server model, and a format string for output form the server model. The last two arguments (the format strings) to both the server and client API calls should be the same.

In the server model YAML, the key/value pair is_server: True needs to be added to the model entry to indicate that the model will be called as a server and requires a set of RPC channels. In the client model YAML, the key/value pair client_of: <server_model_name> is required to indicate that the model will act as a client of the <server_model_name> model.

Model YAML:

1
2
3
4
5
6
7
---

model:
  name: server
  language: python
  args: ./src/server.py
  is_server: True  # Creates a RPC server queue called "server"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---

model:
  name: client
  language: python
  args:
    - ./src/client.py
    - 3  # Pass the number of iterations that should be performed
  client_of: server  # Creates an RPC client queue "server_client"
  outputs: output_log

connections:
  input: output_log
  output: client_output.txt
  in_temp: True

(Example in other languages)

One Server, Two Clients

There is no limit on the number of clients that can connect to a single server. In the example below, the server is the same as above. The client code is also essentially the same except that it has been modified to take a client_index variable that provides information to differentiates between two clients using the same source code.

Model Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import numpy as np
from cis_interface.interface.CisInterface import CisRpcServer


def get_fibonacci(n):
    r"""Compute the nth number of the Fibonacci sequence.

    Args:
        n (int): Index of the Fibonacci number in the Fibonacci sequence that
            should be returned.

    Returns:
        int: The nth Fibonacci number.

    """
    pprev = 0
    prev = 1
    result = 1
    fib_no = 1
    while fib_no < n:
        result = prev + pprev
        pprev = prev
        prev = result
        fib_no = fib_no + 1
    return result


def main():
    r"""Function to execute server communication and computation in a loop."""

    print('Hello from Python server!')

    # Create server-side rpc conneciton using model name
    rpc = CisRpcServer("server", "%d", "%d")

    # Continue receiving requests until the connection is closed when all
    # clients have disconnected.
    while True:
        print('server: receiving...')
        retval, rpc_in = rpc.recv()
        if not retval:
            print('server: end of input')
            break

        # Compute fibonacci number
        n = rpc_in[0]
        print('server: Received request for Fibonacci number %d' % n)
        result = get_fibonacci(n)
        print('server: Sending response for Fibonacci number %d: %d' % (n, result))

        # Send response back
        flag = rpc.send(np.int32(result))
        if not flag:
            raise RuntimeError('server: ERROR sending')

    print('Goodbye from Python server')

    
if __name__ == '__main__':
    main()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import sys
import numpy as np
from cis_interface.interface.CisInterface import (
    CisRpcClient, CisOutput)


def main(iterations, client_index):
    r"""Function to execute client communication with server that computes
    numbers in the Fibonacci sequence.

    Args:
        iterations (int): The number of Fibonacci numbers to log.
        client_index (int): Index of the client in total list of clients.

    """

    print('Hello from Python client%d: iterations = %d ' % (client_index,
                                                            iterations))

    # Set up connections matching yaml
    # RPC client-side connection will be $(server_name)_$(client_name)
    rpc = CisRpcClient("server_client%d" % client_index, "%d", "%d")
    log = CisOutput("output_log%d" % client_index, 'fib(%-2d) = %-2d\n')

    # Iterate over Fibonacci sequence
    for i in range(1, iterations + 1):
        
        # Call the server and receive response
        print('client%d(Python): Calling fib(%d)' % (client_index, i))
        ret, result = rpc.call(np.int32(i))
        if not ret:
            raise RuntimeError('client%d(Python): RPC CALL ERROR' % client_index)
        fib = result[0]
        print('client%d(Python): Response fib(%d) = %d' % (client_index, i, fib))

        # Log result by sending it to the log connection
        ret = log.send(np.int32(i), fib)
        if not ret:
            raise RuntimeError('client%d(Python): SEND ERROR' % client_index)

    print('Goodbye from Python client%d' % client_index)

    
if __name__ == '__main__':
    # Take number of iterations from the first argument and the
    # client index from the second
    main(int(sys.argv[1]), int(sys.argv[2]))

(Example in other languages)

The server YAML is the same as above. The client YAML now has entries for two models which are both clients of the server model and call the same source code.

Model YAML:

1
2
3
4
5
6
7
---

model:
  name: server
  language: python
  args: ./src/server.py
  is_server: True  # Creates a RPC server queue called "server"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
---

models:
  - name: client1
    language: python
    args:
      - ./src/client.py
      - 3  # Pass the number of iterations that should be performed
      - 1  # Pass index of the client
    client_of: server  # Creates an RPC client queue "server_client"
    outputs: output_log1
  - name: client2
    language: python
    args:
      - ./src/client.py
      - 5  # Pass the number of iterations that should be performed
      - 2  # Pass index of the client
    client_of: server  # Creates an RPC client queue "server_client"
    outputs: output_log2

connections:
  - input: output_log1
    output: client_output1.txt
    in_temp: True
  - input: output_log2
    output: client_output2.txt
    in_temp: True

(Example in other languages)

During runtime, request messages from both clients will be routed to the server model which will process the requests in the order they are received.

Todo

Section on having multiple servers.