Sockets in C++


This topic describes the "Sockets in C++" sample application implementation.


Samples


This tutorial demonstrates how to use TCP and UDP sockets in a Native Client (NaCl) embed module. The sample application allows the user to create a basic TCP or UDP server that echoes text messages sent to it.

Figure 1. Sockets in C++ application

To communicate with a TCP or UDP server socket:

  1. To create the server socket, select the socket type, enter the desired port number, and click "Listen".
    The socket automatically starts listening for incoming connections.
  2. To create a client TCP or UDP socket and connect it to a host, select the socket type, enter the host address and port, and click "Connect". You can connect to the locally-created socket, or to a remote socket.
  3. To send a message to the server socket, enter the message in the "Secret Message" field, and click "Send".
  4. To close the client connection, click "Close".

Although the user interacts with the same application interface for both TCP and UDP sockets, the underlying logic depends on the socket type.

The JavaScript component can send the following messages to the NaCl component:

  • Create a client TCP socket and connect to a specified host.
  • Create a client UDP socket and connect to a specified host.
  • Close the client TCP or UDP socket.
  • Listen on a TCP or UDP server socket.
  • Send a message from the client socket to the server.

The NaCl component only sends log messages to the JavaScript component. The logs are shown in the "NaCl messages" area in the application.

Figure 2. Server logs

For information on how to access the sample application cheat sheet and run the application, see Sample-based Tutorials.

Initializing the Instance

To initialize the TCP and UDP interfaces:

  1. In the main SocketInstance class, create the client and server objects:

     // TCP client for connecting, sending and receiving messages
      SimpleTCPClient tcp_client_;
    
      // TCP server for handling incoming connections and sending/receiving messages from them.
      SimpleTCPServer tcp_server_;
    
      // UDP client for sending and receiving messages
      SimpleUDPClient udp_client_;
    
      // UDP server used for binding to local address and 
      // receiving/sending messages from/to remote hosts.
      SimpleUDPServer udp_server_;
    
  2. When initializing the objects, check whether each interface is available.

    An interface can be unavailable if the required permissions have not been specified in the manifest file, or if the interface is not supported by the platform.

    if (!SimpleTCPClient::InterfacesAreAvailable()) {
      Logger::Error("SocketsInstance: Failed to initialize SimpleTCPClient");
      return false;
    }
    

    In the InterfacesAreAvailable() function, check whether the isAvailable() function returns true for each interface used in the class:

    bool SimpleTCPClient::InterfacesAreAvailable() {
      // Check whether the TCPSocket interface is available
      if (!pp::TCPSocket::IsAvailable()) {
        Logger::Error("TCPSocket not available");
        return false;
      }
    
      // Check whether the HostResolver interface is available
      if (!pp::HostResolver::IsAvailable()) {
        Logger::Error("HostResolver not available");
        return false;
      }
    
      return true;
    }
    

Implementing the TCP Server

