FOR DEVELOPERS

A Guide to Socket Programming in Python

Guide to Socket Programming in Python

Socket-based utilities are often used when cloning a Git repository, cURLing a URL, and generally, opening a connection between a computer and another device on the internet. In this guide, we'll learn about some basic network connection protocols (TCP and UDP), followed by a quick implementation in Python using the socket standard library. This will provide a better point of view about Python socket programming.

Transmission Control Protocol (TCP)

For all its positives, the internet can be unreliable. There is no guarantee that your next message or data packet will be received on the destination node. TCP is a connection-based protocol that allows two parties to communicate with each other. It is a stateful protocol that ensures all the data packets arrive at the other side. Even if just one packet misses its way on the connection data sequences, TCP is responsible for that packet getting repaired and retransmitted till the ending node receives it successfully.

User Datagram Protocol (UDP)

Unlike TCP, UDP is a connectionless protocol. There is no three-way handshake. It doesn't care about whether the packet has been received by the destination node or the amount of data that it sends through a connection. What this means is that there is no congestion control in UDP.

A short history of network programming

Back in the 1980s, alongside the issues that people had with AT&T's Unix, students of Berkeley University intended to work on a new OS. They came up with BSD. Regardless of the operating system designs and architectures, there was a connection interface component besides the BSD called the "BSD socket". This interface was made for working with the IPC socket.

Later in the early 1990s as the internet was fully revealed, there was a new topic called network programming. This was a new field in computer science and software development where developers were building network-related utilities to communicate and reach other nodes on the global network or internet.

Python socket and BSD socket

There is an interesting integration between the BSD socket in Python socket programming that we’ll see in the next section.

According to the official Python socket documentation, “This module (socket) provides access to the BSD socket interface. It is available on all modern Unix systems, Windows, macOS, and probably additional platforms.”

In order to work with the facilities that BSD socket provides, you need to work with interfaces. APIs help you interact with other layers. Therefore, in this case, you can use the socket library to access the methods and actions that are implemented in the BSD socket.

Python socket library

The BSD socket has some actions. You know that the socket standard library interacts with the BSD socket. Therefore, there are many similar functionalities between the BSD socket and Python's socket.

Socket Programming in Python.webp

In the next section, you’ll use the concepts mentioned earlier. You will also create a client-server scenario where the server binds the socket address and the client connects to the defined socket within the same network area.

Note: Socket connections sometimes cause weird-looking IO exceptions through the program, depending on the language you use. Every connection that you open for the sake of transmission has to be closed once you’re done working with it.

Setting up

In this Python socket tutorial, you’ll be implementing a simple client-server Python socket using TCP. For this purpose, you need two nodes in the same network. There could be two different smart devices, a virtual machine alongside the host machine, or even a docker container running on the same network as the host machine.

To set things up, try networking the server and client nodes together. If you're working with two physical devices (e.g., your laptop and PC), there are several ways you can join nodes into the same network. You can either use the hosted-network feature if your network interface is capable of it, or a LAN cable followed by a simple static IP addressing on each node.

Once you’ve networked both nodes together, try sending an ICMP request to the other node to make sure it's accessible. You can easily do it by using ifconfig or ip addr on the Unix-like machines or ipconfig on Windows and ping commands.

Use ifconfig or ipconfig (depending on your operating system) to get a summary of all addresses set to each of your network interfaces.

Run the ifconfig in a terminal/CMD tab on the server node.

$ ifconfig
...
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
	options=6463<RXCSUM,TXCSUM,TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
	ether c8:89:f3:c9:4a:b7 
	inet 192.168.185.14 netmask 0xffffff00 broadcast 192.168.185.255
	media: autoselect
	status: active
.

As seen, the IP address is 192.168.185.14. You will need this address for binding your socket object later. In this scenario, the local machine will be the server. You don't need any information about the other parties/clients; however, they need your information to initialize the connection.

To check if you can access the client, you need its local IP address. In this example, the client is a Linux VM. Running the same ifconfig command gets the IP address 192.168.185.72. So, both the local machine (server) and the Linux VM (client) are networked together with the following information:

  • Server: Linux (original host): 192.168.185.14
  • Client: Linux (VM): 192.168.185.72

