0% found this document useful (0 votes)
28 views7 pages

Real Time Communication

This lesson introduces real-time communication using Python sockets, explaining how a server listens for connections and clients connect to it. It provides code examples for creating a simple server and client, as well as enhancements for handling multiple clients and bi-directional communication. The document concludes by emphasizing the basic structure of real-time applications and suggests using libraries like socket-io for more advanced implementations.

Uploaded by

Khalil Hafiz
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views7 pages

Real Time Communication

This lesson introduces real-time communication using Python sockets, explaining how a server listens for connections and clients connect to it. It provides code examples for creating a simple server and client, as well as enhancements for handling multiple clients and bi-directional communication. The document concludes by emphasizing the basic structure of real-time applications and suggests using libraries like socket-io for more advanced implementations.

Uploaded by

Khalil Hafiz
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

In this lesson we are going to be looking at real-time communication and are going to be briefly

introduced to a new Python data type - bytes (or byte strings).

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.

Write the following code in a python file called socket_server.py

import socket

# host and port to listen on


# 0.0.0.0 means to listen on all IP addresses this machine is associated with
host, port = "0.0.0.0", 12345

# create socket object and listen on defined host and port


socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.bind((host, port))
socket.listen(5)

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)

print(f"Message sent from client {message.decode()}")


print(f"The client is of type {type(client)}")

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

# if with a team mate, replace localhost with their IP address


server_host, server_port = "localhost", 12345

# 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>"
}

# convert the dictionary to string


data_to_send = json.dumps(my_details)
# convert string to bytes (encoded in utf-8)
data_to_send = data_to_send.encode('utf-8')

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.

To do so we are going to have to modify our server and client code.

We need the server to:

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

We need the client to:

Stay connected to the server, even after sending messages


Receive messages from the server

To do so, we are going to create Server and Client classes respectively.

Replace the code in socket_server.py with this:

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}")

# listen for connections


while True:
client, address = self.socket.accept()
# add new client to the list of clients
self.__clients.append(client)

print(f"Connection from {address}")


message = client.recv(1024)
print(f"Message sent from client {message.decode()}")
try:
message = json.loads(message.decode())

# listen for messages from the client in a separate thread


client_thread = threading.Thread(target=self.listen_for_messages,
args=(client, message["user"]["name"]))
client_thread.start()

except json.decoder.JSONDecodeError:
pass

def listen_for_messages(self, client, username):


try:
while True:
# Read data from the client
data = client.recv(1024)

if not data:
break

print(f"Data gotten from client {data.decode()}")

# broadcast the received message to every connected


client except the
# one that originally sent the message
self.broadcast(data, msg)

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

Replace the code in socket_client.py with the following:

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

# list of messages received from the server


self.__messages = []

self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


self.socket.connect((server_host, server_port))

my_details = {
"user": {
"id": self.id,
"name": self.username
},
"message": "SYN"
}
self.send_message(my_details)

# a function that the client should call when it receives the


message
self.on_message_receive = on_message_receive

# listen for messages on a separate thread


server_listener = threading.Thread(target=self.listen_to_server)
server_listener.start()

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 the self.on_message_receive attribute exists and it is


a function
# call the function and pass the received message as
argument
if self.on_message_recive and
callable(self.on_message_receive):
message= json.loads(message)
self.on_message_receive(message)

if __name__ == "__main__":
username = input("Username? ")
user_id = input("User id? ")

client = Client(username=username, user_id=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.

You might also like