Engee documentation

Sockets Support Package

Socket (raw socket) is an interface for exchanging data between processes both inside the same computer and between different machines over the network.

In the context of Engee, the raw socket runs on the side of the user’s client program through the Engee subsystem.Integration, and in Engee itself you get the results of data processing with the client program or send data to the client program unilaterally/bilaterally.

To get started with the socket support package, install the Engee subsystem.Integration: Run the client program and specify the Engee server URL (see instructions).

Basic methods

The key methods are listed below:

  • Sockets.Socket(family="AF_INET", socket_type="SOCK_STREAM", socket_descriptor=0) — constructor (TCP/IPv4 by default).

  • Sockets.bind(device, host::String, port::Int64) — binding to the address/port.

  • Sockets.listen(device, backlog::Int64) — Listening mode for incoming connections (for servers).

  • Sockets.accept(device) → Int64 — waits for the client, returns a descriptor[1] of the new socket.

  • Sockets.connect(device, host::String, port::Int64) → Int64 — Connect to a remote server (for clients).

  • Sockets.send(device, message::Vector{UInt8}) — sending bytes.

  • Sockets.receive(device, bufsize::Int64) → Union{Nothing, ResultUIntList} — Reading in blocking mode.

  • Sockets.is_open(device, socket_descriptor::Int64) → Bool — checking whether the socket is open.

  • Sockets.close(device) — closing the socket and releasing resources.

Step-by-step example of working with Sockets

Consider an example of data exchange between two UDP sockets on the same computer (localhost). In this example:

  • Socket 1 acts as a server: it binds to a specific port and waits for data;

  • Socket 2 acts as a client: it connects to the port of socket 1 and sends messages.

    1. Creation of the first socket (server) — create a socket by specifying the address family AF_INET (IPv4) and type SOCK_DGRAM (UDP):

      socket_1 = SOCKET.Socket("AF_INET", "SOCK_DGRAM")
    2. Bind the first socket to the address and port — bind the socket to the address of the local machine (127.0.0.1) and the port 8888. Now the socket "listens" to this port:

      socket_1.bind("127.0.0.1", 8888)
    3. Creating a second socket (client) — create a second socket with the same parameters:

      socket_2 = SOCKET.Socket("AF_INET", "SOCK_DGRAM")
    4. Connecting the second socket to the server — connect the client socket to the address and port on which the server socket is "listening":

      socket_2.connect("127.0.0.1", 8888)
    5. Message sending and verification cycle — Send 1000 messages from the second socket and immediately accept them in socket one, checking the correctness of delivery:

      const num_messages = 1000
      
      for i in 1:num_messages
          # Forming a text message
          message = "Message $i"
          # Convert the message to an array of bytes
          byte_array = collect(codeunits(message))
          # Sending an array of bytes via socket 2
          socket_2.send(byte_array)
      
          # We accept data (maximum 1000 bytes) on socket 1
          res_for_recv = socket_1.receive(1000).data
          # We check that the received data matches the sent data.
          @assert res_for_recv == byte_array
      end
      Type SOCK_DGRAM (UDP) does not guarantee the delivery and order of packets, but within the same computer (localhost), data exchange is almost lossless, which makes the example stable.
    6. Closing sockets and freeing resources — after the exchange is complete, close both sockets:

      socket_1.close()
      socket_2.close()

1. A handle is an integer with which the OS marks an open resource: socket, file, connection, etc. The program does not need to know what’s inside; it passes this number to the read/write/close functions, and the OS uses it to find the right resource. If the handle is closed or invalid, then operations with it do not work.