ReactPHP Tutorial #4: Simple Chat With Sockets – Server

ReactPHP Tutorial #4: Simple Chat With Sockets – Server


In this video we are going to build a
real-time chat on TCP sockets. We start from scratch and step by step we’ll
create a chat server and the client. So, just to recap what is the socket. When we
have two programs that are communicating on the network: socket is one endpoint
of this two-way communication. There are two kinds of sockets. We have a server
socket which is bound to a specific port and is listening for incoming
connections and the client socket which knows where the server is listening. So,
the client connects to the server and the data exchange begins. We begin with
the server socket so we could start listening to incoming connections. As
always the first thing we need to do is to install the package. For our chat we
use a socket component. As always we start with a loop, then create a server
socket. It requires IP address with a port number and the loop. Now, when the
loop runs the socket immediately starts listening to incoming connections. Our
socket is an event emitter, that means that we can attach handlers for certain
events and perform actions when these events occur. For example when the new
connection arrives to the server, the server fires “connection” event and a
handler for this event accepts an instance of the incoming connection. To
prove that our server actually works, we can log a connection remote address. Now,
start the server and it is listening to incoming connections. Then, in another tab,
to connect I use telnet. I provide IP address and the port. Open the server tab
and it works. We have logged the address of our telnet client. Let’s create one
more connection. Again visit the server tab and we see one more logged message.
The next step is to write something back to the
client. For example let’s say “hello’. Now restart the server and again connect via
telnet. You see we have received the data from the server. Consider socket
connection as a stream. If you are not familiar with streams check one of the
previous videos where we have already discussed streams in details. Socket
connection is a duplex stream. We can write data to it and we can also read
data from it. To read data from the connection just add a handler for “data”
event. When new data is available this handler will be called. This is a
so-called echo-server, when new data arrives to it, it just sends it back to
the client. Again restart the server reconnect but now let’s send something.
In telnet just type the message and then press Enter. The exact same message has
been returned. That’s it: low level network communication in action. Now it’s
time to build something cool. A real-time chat. First of all we need to somehow
store our active connections. It will be a pool. Connections will be stored in a
SplObjectStorage. Consider it as a map from objects to data. Our connections
pool will have only one public method add(). When new connections arrive they will
be passed into this method. To each new connection we sent a
greeting message. Then we attach this connection to the pool and register some
event handlers. When the new data comes we will send it to all active
connections in our pool except the current one. In a callback we loop through already
stored connections and write the received data to them. Now when somebody
sends a message to our chat all other clients receive this message. We don’t want to store connections
endlessly. When a client closes the chat we should detach this connection from
the pool. The pool is done. It’s time to update the
server part. Create a pool pass it inside the connection callback
and then add incoming connection. And don’t forget to require connections
pool class. Now we switch to the terminal. At this
time I have two tabs with telnet connections to demonstrate chat with two
different clients. When one client sends the message the other one receives it.
It’s real-time chat written in asynchronous PHP. Let’s make our chat a little friendly.
For example when someone enters, we can notify everybody that a new member has
joined the chat. Again loop through all active connections and write a message
to each of them. And on the opposite when someone leaves,
the connection of this user fires “close” event. We are already listening to this
event so we can update its callback and broadcast a message that some user has
left the chat. And while we are here let’s change a
greeting message I don’t like this one-word “hello”. For example “welcome to chat”
looks much friendly. We are done with code and now it’s time to try our chat
and to prove that it behaves as we expect. Restart the server and connect
from two clients. As expected when someone enters or leaves the chat now it
is visible for other members. Let’s review the code we have written
and maybe there is a room for improvement.
You may notice this duplication with sending a message to all connections
except the current one. This is a good candidate for a method. We pass a message
that we want to send and the connection that should be avoided.
Now small fixes with names and doc block. And let’s replace this duplication with
our new nice method. When the new user arrives when a new message is written and when someone leaves the chat Yes. Now it looks much better. But take a
look at our chat. Still there is one huge problem.
it is completely anonymous. No one knows who enters the chat, who leaves, and even
who writes the message. Actually, it is not a chat, it is a mess of messages. So
let’s fix it. Remember that SplObjectStorage is a map between objects
and data. In our case data will be a user’s name. So we can’t use attach() and
detach() methods. instead we will use connection object as offset keys. Let’s
add two additional methods to get connection name and to set it. When a new user arrives the first thing
we should do is to ask for a name. We also store this connection but with an
empty name. Then next time we receive data from this connection we check if we
have a name associated with it, we consider this data as a message. But if
this connection doesn’t have a stored name we consider the received data
as a name and store it. Chat notification about a new user now
contains his or her name. The next time when we receive a new data from this
connection it will have a name associated with it. So this data will be
considered as a message and we can also update this chat notification with a
user’s name. The last thing we need to do is to remove the connection when it
closes. Again at first we get the name for this connection. Then remove it. And
then send a nice notification that the user with such a name has left the chat.
Now let’s see how we can clear this code. Because method add() is too big I think
that this one can be extracted to a method let’s call it addNewMember(). Fix name here and use new method here And actually this code can be also
extracted. What we are doing here: we add handlers for connection events. So let’s
call it initEvents(). I come back to my terminal and we try
the chat one more time. Now when we connect, we receive a welcome message and
then we need to enter the name. All notifications in chat come with user
names. It looks really nice. Of course our server is very simple but it is a
real-time chat written in PHP and actually nothing complicated here. We
have covered a very basic stuff: sockets and low-level network communication. If
you are familiar with streams you can easily understand sockets because the
socket behind the scenes is just a duplex stream. In the next episode we
will create a client for this chat and we will use this client instead of
telnet.

14 thoughts on “ReactPHP Tutorial #4: Simple Chat With Sockets – Server

  1. Just 1 minute into the video and I was amazed at how well you explain things, you should be making more videos or courses, this is great.

  2. I try to connect to server.php and i do it, but when i writing in one commend line telnet , it show in another telnet command without press enter key .

  3. Было бы интересно узнать как разработать хотя бы простенький интерфейс и как получать и отправлять данные находясь в нем

  4. Thanks Sergey. Great tutorial series. I´m following, line by line but when I close the terminal or use telnet quit, I get the error: Fatal error: Uncaught ArgumentCountError: Too few arguments to function ConnectionsPool::{closure}(), 0 passed in /Users/dev/Documents/PHP/ReactPHP/vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php on line 123 and exactly 1 expected in /Users/dev/Documents/PHP/ReactPHP/ConnectionsPool.php:43

    I have the code exactly as you. Line 43 is: $connection->on( 'close', function ( $data ) use ( $connection ) {

  5. This is works over tcp now. What do I have to change to send via http(s) ? I want to write a browser client and socket.io does not support TCP

Leave a Reply

Your email address will not be published. Required fields are marked *