Real Time Communication
Real Time Communication
The objective of this lesson is to give the student some basic understanding of how real-time
applications work through the use of sockets.
This lesson will be beneficial and much more interactive if done with 1 or more partners.
Intro
Every communication over a network is performed by sockets, just different types with different
protocols, but behind it all is sockets.
What is a socket?
The socket works in 2 ways; firstly someone must open a port and be listening for connections
(this computer is usually known as the server) and one or more other people can connect to this
server.
A server can open up multiple ports for listening, a port can receive multiple connections.
The server listens on the port for connection, this process creates a socket object on the server
side.
When the client connects to the server via its listening port, the client too has a socket object
that is pointing to the server. The server listens for connections and accepts received
connections, these accepted connections contain socket objects themselves.
Take the below example of a simple server listening for connections and a client connecting to
it.
These examples can be run locally or with a team mate. If with a teammate, ensure that you are
both on the same network and can ping each other.
import socket
print(f"Listening on {host}:{port}")
while True:
client, address = socket.accept()
print(f"Connection gotten. client is {client}, address {address}")
# we are expecting a first message from the client,
# typically an identification message
message = client.recv(1024)
Run the above file to ensure it has no errors, keep it running, we'll need it for the next section.
If working with a team mate, each of you should run the file on your computers
Create another file socket_client.py and put the following code in it:
import socket
import json
# create socket object and connect to the server's host and port
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.connect((server_host, server_port))
my_details = {
"user": {
"id": "<put a number here>",
"name": "<username>"
},
"specialty": "<ANYTHING HERE>"
}
socket.send(data_to_send)
Running this file should display the required message on the server side.
Bi-directional communication
We are currently able to create a server listening for communications and to connect to that
server from one client.
But that's not enough, we need to be able to handle multiple clients and also to make it such
that the client can listen for messages when they are not sending.
Accept connections from multiple clients (it needs to have a list of clients, and be able to
add or remove connections from this list)
Get messages sent from the clients and send it to the other clients connected on the
server
import socket
import threading
import json
class Server:
def __init__(self, host="0.0.0.0", port=12345):
self.__clients = []
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind((host, port))
self.socket.listen(5)
print(f"Listening on {host}:{port}")
except json.decoder.JSONDecodeError:
pass
if not data:
break
except ConnectionResetError:
pass
finally:
# remove the client from the list of clients if they disconnect
client.close()
print(f"{username}'s socket closed")
self.__clients.remove(client)
def broadcast(self, msg, sender=None):
"""
Send msg to all connected clients except the sender
If sender is None, send msg to all connected clients
"""
for sock in self.__clients:
if sock != sender:
sock.send(msg)
if __name__ == "__main__":
server = Server()
Exercise: Add a broadcast method whose goal is to send a message to all the clients
connected to the Server.
It takes a message and sender (which has a default value) of None as arguments
It loops through the server's list of clients and for each client, it sends the message (using
the send method of the client) if that client is not the sender
import socket
import json
import threading
class Client:
def __init__(self, server_host="localhost", server_port=12345,
username="Prince", user_id=1, on_message_receive=None) -> None:
self.username = username
self.id = user_id
my_details = {
"user": {
"id": self.id,
"name": self.username
},
"message": "SYN"
}
self.send_message(my_details)
def listen_to_server(self):
"""
Listen to messages from the server. Add received messages to the list
of messages
"""
while True:
data_bytes = self.socket.recv(1024)
print(f"Message received: ")
message = data_bytes.decode()
print(message)
self.__messages.append(message)
if __name__ == "__main__":
username = input("Username? ")
user_id = input("User id? ")
# get a message from the user and send the message to server
while True:
message = input("Say something: ")
client.send_message_as_dict(message)
Exercises:
1. Add a send_message method to the Client class. This method should take as argument a
message (this message should be a dictionary).
Raise a ValueError if the message argument is not a dictionary
Convert the dictionary to string with the json.dumps and store the result in a variable
data_to_send
Convert the data_to_send to bytes using the .encode('utf-8') method.
Send the data_to_send variable using the class's socket attribute
2. Add a send_message_as_dict method to the Client class. This method takes a string as
argument, creates a dictionary and then calls the send_message method and passes this
dictionary as argument.
Create a dictionary, message_dict with 2 keys: "user" and "message"
The "message" key has as value the message that was passed as argument
The "user" key has as value another dictionary with keys "id" and "name". The "id"
key's value is the instance's "id" attribute and the "name" key's value is the
instance's name attribute.
Call the instance's send_message method and pass the message_dict as argument.
Now with this code, we should be able to have multiple connections from our server and receive
messages sent by any client.
Run the socket_server.py on a computer and then the socket_client.py on other computers
on the network try sending messages to the different clients.
This interaction is the skeleton of most real-time applications. A group of people connect to a
server, when one person sends a message to the server, the server forwards it to all the other
connected clients.
The thing that differs from application to application is the data that is actually being sent. For
messaging apps, the data sent is text and possibly files; for games, the data sent can be a
move or a data structure representing the game's state.
Now this is a more crude implementation of real-time apps, in reality you would probably use
some library like socket-io to help with the server and client side interactions.