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.

_images/fignetworktopologies.png

Fig. 78 A star, ring, and bus network.

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.

_images/fignetworklayers.png

Fig. 79 Following a 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.

_images/figtcpserver0.png

Fig. 80 Server waiting for a connection from a client.

The client tcp_client.py runs in another terminal, shown in Fig. 81.

_images/figtcpclient0.png

Fig. 81 The client makes the connect to the server.

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.

_images/figtcpserver1.png

Fig. 82 Server and client are connected.

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.

_images/figtcpclient1.png

Fig. 83 The users enter a message prompted by the client script.

Then the server script confirms the message, as shown in Fig. 84.

_images/figtcpserver2.png

Fig. 84 The server confirms the message received from the client.

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

  1. a socket client for receiving data;
  2. 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

  1. Extend the client/server interaction to simulate a password dialogue. After receiving data from a client, the server returns access granted or access denied depending on whether the received data matches the password.
  2. Describe how you would implement the game of rock, paper, and scissors in a client/server manner.
  3. Redefine the mancala game of Project Two as a client/server interaction.
  4. Write an implementation of the server in the previous exercise.
  5. Write an implementation of the client in the previous exercise.