Server/Client I/O — cis_interface 0.3.0 documentation

# 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
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    import sys
    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.

        Returns:
            int: Exit code. Negative if an error occurred.

        """

        exit_code = 0
        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(result)
            if not flag:
                print('server: ERROR sending')
                exit_code = -1
                break

        print('Goodbye from Python server')
        return exit_code

    if __name__ == '__main__':
        exit_code = main()
        sys.exit(exit_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
    import sys
    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.

        Returns:
            int: Exit code. Negative if an error occurred.

        """

        exit_code = 0
        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(i)
            if not ret:
                print('client(Python): RPC CALL ERROR')
                exit_code = -1
                break
            fib = result[0]
            print('client(Python): Response fib(%d) = %d' % (i, fib))

            # Log result by sending it to the log connection
            ret = log.send(i, fib)
            if not ret:
                print('client(Python): SEND ERROR')
                exit_code = -1
                break

        print('Goodbye from Python client')
        return exit_code

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

(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
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    import sys
    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.

        Returns:
            int: Exit code. Negative if an error occurred.

        """

        exit_code = 0
        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(result)
            if not flag:
                print('server: ERROR sending')
                exit_code = -1
                break

        print('Goodbye from Python server')
        return exit_code

    if __name__ == '__main__':
        exit_code = main()
        sys.exit(exit_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
    import sys
    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.

        Returns:
            int: Exit code. Negative if an error occurred.

        """

        exit_code = 0
        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(i)
            if not ret:
                print('client%d(Python): RPC CALL ERROR' % client_index)
                exit_code = -1
                break
            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(i, fib)
            if not ret:
                print('client%d(Python): SEND ERROR' % client_index)
                exit_code = -1
                break

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

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

(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.