Socket Programming

Have you ever wondered about how computers are connected across a network and how information is exchanged between them?

Well, socket programming is one way of connecting two nodes on a network to communicate with each other. (A node is any device that can send or receive information- a computer in simple words).This connection includes a server-side node, which is a socket that listens on a particular port at an IP, and a client-side node, which is a socket that reaches out to the server.

The connection

In this blog, I will be explaining how to send data from the client to the server socket, using python. The data will be obtained from the user using a joystick (XBOX PC controller) or a keyboard, with the help of the PyGame module.
The objective of the project is to print a message on the server depending on the various keys pressed by the user on the client.

The Basic Connection

We will need two python programs, one running on the server and the other running on the client node. (They can be run on the same device too).

Let us look at the stages of the connection for the server:

server-side connection

Consider the above code snippet,

To work with sockets in python, we need to first import the socket module. (No pip installation is required).

We have an object to identify the socket, initialized using the socket() function. It accepts two arguments- domain (IPv4 or IPv6) and connection type( TCP or UDP).

TCP (Transmission Control Protocol) is connection-oriented, which means, you need to create a connection before communicating.
UDP (User Diagram Protocol) is a connectionless network, where you just need to send data without making an actual connection. The obvious drawback is that we would not know if the packet has reached the destination. We will go with the default arguments, that is, IPv4 and TCP.

Next, we mention the IPv4 address of the server node. This address will be required by both the programs. You can obtain your server node’s IPv4 address by running the ‘ipconfig’ command in cmd.

C:\Users\Varun>ipconfig
. . .
Wireless LAN adapter Wi-Fi:
Connection-specific DNS Suffix . :
IPv6 Address. . . . . . . . . . . :
IPv4 Address. . . . . . . . . . . : 192.168.0.55

For the Port number, it can range from anywhere between 1024–65535. The initial port numbers 0–1023 are reserved for some privileged services.
The bind() function binds the socket to the IPv4 address and the port number, which are given in a tuple as the arguments.
The listen() function will determine the number of connections the server can accept.

The next step for the server is accepting the connection from the client, for which we use s.accept(). It returns the client socket and its address.
Since we want this connection to be continuous, we include it within a while loop.

Next, let us look at the stages of connection for the client:

client-side connection

Consider the above code snippet,
In addition to the socket module, we need to import another module called PyGame, and you will be required to perform a pip install if you are working with it for the first time.

pip install pygame

We use another object, ‘c’ to represent the client and the steps remain the same as that of the server. The host is always the address of the server. Also, make sure to connect to the same port number.
The connect() function connects our client socket to the socket whose address is given to it.

We have finally established a connection between the server and a client!

Exchange Of Messages

We primarily use two functions- send() and recv() to send and receive data respectively.

It is important that you realize that you can only send data in byte-format using the send() function. So if you have to send strings, then you must convert it using the bytes() function.

c.send(bytes(<your string>,'utf-8'))

The recv() function should be provided with the size of the message in bytes and is to be stored in its respective variable. Since this message is still in bytes form, we must convert it back to string format using the decode() function.

str=str.decode('utf-8')
sending and receiving

Above is a code snippet showing the server and client stages simultaneously.

First, the server sends a message welcoming the client and asking for a name.
The client receives the bytes, decodes them, takes a name input from the user and sends it back to the server in bytes form.

Size Of The Packet

The byte-size of the message is an important concept to be understood.
There is a common misconception that one send() call always results in one recv() call. This is far from the truth!

The number of send() and recv() calls depend on many factors. One of them is the byte size mentioned in recv(). If the byte size is less than the message size, then it will require more than one recv() call to collect the bytes sent.
If the receiving byte size is larger, then a single recv() call will still have more room after accepting a send() call. In this case, multiple partial messages are received as one byte array.

To explain this in a better way, let me show you an error I had experienced.

I wanted to send the same message one after the other continuously.
The message to be sent was: “No Button is Pressed”, which is 20 bytes.
But on the receiving end, I had mentioned the size to be 28 and issued a line break after a recv() call. This was the output obtained on the receiving end.

No Button is PressedNo Butto
n is PressedNo Button is Pre
ssedNo Button is PressedNo B
utton is PressedNo Button is
PressedNo Button is PressedN

The explanation for this logical error is that the recv() function would accept the message of 20 bytes, before adding a part of the next message to be sent until it reaches its threshold of 28 and only then, would it move to its next call.

This can be avoided only if the receiver knows the length of each message before it is actually sent. Hence, we must first send the size of the message to the receiver, thus helping it to set the byte size for the recv() function.
Let us skip to the ending of the code, to demonstrate this.

Accommodating for the size of the message

In the above snippet, ‘button’ is the variable that stores the message to be sent to the server. Let us ignore the process of generating the message based on the key presses of keyboard and joystick for now.

The length of the variable ‘button’ is determined first. Consider this integer value to be an ASCII value. This helps us to convert the number to a single character. (For eg: a message of length 65 will be converted to the character ‘A’.) The function chr() is used for this purpose. This single character is sent to the server.
The server decodes this single character back to its numerical value using the ord() function, and the length of the message is hence now known. The server then uses this size to then receive the actual message, ie ‘button’.

It is important to close the socket connection every time using socket.close().
This was all there is to know about basic socket connections.

Generating the message using PyGame

Our objective is to identify the buttons and keys that are pressed on the joystick or the keyboard. This can be identified using a few PyGame functions.

All operations must be performed in a PyGame window, hence we write all our codes within the pg.init() and pg.quit(). We must also initialize the PyGame screen.

The joystick has two types of input options:
A button, which will return a boolean value for its two states: 0 for not pressed and 1 for pressed. We use the joystick.get_button(i) function, where i ranges from 0–9 for the 10 buttons (A, B, X, Y, LB, RB, SELECT, START, LSTICK, RSTICK).

An axis, which will return float values (up to 8 decimal places) ranging from -1 to 1. The left stick of the joystick is split as two axes (vertical and horizontal). The left and right triggers make up a single axis. We use the joystick.get_axis(i) function to return the value.

Generating message based on input

The rest can be understood from the comments in the above code snippet.

Your Turn!

Things to remember before running the codes:

  1. Install the pygame module on the client-end.
  2. ALWAYS run the server first and then the client program.
  3. See that both sockets connect to the same PORT.
  4. Make sure that your server address is its updated IPv4 address.
    There are times when you disconnect and reconnect your device to your network, the IPv4 address could change if the previous one was assigned to another device in your network.
  5. Check for an active internet connection. (This was one error that was difficult to figure out from the prompt’s msg)

The only thing left now is running the two codes.

complete server.py
complete client.py

If all goes well, you should be getting a similar output.

Server, Client output and PyGame window

Thank you, readers!

Hope you enjoyed reading and found this blog helpful :)

--

--