On the server, create an ICMP request to the VM to ensure the connection between two nodes.

$ ping 192.168.185.72
PING 192.168.185.72 (192.168.185.72): 56 data bytes
64 bytes from 192.168.185.72: icmp_seq=0 ttl=64 time=3.637 ms
64 bytes from 192.168.185.72: icmp_seq=1 ttl=64 time=5.600 ms
64 bytes from 192.168.185.72: icmp_seq=2 ttl=64 time=4.682 ms
64 bytes from 192.168.185.72: icmp_seq=3 ttl=64 time=6.220 ms
...

Nice! You can access the client which means you're also accessible to the client. You can try pinging the server from the other side as well.

Tip: If you want to set up the socket locally and access that port from your local machine, you can use the localhost address as your server address and access a specific port. You'll have only one machine that will act as both client and server in two different terminal tabs. However, your server address will be 127.0.0.1 with any desired port (make sure that port is available and not in use).

Server-side implementation

On the server machine, which is the original host, you need to set up the socket and listen for incoming connections. Create an empty server.py source code file. Import socket and define a socket object.

import socket

IP_PORT = ('192.168.185.14', 9090) mysocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

First, define your IP and port addresses in a Tuple variable called IP_PORT. You’ll need it for binding. socket.socket() needs two required parameters to be able to define the socket. The first one is family, which you passed socket.AF_INET as its value, and the other one is type, which is socket.SOCK_STREAM, in this case.

Earlier, SOCK_* and AF_* were constants. They became IntEnums in the later versions of the socket library. Each constant used here is a number.

>>> import socket
>>> socket.AF_INET.value
2
>>> socket.SOCK_STREAM.value
1
  • AF_INET is the network address family for IPv4 (use AF_INET6 for IPv6).
  • SOCK_STREAM is the socket type for TCP connections (use SOCK_DGRAM for UDP connections).

Once you've defined your socket object, it's time to bind the socket to an address and tell it to listen for incoming calls.

...
mysocket.bind(IP_PORT)
mysocket.listen()

Now, you need to accept any incoming connection requests. You need a loop and iterate through each incoming request to let that connection happen.

...
while True:
client, client_information = mysocket.accept()
client_IP = client_information[0]
print(f'A TCP connection is opened for {client_IP}')

After a connection is opened, wait for the client to send a piece of data. Of course, that data needs to be encoded or decoded. So, first receive it and then decode and print out the message.

...
received_data = client.recv(1024)
print(f'{client_IP}: {received_data.decode()}')

The .recv() method takes a buffer size. It's basically the maximum amount of data size the server can receive at once, which you set to 1024 bytes. You can send the client back a message from the server once the connection has been established. You also encode the string data you send to the nodes.

...
client.send('Goodbye client..!'.encode())

The client needs to decode all data it receives and encode the data it sends. This is to make sure all characters and symbols look the same and that there is no issue with displaying the message context.

The server.py source code on the server node should look like this:

#! /bin/python

server.py

import socket

IP_PORT = ('192.168.185.14', 9090) mysocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

mysocket.bind(IP_PORT) mysocket.listen()

while True: client, client_information = mysocket.accept() client_IP = client_information[0] print(f'A TCP connection is opened for {client_IP}')

received_data = client.recv(1024)
print(f&#39;{client_IP} said: {received_data.decode()}&#39;)

client.send(&#39;Goodbye client!&#39;.encode())</pre>

Client-side implementation

On the client side, you have almost the same implementation but there will be no listening and binding. All you need is to make the connection, send a message, and expect the server to send a "Goodbye client..!" message as well.

Switch to the client machine and create a client.py file. The client source code is as below.

#! /bin/python
# client.py

import socket

SERVER_IP_PORT = ('192.168.185.14', 9090) mysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

mysocket.connect(SERVER_IP_PORT) print(f'== Connected to {SERVER_IP_PORT[0]} ==') message = input('Message: ')

mysocket.send(message.encode())

payload = mysocket.recv(1024) print(f'Server says: {payload.decode()}')

You need the server's IP and port address. Define them and then define a socket object to make the connection happen. Once established, the following will be the output on the server terminal.

A TCP connection is opened for 192.168.112.72

On the client side, type "hello!" as the input for the message variable. Once you hit ENTER, the message will be sent through the connection to the server. On the server machine, there will be the following new line in the terminal.

A TCP connection is opened for 192.168.112.72
192.168.112.72 said: hello!   

On the client machine, you'll see that the ending message that has been received from the server.

== Connected to 192.168.112.14 ==
Message: hello!
Server says: Goodbye client!

Why close a socket connection?

In this guide, you used socket.socket() to create a socket object for further connections. Although you didn't close any connection, the server is still listening. There is no connection termination request coming from the client, which is not good.

It's very important not to leave the connections open as they can cause resource issues and what’s called a socket descriptor leak.

Long story short, in Unix systems, you always deal with files. When you send an HTTP request, the OS puts the response into a file and responds back with the unique ID corresponding to that file (descriptor file). So do socket connections.

When you open a socket connection, you create a new file descriptor in the system. The full list of all system file descriptors can be seen using the lsof command. If you leave each file descriptor open, they might cause IO issues and resource exceptions during the connections. The best practice is to close the connections once you’re done working with them.

As the official documentation says:

Sockets are automatically closed when they are garbage-collected, but it is recommended to close() them explicitly, or to use a with statement around them.

Thanks to Python's socket, there is a close() method implemented to close a socket connection.

mysocket = socket.socket(...)
...
print(mysocket._closed) # False
mysocket.close()
print(mysocket._closed) # True

Using context managers to close a connection

There is also a "close()" magic-method implemented in the socket.socket() component. This means you can use context managers to handle connections.

with socket.socket(...) as mysocket:
  ...

After running the body of the with statement, the "close()" method is called and closes the connection immediately. This is a key feature in Python's context managers.

You can update the client.py file and close the connection after they serve their purpose.

#! /bin/python
# client.py

import socket

SERVER_IP_PORT = ('192.168.185.14', 9090)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as mysocket: mysocket.connect(SERVER_IP_PORT) print(f'== Connected to {SERVER_IP_PORT[0]} ==') message = input('Message: ')

mysocket.send(message.encode())

payload = mysocket.recv(1024)
print(f&#39;Server says: {payload.decode()}&#39;)

then mysocket._closed returns True

In this Python socket programming guide, you learned about socket connections and socket libraries. You also learned how to implement a simple client-server Python socket and how to close a socket connection. Note that with smart devices migrating to IPv6, it’s important to design software systems to make them compatible with IPv6.

Author

  • A Guide to Socket Programming in Python

    Sadra Yahyapour

    Sadra is a Python back-end developer who loves the architectural design behind the software. A GitHub Campus Expert and open-source contributor. He spends his free time writing high-quality technical articles.

Frequently Asked Questions

Socket programming is the act of making connections through the network ports between the nodes. In Python, you use the socket standard library to establish the connections and use them.

Consider the example in the guide. All you need to do is keep the connection open and consider each side as two clients when working with recv() and send() through a loop.

In this article’s scenario, you brought a one-time connection example whereas the nodes were sending only one message before the termination. You can control each connection’s longevity by setting intervals and timeouts, but you need to make sure to close it once you’re done.

Yes. On a smaller scale, you can consider each port as a socket connection and manage them concurrently.

It’s a state where the system file allocation is out of control. In most cases, garbage collector is smart enough to manage the leak’s causes, such as unallocated and unneeded resource usage.

You can use any additional library during your connection and message transmission. You can track all logs and messages as well as data sent and received by interacting with a database, such as SQLite.

View more FAQs
Press

Press

What’s up with Turing? Get the latest news about us here.
Blog

Blog

Know more about remote work. Checkout our blog here.
Contact

Contact

Have any questions? We’d love to hear from you.

Hire remote developers

Tell us the skills you need and we'll find the best developer for you in days, not weeks.