Skip to main content
Engineering LibreTexts

3.3: TCP Server

  • Page ID
    43801
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Now, let us build a simple TCP server. A TCP Server is an application that awaits TCP connections from TCP clients. Once a connection is established, both the server and the client can send a receive data in any order. A big difference between the server and the client is that the server uses at least two sockets. One socket is used for handling client connections, while the second serves for exchanging data with a particular client.

    TCP Socket Server Life-cycle

    The life-cycle of a TCP server has 5 steps:

    1. Create a first TCP socket labelled connectionSocket.

    2. Wait for a connection by making connectionSocket listen on a port.

    3. Accept a client request for connection. As a result, connectionSocket will build a second socket labelled interactionSocket.

    4. Exchange data with the client through interactionSocket. Meanwhile, connectionSocket can continue to wait for a new connection, and possi- bly create new sockets to exchange data with other clients.

    5. Close interactionSocket.

    6. Close connectionSocket when we decide to kill the server and stop accepting client connections.

    Socket server with multiple clients.
    Figure \(\PageIndex{1}\): Socket Server Concurrently Servers Multiple Clients.

    Concurrency of this life-cycle is made explicit on Figure \(\PageIndex{1}\). The server listens for incoming client connection requests through connectionSocket, while exchanging data with possibly multiple clients through multiple interactionSockets (one per client). In the following, we first illustrate the socket serving machinery. Then, we describe a complete server class and explain the server life-cycle and related concurrency issues.

    Serving Basic Example

    We illustrate the serving basics through a simple example of an echo TCP server that accepts a single client request. It sends back to clients whatever data it received and quits. The code is provided by Script \(\PageIndex{1}\).

    Script \(\PageIndex{1}\) (Pharo): Basic Echo Server

    | connectionSocket interactionSocket receivedData |
    "Prepare socket for handling client connection requests"
    connectionSocket := Socket newTCP.
    connectionSocket listenOn: 9999 backlogSize: 10.
    
    "Build a new socket for interaction with a client which connection request is accepted"
    interactionSocket := connectionSocket waitForAcceptFor: 60.
    
    "Get rid of the connection socket since it is useless for the rest of this example"
    connectionSocket closeAndDestroy.
    
    "Get and display data from the client"
    receivedData := interactionSocket receiveData.
    receivedData crLog.
    
    "Send echo back to client and finish interaction"
    interactionSocket sendData: 'ECHO: ', receivedData.
    interactionSocket closeAndDestroy. 
    

    First, we create the socket that we will use for handling incoming connections. We configure it to listen on port 9999. The backlogSize is set to 10, meaning that we ask the Operating System to allocate a buffer for 10 connection requests. This backlog will not be actually used in our example. But, a more realistic server will have to handle multiple connections and then store pending connection requests into the backlog.

    Once the connection socket (referenced by variable connectionSocket) is set up, it starts listening for client connections. The waitForAcceptFor: 60 message makes the socket wait connection requests for 60 seconds. If no client attempts to connect during these 60 seconds, the message answers nil. Otherwise, we get a new socket interactionSocket connected the client’s socket. At this point, we do not need the connection socket anymore, so we can close it (connectionSocket closeAndDestroy message).

    Since the interaction socket is already connected to the client, we can use it to exchange data. Messages receiveData and sendData: presented above (see Section 4.2) can be used to achieve this goal. In our example, we wait for data from the client and next display it on the Transcript. Lastly, we send it back to the client prefixed with the 'ECHO: ' string, finishing the interaction with the client by closing the interaction socket.

    There are different options to test the server of Script \(\PageIndex{1}\). The first simple one is to use the nc (netcat) utility discussed in Section 4.5. First run the server script in a workspace. Then, in a terminal, evaluate the following command line:

    echo "Hello Pharo" | nc localhost 9999
    

    As a result, on the Transcript of the Pharo image, the following line should be displayed:

    Hello Pharo
    

    On the client side, that is the terminal, you should see:

    ECHO: Hello Pharo
    

    A pure Pharo alternative relies on using two different images: one that runs the server code and the other for client code. Indeed, since our exam- ples run within the user interaction process, the Pharo UI will be frozen at some points, such as during the waitForAcceptFor:. Script \(\PageIndex{2}\) provides the code to run on the client image. Note that you have to run the server code first. Otherwise, the client will fail. Note also that after the interaction, both the client and the server terminate. So, if you want to run the example a second time you need to run again both sides.

    Script \(\PageIndex{2}\) (Pharo): Echo Client

    | clientSocket serverAddress echoString |
    serverAddress := NetNameResolver addressForName:'127.0.0.1'.
    clientSocket := Socket newTCP.
    [ clientSocket
            connectTo: serverAddress port: 9999;
            waitForConnectionFor: 10.
        clientSocket sendData: 'Hello Pharo!'.
        echoString := clientSocket receiveDataTimeout: 5.
        echoString crLog.
    ] ensure: [ clientSocket closeAndDestroy ].
    

    Echo Server Class

    We define here the EchoServer class that deals with concurrency issues. It handles concurrent client queries and it does not freeze the UI. Figure \(\PageIndex{2}\) shows an example of how the EchoServer handles two clients.

    Echo server with two clients.
    Figure \(\PageIndex{2}\): Echo Server Concurrently Serving Two Clients

    As we can see in the definition labelled Class \(\PageIndex{1}\), the EchoServer declares three instance variables. The first one (connectionSocket) refers to the socket used for listening to client connections. The two last instance variables (isRunning holding a boolean and isRunningLock holding a Mutex) are used to manage the server process life-cycle while dealing with synchronization issues.

    Class \(\PageIndex{1}\) (Pharo): EchoServer Class Definition

    Object subclass: #EchoServer
        instanceVariableNames: 'connectionSocket isRunning isRunningLock'
        classVariableNames: ''
        poolDictionaries: ''
        category: 'SimpleSocketServer'
    

    The isRunning instance variable is a flag that is set to true while the server is running. As we will see below, it can be accessed by different processes. Therefore, we need to ensure that the value can be read in presence of multiple write accesses. This is achieved using a lock (isRunningLock instance variable) that guarantees that isRunning is accessed by only by a single process each time.

    Method \(\PageIndex{1}\) (Pharo): The EchoServer»isRunning Read Accessor

    EchoServer»isRunning
        ^ isRunningLock critical: [ isRunning ]
    

    Method \(\PageIndex{2}\) (Pharo): The EchoServer»isRunning: Write Accessor

    EchoServer»isRunning: aBoolean
        isRunningLock critical: [ isRunning := aBoolean ]
    

    Accesses to the flag are only possible through accessor methods (Method \(\PageIndex{1}\) and Method \(\PageIndex{2}\)). Thus, isRunning is read and written inside blocks that are arguments of message critical: sent to isRunningLock. This lock is an instance of Mutex (see Method \(\PageIndex{3}\)). When receiving a critical: message, a mutex evaluates the argument (a block). During this evaluation, other processes that send a critical: message to the same mutex are suspended. Once the first block is done, the mutex resumes a suspended process (the one that was first suspended). This cycle is repeated until there are no more suspended processes. Thus, the mutex ensures that the isRunning flag is read and wrote sequentially.

    Method \(\PageIndex{3}\) (Pharo): The EchoServer»initialize Method

    EchoServer»initialize
        super initialize.
        isRunningLock := Mutex new.
        self isRunning: false
    

    To manage the life-cycle of our server, we introduced two methods EchoServer»start and EchoServer»stop. We begin with the simplest one EchoServer»stop whose definition is provided as Method \(\PageIndex{4}\). It simply sets the isRunning flag to false. This will have the consequence of stopping the serving loop in method EchoServer»serve (see Method \(\PageIndex{5}\)).

    Method \(\PageIndex{4}\) (Pharo): The EchoServer»stop Method

    EchoServer»stop
        self isRunning: false
    

    Method \(\PageIndex{5}\) (Pharo): The EchoServer»serve Method

    EchoServer»serve
        [ [ self isRunning ]
            whileTrue: [ self interactOnConnection ] ]
            ensure: [ connectionSocket closeAndDestroy ]
    

    The activity of the serving process is implemented in the serve method (see Method \(\PageIndex{5}\)). It interacts with clients on connections while the isRunning flag is true. After a stop, the serving process terminates by destroying the connection socket. The ensure: message guarantees that this destruction is performed even if the serving process is terminated abnormally. Such termination may occur because of an exception (e.g., network disconnection) or a user action (e.g., through the process browser).

    Method \(\PageIndex{6}\) (Pharo): The EchoServer»start Method

    EchoServer»start
        isRunningLock critical: [
            self isRunning ifTrue: [ ^ self ].
            self isRunning: true].
        connectionSocket := Socket newTCP.
        connectionSocket listenOn: 9999 backlogSize: 10.
        [ self serve ] fork
    

    The creation of the serving process is the responsibility of method EchoServer»start (see the last line of Method \(\PageIndex{6}\)). The EchoServer»start method first checks wether the server is already running. It returns if the isRunning flag is set to true. Otherwise, a TCP socket dedicated to connection handling is created and made to listen on port 9999. The backlog size is set to 10 that is -as mentioned above- the system that allocates a buffer for storing 10 pending client connection requests. This value is a trade-off that depends on how fast the server is (depending on the VM and the hardware) and the maximum rate of client connections requests. The backlog size has to be large enough to avoid losing any connection request, but not too big, to avoid wasting memory. Finally EchoServer»start method creates a process by sending the fork message to the [ self serve ] block. The created process has the same priority as the creator process (i.e., the one that performs the EchoServer»start method, the UI process if you have executed it from a workspace).

    Method \(\PageIndex{7}\) (Pharo): The EchoServer»interactOnConnection Method

    EchoServer»interactOnConnection
        | interactionSocket |
        interactionSocket := connectionSocket waitForAcceptFor: 1 ifTimedOut: [^self].
        [self interactUsing: interactionSocket] fork
    

    Method EchoServer»serve (see Method \(\PageIndex{5}\)) triggers interactions with connected clients. This interaction is handled in the EchoServer» interactOnConnection method (see Method \(\PageIndex{7}\)). First, the connection socket waits for client connections for one second. If no client attempts to connect during this period we simply return. Otherwise, we get another socket dedicated to interaction as result. To process other client connection requests, the interaction is performed in another process, hence the fork in the last line.

    Method \(\PageIndex{8}\) (Pharo): The EchoServer»interactUsing: Method

    EchoServer»interactUsing: interactionSocket
        | receivedData |
        [ receivedData := interactionSocket receiveDataTimeout: 5.
            receivedData crLog.
            interactionSocket sendData: 'ECHO: ', receivedData
        ] ensure: [
            interactionSocket closeAndDestroy ]
    

    The interaction as implemented in method EchoServer»interactUsing: (see Method \(\PageIndex{8}\)) with a client boils down to reading data provided by the client and sending it back prefixed with the 'ECHO: ' string. It is worth noting that we ensure that the interaction socket is destroyed, whether we have exchanged data or not (timeout).


    This page titled 3.3: TCP Server is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.