Streams vs Datagrams
When you send data through the sockets, there are situations when you really do not care if some packets are lost during the transition. For example, you are streaming a live video from your camera. The order of the frames is less important than timely delivery, you only want the frames to arrive at the clients as soon as possible. And it also does not matter if some of the frames are lost. You are not going to make a resend request just to make sure that all the frames are shown to the client in the correct order because otherwise your live video will be delayed. Nobody wants to be 10 seconds behind the present moment, so you simply skip the lost frames and move further. This is exactly how UDP (User Datagram Protocol) works. With UDP we send packets of data (datagrams) to some IP address and that is all. We have no guarantee that these packets will arrive, we also have no guarantee about their order.
TCP (Transmission Control Protocol) instead guarantees that your data arrives otherwise it will tell you that an error occurred. This protocol uses the principle of a reliable connection. This means that we are establishing a connection between server and client and then start transferring the data. This connection can be considered as a duplex stream which provides a reliable and sequenced communication. It looks like we write information to a file on one computer, and on the other computer, you can read it from the same file. Also, the TCP connection can be considered as a continuous stream of data - the protocol itself takes care of splitting the data into packets and sending them over the network. Datagram, on the other hand, has no connection, only a source, and a destination. Communication via UDP cannot be considered as reliable and it doesn’t provide any sequence.
- Uses the principle of connections.
- Guarantees the delivery and sequence.
- Automatically splits information into the packets.
- Ensures that data is not sent too heavily (data flow control).
- No connection between client and server.
- Not 100% reliable and may lose data.
- You need to manually split the data into datagrams and send them.
- Data sent/received order might not be the same.
Simple Echo Server
ReactPHP Datagram component provides socket client and server for ReactPHP. There is one entry point to create both client and server:
React\Datagram\Factory. This factory has two methods
createClient() and requires an instance of the event loop:
Here we create a server socket listening on
localhost and port
createServer() returns a promise. Then we can specify handlers when this promise becomes fulfilled and when it fails. Fulfilled handler accepts an instance of the datagram socket
We can listen to
message event to receive the datagrams sent by the client. The handler accepts the message received from the client, the client address and an instance of the datagram socket:
To test our server we can use netcat from the command line:
Nice! The server is working and listening for the incoming datagrams. Now it’s time to create a simple client. And again we create an event loop and a factory, and then use the factory’s
createClient() method, which also returns a promise:
The fulfilled handler accepts an instance of the datagram socket we have connected to. We are already familiar with this socket, which we have used in the fulfilled handler of the server.
Now we are implementing simple echo UDP server/client, so our client will take the input from the console and send it to the server. The server will receive it and send it back to the client. When the client receives the data from the server it outputs it to the console. At first, we modify the server to send a data received from a client back to this client:
To send data via UDP socket we can use
send($data, $remoteAddress = null). We build a message and send it back to the address, from which we have received the incoming message. We also log some data to the console. The client part will be a bit more complex. We need an instance of a readable stream to get the input from the console.
In the snippet above we create an instance of the
\React\Stream\ReadableResourceStream class. Then we pass this instance to the fulfilled handler. Then when we receive the input from the console, we
trim it to remove a new line character and then send this data to the server.
The last step now is to receive the data from the server on the client side. Like we did it with the server we can listen to the
Now we have a simple echo UDP server and a client that sends data to this server:
Simple UDP Chat
UDP has no such thing as a connection between server and client, so we should implement some events ourselves. For example, the server doesn’t know when a client enters or leaves the chat (connects and disconnects). The server only receives data from clients. We have a client’s address and some data. So, it is a client’s job to notify a server what client is going to do: to enter a chat, to leave it, or simply to write a message. Looks like we have three different types of data:
message. So, let’s start coding with a server.
This is what we have built in the echo section of this article but now encapsulated in the class. The main logic of our server will be implemented in the handler for the
message event. We assume that clients send JSON-encoded arrays to us. Each array has three fields:
type- for the type of the action.
name- a client’s name.
message- a message from the client (can be empty for example, when a client enters/leaves the chat).
Then according to the
type, we should perform different actions:
Next step is to implement each of these actions. When a new client arrives we store its address in the
$clients array. We also notify all other clients that we have a new member in the chat:
When we receive a
leave typed message, we
unset this client’s address and notify other clients that this client has left the chat:
Otherwise, we have a
message data, so we simply send this message to other clients. We format a message to include a client’s name in it and then send this message to everybody except this current client:
Now our main
process method will look like this. Very simple stuff:
The server part now is ready. You can find a full source of it on GitHub.
The client grabs the input from the console and then sends some data to the server. To interact with the console we are going to use a readable stream. So let’s start implementing the client:
There are two main methods here.
initClient to setup main socket event handlers and
processInput which is responsible for taking a user input, processing it and then sending to a server. The first method will be very simple. We are going to listen to several events:
message to output the received data and
close to stop the event loop. Also when we are connected to a socket we ask a user to enter the name:
The next method
processInput($data) is responsible for the main logic of the client. When we grab the input we need to perform some checks. First of all, if the
$name property is empty that means that we have just connected to a socket. So, we assume that a user’s input is his or her name, we store it in the
$name property and then we send a data with type
enter indicating that a new user enters the chat. But how can we determine that a client leaves that chat? I’m going to use a vim-like command here. For example, when a user enters
:exit string that means that we are leaving a chat. In all other cases we consider that a user has entered a simple message:
A full source code for a client can be found on GitHub.
Now we can test our chat in action. This is how it looks:
Of course, the goal of this article was not to build a robust chat but to show how you can use ReactPHP Datagram component to work with UDP sockets. We have also covered the difference between TCP and UDP sockets and in which situations each of them will be more preferable. A simple chat here was a sort of a real world application built on UDP sockets to demonstrate their usage.
You can find a source code for this client on GitHub.
This article is a part of the ReactPHP Series.