To implement the TCP server functionality:

  1. Create the sockets. The sample application TCP server handles 1 connection at a time. It needs separate sockets to listen for messages and to save the incoming connection:

    // Socket used as a server
    pp::TCPSocket listening_socket_;
    
    // Socket used to save incoming connections
    pp::TCPSocket incoming_socket_;
    
  2. Listen for connections.

    When the user clicks "Listen", create new event handlers, reset the server instance if it already exists, and start the listening routine:

     // Create callbacks for accepting an incoming connection and receiving a message
      pp::CompletionCallbackWithOutput<std::string> on_server_receive_callback =
        callback_factory_.NewCallbackWithOutput(
          &SocketsInstance::OnTCPServerReceiveCallback);
      pp::CompletionCallbackWithOutput<std::string> on_server_accept_callback =
        callback_factory_.NewCallbackWithOutput(
          &SocketsInstance::OnTCPServerAcceptCallback);
    
      // If a previous server existed, close all connections on it
      tcp_server_.Reset();
    
      // Listen on the specified port
      tcp_server_.Listen(port, on_server_accept_callback,
                         on_server_receive_callback);
    
  3. In the Listen() function, initialize the listening socket by binding it to a port. When the bind operation is successful, begin listening:

    void SimpleTCPServer::Listen(uint16_t port,
      // ...
      pp::CompletionCallback callback =
        callback_factory_.NewCallback(&SimpleTCPServer::OnBindCompletion);
    
      // Bind to a specific address (optional)
      int32_t rtn = listening_socket_.Bind(addr, callback);
      // ...
    }
    
    void SimpleTCPServer::OnBindCompletion(int32_t result) {
      // ...
      pp::CompletionCallback callback =
        callback_factory_.NewCallback(&SimpleTCPServer::OnListenCompletion);
      // When binding is successful, start listening for messages
      int32_t rtn = listening_socket_.Listen(kBacklog, callback);
      // ...
    }
    
  4. When a client connection attempt is detected, trigger a listening callback to accept the connection.

  5. In the on_server_accept_callback_ function, which is triggered when a client connection attempt is successful, save the client socket to the incoming_socket_ object and log the IP address the connection was accepted from.

  6. Wait for incoming messages on the client socket using the TryRead() function.

  7. Implement echoing received messages to the client in the on_server_receive_callback_ function, which is triggered when a message is successfully read and saved in the receive_buffer_ variable:

    // This callback is run when a message is received on the server socket
    void SocketsInstance::OnTCPServerReceiveCallback(int32_t result,
                                                     const std::string& message) {
      // ...
      // Echo the received message
      tcp_server_.Write(message,
                        callback_factory_.NewCallbackWithOutput(
                          &SocketsInstance::OnTCPServerReceiveCallback));
    }
    
  8. In the Write() function, write the message to the connected socket, and resume waiting for incoming messages.

  9. When the client disconnects, reading fails and the failure message is passed to the on_server_receive_callback_ function. When the client disconnection message is detected, restart listening for connections.

Implementing the TCP Client

The client TCP uses a single pp::TCPSocket object. After the application is initialized, the TCP socket client can connect to the TCP server in the sample application or an external TCP server.

To implement the TCP client functionality:

  1. To connect to a TCP server:

    1. Create a TCP socket, and resolve the host address using the pp::HostResolver interface, transforming the address string into a pp::NetAddress object:
      void SimpleTCPClient::Connect(const std::string& host,
        pp::CompletionCallbackWithOutput<std::string> receive_callback) {
        // ...
        // Create the TCPSocket instance for the connection
        tcp_socket_ = pp::TCPSocket(instance_handle_);
        // ...
        // Create a HostResolver instance
        resolver_ = pp::HostResolver(instance_handle_);
        // ...
        pp::CompletionCallback callback =
          callback_factory_.NewCallback(&SimpleTCPClient::OnResolveCompletion);
        // Resolve the host name to its IP
        resolver_.Resolve(hostname.c_str(), port, hint, callback);
      }
      
    2. Establish the connection:
      void SimpleTCPClient::OnResolveCompletion(int32_t result) {
        // ...
      
        // Use the IP address extracted in the Connect() function
        pp::NetAddress addr = resolver_.GetNetAddress(0);
      
        pp::CompletionCallback callback =
          callback_factory_.NewCallback(&SimpleTCPClient::OnConnectCompletion);
      
        // Invoke Connect() with the resolved IP address
        Logger::Log("TCPClient: Connecting ...");
        tcp_socket_.Connect(addr, callback);
      }
      
  2. To send a text message, write it to a socket:

     uint32_t size = message.size();
    const char* data = message.c_str();
    
    // Write the message to the socket
    result = tcp_socket_.Write(data, size, callback);
    
  3. To receive messages from the server without requiring an explicit server response, in the Receive() function, implement callbacks to listen for and receive messages:

    1. In the SimpleTCPClient::Receive() function, attempt to read from the socket.
    2. When a server message is received, trigger the SimpleTCPClient::OnReceiveCompletion() function.
    3. In the SimpleTCPClient::OnReceiveCompletion() function, trigger the OnTCPClientReceiveCallback() function from the main class.
    4. Resume waiting for messages by calling the Receive() function again.
    void SimpleTCPClient::Receive(
      pp::CompletionCallbackWithOutput<std::string> on_receive_callback) {
      // Remember the on_receive_callback callback
      // so it can be called when a message is received
      on_receive_callback_ = on_receive_callback;
      memset(receive_buffer_, 0, kBufferSize);
      pp::CompletionCallback callback =
        callback_factory_.NewCallback(&SimpleTCPClient::OnReceiveCompletion);
    
      // Read from the socket
      tcp_socket_.Read(receive_buffer_, kBufferSize, callback);
    }
    
    void SimpleTCPClient::OnReceiveCompletion(int32_t result) {
      // ...
      // receive_buffer_ contains the received message
      std::string message(receive_buffer_, result);
      // Pass the message to the callback
      *on_receive_callback_.output() = message;
      // Invoke the callback with the result
      on_receive_callback_.Run(result);
    }
    
    // Invoked when a message is received on the client socket
    void SocketsInstance::OnTCPClientReceiveCallback(int32_t result,
                                                     const std::string& message) {
      // ...
      tcp_client_.Receive(
        callback_factory_.NewCallbackWithOutput(
          &SocketsInstance::OnTCPClientReceiveCallback));
    }
    
  4. To close the connection, close the current pp::TCPSocket instance and reset it by creating a new one:

    tcp_socket_.Close();
    tcp_socket_ = pp::TCPSocket();
    

Implementing the UDP Server

Unlike a TCP server, only a single UDP socket is needed for a UDP server. After the server has been bound to a listening port, it can monitor incoming connections and receive messages from multiple sources.

To implement the UDP server functionality:

  1. Create the UDP socket:

      pp::UDPSocket udp_listening_socket_;
    
  2. Bind the server to listen for messages:

    void SimpleUDPServer::RecvFrom(uint16_t port,
      pp::CompletionCallbackWithOutput<struct MessageInfo> on_server_receive_from_callback) {
      udp_listening_socket_ = pp::UDPSocket(instance_handle_);
      // ...
      pp::CompletionCallback callback =
        callback_factory_.NewCallback(&SimpleUDPServer::OnBindCompletion, port);
      
      // Bind to a specific address
      int32_t rtn = udp_listening_socket_.Bind(addr, callback);
      // ...
    }
    
    void SimpleUDPServer::OnBindCompletion(int32_t result, uint16_t port) {
      // ...
      pp::CompletionCallbackWithOutput<pp::NetAddress> callback =
        callback_factory_.NewCallbackWithOutput(
          &SimpleUDPServer::OnRecvFromCompletion);
    
      // Start receiving data on the socket
      udp_listening_socket_.RecvFrom(receive_buffer_, kBufferSize, callback);
    }
    
  3. When a message is received, trigger the OnUDPServerReceiveCallback() function of the SocketInstance class. In this callback, echo the received message:

    void SimpleUDPServer::OnRecvFromCompletion(int32_t result,
                                               pp::NetAddress address) {
      // ...
      std::string message(receive_buffer_, result);
      struct MessageInfo ext_message(address, message);
      // Set the message as a parameter to pass to the callback function
      *on_server_receive_from_callback_.output() = ext_message;
      on_server_receive_from_callback_.Run(result);
    }
    
    // Invoked on an incoming message to the server socket
    void SocketsInstance::OnUDPServerReceiveCallback(
      int32_t result, struct MessageInfo extended_message) {
      // ...
      // Echo the message to the sender
      udp_server_.SendTo(extended_message.message, extended_message.remoteAddress,
                         callback_factory_.NewCallbackWithOutput(
                           &SocketsInstance::OnUDPServerReceiveCallback));
    }
    
  4. After the message is sent, resume listening for incoming messages:

    pp::CompletionCallbackWithOutput<pp::NetAddress> callback =
      callback_factory_.NewCallbackWithOutput(
        &SimpleUDPServer::OnRecvFromCompletion);
      // Try to read data from the socket
      udp_listening_socket_.RecvFrom(receive_buffer_, kBufferSize, callback);
    

Implementing the UDP Client

The UDP client is implemented similarly to the TCP client. The main difference is that you must bind the UDP socket before you can listen for messages. The logic for sending and handling received messages, and closing the connection is the same as for the TCP client:

  1. To connect to a server:

    1. Create a pp::UDPSocket instance.
    2. Resolve the host address.
    3. Bind the socket to an IP address and port.
    4. Listen for incoming messages.
  2. To send and receive messages:

    • To send a message, write the message to the socket.
    • To handle a received message:
      1. Trigger the OnReceiveFromCompletion() function, which invokes the OnUDPClientReceiveFromCallback() function of the SocketInstance class.
      2. To resume listening, call the Receive() function.
  3. To close the connection:

    1. Close the current pp::UDPSocket instance.
    2. Reset the socket instance by creating a new one.