Skip to main content
Engineering LibreTexts

3.4: SocketStream

  • Page ID
    43802
  • \( \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}}\)

    SocketStream is a read-write stream that encapsulates a TCP socket. It eases the data exchange by providing buffering together with a set of facility methods. It provides an easy-to-use API on top of Socket.

    SocketStream diagram.
    Figure \(\PageIndex{1}\): SocketStream allows the use of Socket in an easy way.

    A SocketStream can be created using the method SocketStream class>>openConnectionToHost:port:. By providing the host address or name and the port, it initialises a new Socket with the default parameter of the system. But, you can build a SocketStream on top of an existing Socket with the method SocketStream class>>on:, which allows you to send data on a socket you have already configured.

    With your new SocketStream, you can receive data with the useful methods receiveData, which waits data until a timeout, or receiveAvailableData, which receives data, but does not wait for more to arrive. The method isDataAvailable allows you to check if data is available on the stream before receiving it.

    You can send data using the methods nextPut:, nextPutAll:, or nextPutAllFlush: that put data in the stream. The method nextPutAllFlush: flushes the other pending data before putting the data in the stream.

    Finally, when the SocketStream is finished being used, send the message close to finish and close the associated socket. Other useful methods of SocketStream are explained below in the chapter.

    SocketStream at Client Side

    We illustrate socket stream use at client side with the following code snippet (Script \(\PageIndex{1}\)). It shows how the client uses a socket stream to get the first line of a webpage.

    Script \(\PageIndex{1}\) (Pharo): Getting the first line of a web page using SocketStream

    | stream httpQuery result |
    stream := SocketStream
        openConnectionToHostNamed: 'www.pharo-project.org'
        port: 80.
    httpQuery := 'GET / HTTP/1.1', String crlf,
        'Host: www.pharo-project.org:80', String crlf,
        'Accept: text/html', String crlf.
    [ stream sendCommand: httpQuery.
    stream nextLine crLog ] ensure: [ stream close ]
    

    The first line creates a stream that encapsulates a newly created socket connected to the provided server. It is the responsibility of message openConnectionToHostNamed:port:. It suspends the execution until the connection with the server is established. If the server does not respond, the socket stream signals a ConnectionTimedOut exception. This exception is actually signaled by the underlying socket. The default timeout delay is 45 seconds (defined in method Socket class»standardTimeout). One can choose a different value using the SocketStream»timeout: method.

    Once our socket stream is connected to the server, we forge and send an HTTP GET query. Notice that compared to Script 4.2.5, we skipped one final String crlf (Script \(\PageIndex{1}\)). This is because the SocketStream»sendCommand: method automatically inserts CR and LF characters after sending data to mark line ending.

    Reception of the requested web page is triggered by sending the nextLine message to our socket stream. It will wait for a few seconds until data is received. Data is then displayed on the transcript. We safely ensure that the connection is closed.

    In this example, we only display the first line of response sent by the server. We can easily display the full response including the html code by sending the upToEnd message to our socket stream. Note however, that you will have to wait a bit longer compared to displaying a single line.

    SocketStream at Server Side

    SocketStreams may also be used at the server side to wrap the interaction socket as shown in Script \(\PageIndex{2}\).

    Script \(\PageIndex{2}\) (Pharo): Simple Server using SocketStream

    | connectionSocket interactionSocket interactionStream |
    connectionSocket := Socket newTCP.
    [
        connectionSocket listenOn: 12345 backlogSize: 10.
        interactionSocket := connectionSocket waitForAcceptFor: 30.
        interactionStream := SocketStream on: interactionSocket.
        interactionStream sendCommand: 'Greetings from Pharo Server'.
        interactionStream nextLine crLog.
    ] ensure: [
        connectionSocket closeAndDestroy.
        interactionStream ifNotNil: [interactionStream close]
    ]
    

    A server relying on socket streams still uses a socket for handling incoming connection requests. Socket streams come into action once a socket is created for interaction with a client. The socket is wrapped into a socket stream that eases data exchange using messages such as sendCommand: or nextLine. Once we are done, we close and destroy the socket handling connections and we close the interaction socket stream. The latter will take care of closing and destroying the underlying interaction socket.

    Binary vs. Ascii mode

    Data exchanged can be treated as bytes or characters. When a socket stream is configured to exchange bytes using binary, it sends and receives data as byte arrays. Conversely, when a socket stream is configured to exchange characters (default setting) using message ascii, it sends and receives data as Strings.

    Suppose we have an instance of the EchoServer (see Section 4.3) started by means of the following expression

    server := EchoServer new.
    server start.
    

    The default behavior of socket stream is to handle ascii strings on sends and receptions. We show instead in Script \(\PageIndex{3}\) the behavior in binary mode. The nextPutAllFlush: message receives a byte array as argument. It puts all the bytes into the buffer then immediately triggers the sending (hence the Flush in the message name). The upToEnd message answers an array with all bytes sent back by the server. Note that this message blocks until the connection with the server is closed.

    Script \(\PageIndex{3}\) (Pharo): A SocketStream Interacting in Binary Mode

    interactionStream := SocketStream
        openConnectionToHostNamed: 'localhost'
        port: 9999.
    interactionStream binary.
    interactionStream nextPutAllFlush: #[65 66 67].
    interactionStream upToEnd.
    

    Note that the client manages strings (ascii mode) or byte arrays (binary mode) have no impact on the server. Indeed in ascii mode, the socket stream handles instances of ByteString. So, each character maps to a single byte.

    Delimiting Data

    SocketStream acts simply as a gateway to some network. It sends or reads bytes without giving them any semantics. The semantics, that is the organization and meaning of exchanged data should be handled by other objects. Developers should decide on a protocol to use and enforce on both interact- ing sides to have correct interaction.

    A good practice is to reify a protocol that is to materialize it as an object which wraps a socket stream. The protocol object analyzes exchanged data and decides accordingly which messages to send to the socket stream. Involved entities in any conversation need a protocol that defines how to organize data into a sequence of bytes or characters. Senders should conform to this organization to allow receivers to extract valid data from received sequence of bytes.

    One possible solution is to have a set of delimiters inserted between bytes or characters corresponding to each data. An example of delimiter is the sequence of ASCII characters CR and LF. This sequence is considered so useful that the developers of the SocketStream class introduced the sendCommand: message. This method (illustrated in Script 4.2.5) appends CR and LF after sent data. When reading CR followed by LF the receiver knows that the received sequence of characters is complete and can be safely converted into valid data. A facility method nextLine (illustrated in Script \(\PageIndex{1}\)) is implemented by SocketStream to perform reading until the reception of CR+LF sequence. One can however use any character or byte as a delimiter. Indeed, we can ask a socket stream to read all characters/bytes up to some specific one using the upTo: message.

    The advantage of using delimiters is that it handles data of arbitrary size. The cons is that we need to analyze received bytes or characters to find out the limits, which is resource consuming. An alternative approach is to exchange bytes or characters organized in chunks of a fixed size. A typical use of this approach is for streaming audio or video contents.

    Script \(\PageIndex{4}\) (Pharo): A content streaming source sending data in chunks

    interactionStream := "create an instance of SocketStream".
    contentFile := FileStream fileNamed: '/Users/noury/Music/mySong.mp3'.
    contentFile binary.
    content := contentFile upToEnd.
    chunkSize := 3000.
    chunkStartIndex := 1.
    [chunkStartIndex < content size] whileTrue: [
        interactionStream next: chunkSize putAll: content startingAt: chunkStartIndex.
        chunkStartIndex := chunkStartIndex + chunkSize.
    ]
    interactionStream flush.
    

    Script \(\PageIndex{4}\) gives an example of a script streaming an mp3 file. First we open a binary (mp3) file and retrieve all its content using the message upToEnd:. Then we loop, sending data in chunks of 3000 bytes. We rely on the next:putAll:startingAt: message that takes three arguments: the size (number of bytes or characters) of the data chunk, the data source (a sequenceable collection) and the index of the first element of the chunk. In this example, we make the assumption that the size of the content collection is a multiple of the chunk size. Of course, in a real setting, this assumption does not hold and one needs to deal with the last part of data that is smaller than a chunk. A possible solution is to replace missing bytes with zeros. In addition, loading everything in memory first is often not a practical solution and streaming approaches are usually used instead.

    Script \(\PageIndex{5}\) (Pharo): Reading data in chunks using SocketStream

    | interactionStream chunkSize chunk |
    interactionStream := SocketStream
        openConnectionToHostNamed: 'localhost'
        port: 9999.
    interactionStream isDataAvailable ifFalse: [(Delay forMilliseconds: 100) wait].
    chunkSize := 5.
    [interactionStream isDataAvailable] whileTrue: [
        chunk := interactionStream next: chunkSize.
        chunk crLog.
    ].
    interactionStream close. 'DONE' crLog.
    

    To read data in chunks, SocketStream responds to the next: message as illustrated by Script \(\PageIndex{5}\). We consider that we have a server running at port 9999 of our machine that sends a string which size is a multiple of 5. Right after the connection, we wait 100 milliseconds until the data is received. Then, we read data in chunks of five characters that we display on the Transcript. So, if the server sends a string with ten characters 'HelloWorld', we will get on the Transcript Hello on one line and World on a second line.


    This page titled 3.4: SocketStream 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.