This topic describes the "Sockets in C++" sample application implementation.
Due to NaCl deprecation by the Chromium project, Tizen TV will continue its support for NaCl only until 2021-year products. Meanwhile, Tizen TV will start focusing on high-performance, cross-browser WebAssembly from 2020-year products.
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.
embed
To communicate with a TCP or UDP server socket:
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:
The NaCl component only sends log messages to the JavaScript component. The logs are shown in the "NaCl messages" area in the application.
For information on how to access the sample application cheat sheet and run the application, see Sample-based Tutorials.
To initialize the TCP and UDP interfaces:
In the main SocketInstance class, create the client and server objects:
SocketInstance
// 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_;
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:
InterfacesAreAvailable()
isAvailable()
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; }
To implement the TCP server functionality:
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_;
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);
In the Listen() function, initialize the listening socket by binding it to a port. When the bind operation is successful, begin listening:
Listen()
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); // ... }
When a client connection attempt is detected, trigger a listening callback to accept the connection.
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.
on_server_accept_callback_
incoming_socket_
Wait for incoming messages on the client socket using the TryRead() function.
TryRead()
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:
on_server_receive_callback_
receive_buffer_
// 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)); }
In the Write() function, write the message to the connected socket, and resume waiting for incoming messages.
Write()
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.
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.
pp::TCPSocket
To implement the TCP client functionality:
To connect to a TCP server:
pp::HostResolver
pp::NetAddress
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); }
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); }
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);
To receive messages from the server without requiring an explicit server response, in the Receive() function, implement callbacks to listen for and receive messages:
Receive()
SimpleTCPClient::Receive()
SimpleTCPClient::OnReceiveCompletion()
OnTCPClientReceiveCallback()
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)); }
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();
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:
Create the UDP socket:
pp::UDPSocket udp_listening_socket_;
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); }
When a message is received, trigger the OnUDPServerReceiveCallback() function of the SocketInstance class. In this callback, echo the received message:
OnUDPServerReceiveCallback()
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)); }
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);
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:
To connect to a server:
pp::UDPSocket
To send and receive messages:
OnReceiveFromCompletion()
OnUDPClientReceiveFromCallback()
To close the connection: