Client/Server Interactions¶
With the socket module in Python, we can define client/server interactions.
Computer Networks¶
We classify types of connections based on the transmission media and the network topologies.
The unit for transmission speed is bps
,
short for bits per second.
We distinguish four categories of media used for data transmission:
- twisted pair wire: pair of copper wires used for
telephone communications.
Transmission speeds vary from 2,400 to 33,600
bps
. - coaxial cable: for many telephone lines or television
Data transmission over short distances guarantees
speeds up to \(10^7\)
bps
. - optical fibre: signals transmitted via light-emitting
diodes encode bits as presence or absence of light.
Transmission speeds reach up to \(10^9\)
bps
. - electromagnetic waves: wireless over short distances or via satellites across long distances.
Three of the most common regular topologies are shown in Fig. 78.
In a star network, there is one central node. All other nodes are connected to this central node. In a ring network, the nodes from a closed circuit, message circle around the ring of nodes. In a bus network, all nodes are connect on a single bus, as used in the Von Neumann architecture.
Client/server architecture defines the communication between two computers: one is the server and the other acts as the client. A client places a request or order to a server. The server processes the request. The client does not need to know how the server processes the requests. We distinguish between software and hardware client/server architectures:
- web and database servers offer software services;
- file and print servers offer hardware services.
The four Layers in Internet Communication are application, transport, network, and link.
- The application layer consists of client and server software. The application prepares the message and defines the destination address.
- The transport layer formats the message by chopping it into packets attaching a sequence number and destination address to each packet. When receiving, packets are collected and reassembled into a message.
- The network layer determines the (intermediate) address for each packet. This layer detects when a packet has reached its final destination.
- The link layer is responsible for the actual transmission of packets.
The workings of the layers are illustrated in Fig. 79, as we follow one message from source to destination.
Network protocols are rules for network communication. We consider two types of protocols:
- TCP Transmission Control Protocol First a message is sent that data is coming. Only after the receiver acknowledges this message will the sender send the data. All successful transmissions are confirmed, and retransmissions are acknowledged.
- UDP User Datagram Protocol Unlike TCP, no connection is established prior to sending data. The sender just carries on after sending a message.
TCP is connection oriented, UDP is connectionless. TCP is more reliable whereas UDP is more streamlined.
Sockets are objects programs use to connect. Sockets were introduced in 1981 in BSD Unix. Originally used for communication between processes, i.e.: between two programs on same computer. Sockets support communication across platforms, independent of the operating system.
In addition to the IP address of the computer, both server and client must use the same port. A port is a 16-bit integer, some are reserved for particular protocols. Any port between 1,024 and 65,535 is free. Sockets support both TCP and UDP.
Defining a Simple Client/Server Interaction¶
A simple client/server interaction is defined by
the scripts tcp_server.py
and tcp_client.py
which respectively define the actions of the server
and the client.
Both scripts runs in separate terminal windows.
First we start tcp_server.py
,
as shown in Fig. 80.
The client tcp_client.py
runs in another terminal,
shown in Fig. 81.
As soon as we start the script tcp_client.py
we notice a change in the terminal window where
the server script runs, shown in Fig. 82.
In this simple client/server interaction,
a string will be transmitted from the client to the server.
The user of the client script tcp_client.py
types in
the message, as shown in Fig. 83.
Then the server script confirms the message, as shown in Fig. 84.
The socket module exports the class socket()
which returns an object representing a socket.
The first argument of socket
is the Address Family (AF):
AF_UNIX
: for UNIX sockets;AF_INET
: most commonly used for internet
AF_INET
supports both TCP and UDP,
given respectively by SOCK_STREAM
and SOCK_DGRAM
as the second argument of socket()
.
For example:
from socket import socket as Socket
from socket import AF_INET, SOCK_STREAM
sock = Socket(AF_INET, SOCK_STREAM)
The methods bind
and connect
are called
to define connections between client and server.
After a socket is created, both server and client define
server_address = (hostname, number)
To bind the socket to the address, the server does
sock.bind(server_address)
and the client contacts the server then via
sock.connect(server_address)
With the methods listen
and accept
, requests are taken.
With listen()
the server indicates how many
incoming connections will be accepted:
sock.listen(2) # accept at most 2 connections
The server takes requests via the accept()
method:
client, client_address = sock.accept()
The accept()
method returns
- a socket
client
for receiving data; - the address of the client.
The methods send
and recv
are used to send and receive data.
The client sends data with send()
.
sock.send(data)
The server receives data applying recv()
.
data = client.recv(buffer)
to the socket client
obtained with accept()
.
When all is over, both client and server do
sock.close()
What is sent must be of type bytes.
The methods encode()
and decode()
are applied
to convert strings into bytes and bytes into strings.
To avoid an error such as
TypeError: a bytes-like object is required, not 'str'
we have to work with encode()
and decode()
,
as illustrated in the session below.
>>> n = 4
>>> b = str(n).encode()
>>> b
b'4'
>>> type(b)
<class 'bytes'>
>>> c = int(b.decode())
>>> c
4
Now we are ready to look at the definition of the actions
of the server in tcp_server.py
.
This script with the definition of the setup starts as follows:
from socket import socket as Socket
from socket import AF_INET, SOCK_STREAM
HOSTNAME = '' # blank so any address can be used
PORTNUMBER = 41267 # number for the port
BUFFER = 80 # size of the buffer
SERVER_ADDRESS = (HOSTNAME, PORTNUMBER)
SERVER = Socket(AF_INET, SOCK_STREAM)
SERVER.bind(SERVER_ADDRESS)
SERVER.listen(2)
Then the interaction with the client are defined
in the continuation of the script tcp_server.py
listed below:
print('server waits for connection')
CLIENT, CLIENT_ADDRESS = SERVER.accept()
print('server accepted connection request from ',\
CLIENT_ADDRESS)
print('server waits for data')
DATA = CLIENT.recv(BUFFER).decode()
print('server received \"%s\"' % DATA)
SERVER.close()
The script tcp_client.py
is shorter than tcp_server.py
.
The client script is listed below.
from socket import socket as Socket
from socket import AF_INET, SOCK_STREAM
HOSTNAME = 'localhost' # on same host
PORTNUMBER = 41267 # same port number
BUFFER = 80 # size of the buffer
SERVER_ADDRESS = (HOSTNAME, PORTNUMBER)
CLIENT = Socket(AF_INET, SOCK_STREAM)
CLIENT.connect(SERVER_ADDRESS)
print('client is connected')
DATA = input('Give message : ')
CLIENT.send(DATA.encode())
Guessing a Secret¶
As an application, consider a little game of a player/dealer communication.
The dealer generates a secret number.
The player must guess the number.
After each guess, the dealer sends feedback:
too low
, too high
, or found the secret
.
The game terminates when the secret is found.
The player is the client, the dealer is the server
The setup of the server (the dealer) starts as follows:
from random import randint
from socket import socket as Socket
from socket import AF_INET, SOCK_STREAM
HOSTNAME = '' # blank so any address can be used
PORTNUMBER = 11267 # number for the port
BUFFER = 80 # size of the buffer
DEALER_ADDRESS = (HOSTNAME, PORTNUMBER)
DEALER = Socket(AF_INET, SOCK_STREAM)
DEALER.bind(DEALER_ADDRESS)
DEALER.listen(1)
print('dealer waits for player to connect')
PLAYER, PLAYER_ADDRESS = DEALER.accept()
print('dealer accepted connection request from ',\
PLAYER_ADDRESS)
The script for the server continues below.
SECRET = randint(0, 9)
print('the secret is %d' % SECRET)
while True:
print('dealer waits for a guess')
GUESS = PLAYER.recv(BUFFER).decode()
print('dealer received ' + GUESS)
if int(GUESS) < SECRET:
REPLY = 'too low'
elif int(GUESS) > SECRET:
REPLY = 'too high'
else:
REPLY = 'found the secret'
PLAYER.send(REPLY.encode())
if REPLY == 'found the secret':
break
DEALER.close()
The code for the client (the player) starts with the setup of the communication.
from socket import socket as Socket
from socket import AF_INET, SOCK_STREAM
HOSTNAME = 'localhost' # on same host
PORTNUMBER = 11267 # same port number
BUFFER = 80 # size of the buffer
DEALER = (HOSTNAME, PORTNUMBER)
PLAYER = Socket(AF_INET, SOCK_STREAM)
PLAYER.connect(DEALER)
The player is prompted for a guess until the secret number is found. The script for the player then continues below:
print('player is ready to guess')
while True:
GUESS = input('Give number : ')
PLAYER.send(GUESS.encode())
ANSWER = PLAYER.recv(BUFFER).decode()
print('>', ANSWER)
if ANSWER == 'found the secret':
break
PLAYER.close()
Exercises¶
- Extend the client/server interaction to simulate a password
dialogue. After receiving data from a client, the server
returns
access granted
oraccess denied
depending on whether the received data matches the password. - Describe how you would implement the game of rock, paper, and scissors in a client/server manner.
- Redefine the mancala game of Project Two as a client/server interaction.
- Write an implementation of the server in the previous exercise.
- Write an implementation of the client in the previous exercise.