בס"ד
Python Socket
Programming
Lior Shalom
The Complete Starting-Point
For Socket Programming
In Python
Suitable for both beginners and experienced developers
LinkedIn: https://www.linkedin.com/in/-lior-shalom/
Email: [email protected]
1 © All rights reserved to Lior Shalom
בס"ד
What is a Socket?
A socket is an endpoint for sending or receiving data between two
machines over a network. It allows programs to communicate with each
other by sending and receiving data streams .
Sockets come in two main types:
1. TCP (Transmission Control Protocol) Sockets: These provide
reliable, ordered, and error-checked delivery of data streams. They
are commonly used for applications that require a stable and
guaranteed delivery of data, such as web browsing, email, and file
transfer.
2. UDP (User Datagram Protocol) Sockets: These provide a
connectionless service, where data packets are sent individually
without any guarantees of delivery, order, or error-checking. They are
commonly used for applications that require low-latency and fast
transmission, such as real-time audio/video streaming and online
gaming.
Socket Programming in Python
Python ‘socket’ module provides a straightforward interface for socket
programming. It allows you to create, connect, send, and receive data
through sockets.
To get started, you need to import the ‘socket’ module in your Python
script:
Creating a Socket
To create a socket, you need to call the ‘socket()’ function provided by
the ‘socket’ module. It takes two arguments:
- The address family (AF_INET for IPv4 or AF_INET6 for IPv6).
- The socket type (SOCK_STREAM for TCP or SOCK_DGRAM for UDP).
Here’s an example of creating a TCP socket:
And here’s an example of creating a UDP socket:
2 © All rights reserved to Lior Shalom
בס"ד
Binding a Socket
After creating a socket, you need to bind it to a specific address and port
on your machine. The address can be an IP address or a hostname
(“www.example.com”), and the port is a numerical value that identifies a
specific service.
Here’s an example of binding a socket to a specific address and port:
Let’s break it down:
- In the line of code ‘sock.bind((host, port))’ the double
parentheses are used to create a tuple containing the ‘host’ and
‘port’ values.
- The ‘bind()’ method is used to associate a socket with a specific
network address (IP address) and port number. The method expects
a single argument, which is a tuple containing the host and port
values.
- By using double parentheses ‘((host, port))’, you are creating a
tuple with the ‘host’ and ‘port’ values enclosed within it. This is the
expected format for the ‘bind()’ method to receive the network
address and port as a single argument.
3 © All rights reserved to Lior Shalom
בס"ד
Listening for Connections
If you’re creating a server socket and expecting incoming connections,
you need to call the ‘listen()’ method on the socket object. It takes an
argument specifying the maximum number of queued connections.
-
The argument passed to the ‘listen()’ method specifies the
maximum length of this connection queue, indicating how many
connections can be in the queue at a given time. Once the queue is
full, any additional incoming connections may be refused or blocked
until space becomes available in the queue.
- For example, if you call ‘listen(5)’ on a server socket, it means that
the server can have up to five connections waiting in the queue while
it processes existing connections. If a sixth connection arrives, it may
be rejected or put on hold until one of the previous connections is
accepted and processed.
- The purpose of specifying the maximum number of queued
connections is to manage the server’s workload and prevent
overwhelming it with more connections than it can handle
concurrently.
Here’s an example of how this is done:
- By specifying ‘1’ as the argument, it means that the socket can only
handle one connection at a time. When a connection request arrives,
it will be added to a queue, and the socket will process the
connections in the order they were received.
- If you want to support multiple users or simultaneous connections,
you can increase the argument value to indicate the maximum
number of queued connections that can be waiting. For example,
‘sock.listen(5)’ would allow up to five connections to wait in the
queue.
4 © All rights reserved to Lior Shalom
בס"ד
Accepting Connections
Once the server socket is listening, you can accept incoming connections
using the ‘accept()’ method. This method blocks the execution of your
program until a client connects and it returns a new socket object
representing the connection and the client’s address.
Here’s an example:
In this code snippet, we're accepting a connection from a client.
Let’s break it down:
- ‘sock.accept()’ is a method call on the server socket object ‘sock’.
It waits until a client connects to the server socket.
- When a client connects, the ‘accept()’ method returns two values:
1. ‘client_socket’: This is a new socket object representing the
connection between the server and the client. You can use this socket
to send and receive data to or from the client.
2. ‘client_address’: This is a tuple containing the address (IP
address) and port number of the client.
- After the ‘accept()’ call, you have successfully established a
connection with the client. You can use the ‘client_socket’ object to
send and receive data to/from the client.
The ‘accept()’ method is used on the server side only.
When the server socket is in the listening state, the ‘accept()’ method is
called on the server socket. It blocks the program’s execution until a
client connects to the server. Once a client connection is established, the
‘accept()’ method returns a new socket object representing the
connection between the server and that specific client. This new socket
can be used by the server to communicate with the client.
In summary, the ‘accept()’ method is used by the server to accept
incoming client connections and create a separate socket specifically for
communicating with each client.
In this page:
I provided an easy-to-understand explanation of the ‘accept()’ method
and its usage. It explains the process of accepting connections using the
‘accept()’ method on the server socket. It describes how the method
blocks program execution until a client connects and returns a new socket
object representing the connection with the client. The information also
clarifies that the ‘accept()’ method is used on the server side, and once
the client connection is established, the server can use the returned
socket object to communicate with that specific client.
5 © All rights reserved to Lior Shalom
בס"ד
Additionally (The Client Side)
In a typical client-server architecture, both the client and the server
would have sockets. The client socket is used to establish a connection
with the server socket and to exchange data.
Here’s an example of how to create a socket for the client side:
Let’s break it down:
- In this code, we create a TCP client socket using ‘socket.socket()’
with the same address family (‘AF_INET’) and socket type
(‘SOCK_STREAM’) as we used for the server socket.
- We then used the ‘connect()’ method of the client socket to
establish a connection to the server. The ‘connect()’ method takes
the server’s address (an IP address or hostname) and port number
as a tuple.
- Once the connection is established, we can send and receive data
to/from the server as we will learn in the next topic.
In summary, in a client-server model, you would have a socket for the
server to listen and accept connections (‘socket.socket()’ + ‘bind()’ +
‘listen()’ + ‘accept()’), and a separate socket for the client to connect
to the server (‘socket.socket()’ + ‘connect()’). Both the server and the
client can use the ‘send()’ and ‘recv()’ methods (that we will learn in
the next topic) to exchange data over their respective sockets.
6 © All rights reserved to Lior Shalom
בס"ד
Sending Data
Once you have a socket connection established with a client, you can send
data from the server to the client using the ‘send()’ method of the socket
object.
Here’s an example:
In this code, we are sending the string ‘Hello, client!’ to the client using
the ‘send()’ method. The ‘encode()’ method is used to convert the
string into bytes before sending it over the socket.
Note that the ‘send()’ method may not send all the data at once,
especially for large data. It will return the number of bytes sent, which
you can use to ensure that all the data is sent if needed.
The ‘encode()’ method is a general method in Python that converts a
string into its corresponding byte representation, which is necessary when
working with binary data, including socket communication.
When using sockets, data is transmitted in binary format, and strings
need to be converted into bytes before sending them over the network.
The ‘encode()’ method is used to perform this conversion, and on the
receiving side, the ‘decode()’ method is used to perform the reverse
operation (converting the received data from bytes back into string
representation).
7 © All rights reserved to Lior Shalom
בס"ד
Receiving Data
Similarly, you can receive data from the client using the ‘recv()’ method
of the socket object.
Here’s an example:
In this code we are receiving data from the client using the ‘recv()’
method with a buffer size of ‘1024’ bytes (I will explain about it soon).
The received data is stored in the ‘data’ variable. The ‘decode()’ method
as I mentioned before is used to convert the received bytes into a string
for easier handling and printing.
Just like the ‘send()’ method, the ‘recv()’ method may not receive all
data at once, especially for large data. It will return the data received up
to the buffer size specified. If you expect large data, you would need to
receive in multiple iterations and concatenate the received data.
In our case, the buffer size specified as ‘1024’ bytes, determines the
maximum amount of data that can be received in a single call to ‘recv()’.
- Think about the buffer as a temporary storage area. When you call
‘recv()’, it reads data from the network and places it into this buffer.
The method then returns the data from the buffer up to the specified
buffer size.
- However, it’s important to note that if the data being sent by the
client is larger than the buffer size, the ‘recv()’ method will retrieve
data up to the maximum capacity of the buffer which was defined.
The remaining data will still be present on the network, waiting to be
received.
In summary, the buffer size determines the maximum amount of data
that can be received at once using the ‘recv()’ method. If the data size
exceeds the buffer size, you need to receive the data in multiple iterations
and concatenate the received portions to obtain the complete data.
8 © All rights reserved to Lior Shalom
בס"ד
Closing the Connection
Once you have finished communicating with the client, it’s important to
close the client socket using the ‘close()’ method. This releases the
resources associated with the connection and terminates the connection
gracefully.
Here's an example:
In addition to the matter of resource consumption, closing the client
connection is crucial for network security. By promptly closing the
connection, you minimize the risk of unauthorized access or malicious
attacks. It’s a straightforward and effective way to protect your system
and keep your network secure.
Closing the Server Socket
Similarly, when it comes to the server side, closing the server socket is a
crucial step in socket programming especially when you’re building a
server that listens for multiple connections. It is accomplished by invoking
the ‘close()’ method on the server socket object.
Here’s an example:
In this page:
We covered the crucial step of closing the connection in both the client
and server sockets. By properly closing the client socket using the
‘close()’ method, we release associated resources and terminate the
connection gracefully. This crucial operation not only ensures efficient
resource management but also plays a vital role in maintaining network
security. Similarly, on the server side, closing the server socket is
essential, particularly when dealing with multiple connections. By invoking
the ‘close()’ method on the server socket object, we complete the socket
programming process and create a secure network environment.
In Conclusion
This comprehensive guide covered various aspects of socket programming
in Python. Starting with an understanding of sockets, we explored the
creation, binding, and listening for connections. Additionally, we discussed
accepting connections, sending and receiving data, and the significance of
closing both client and server sockets.
9 © All rights reserved to Lior Shalom
בס"ד
Let’s put it all together!
In this practical example, we'll take it a step further and put together all
the knowledge we’ve gained so far. Our aim is to reinforce your
understanding of socket programming in Python by guiding you through
the process of sending and receiving data between sockets over a
network.
By following this step-by-step walkthrough, you’ll gain hands-on
experience and ensure that the concepts are absorbed in a clear and
comprehensible manner.
To enable data communication between sockets, we’ll need to set up both
a server socket and a client socket. These two components will work
together to establish a connection and facilitate the exchange of
information. Let’s begin by creating the foundation for our project.
Step 1: Setting up the Project
In your preferred IDE, such as Visual Studio Code (VS Code) or PyCharm
(I’m using VS Code), let’s start by creating two separate project files for
the server and for the client components: ‘server.py’ and ‘client.py’.
1. Open your IDE and create a new file called ‘server.py’.
2. Similarly, create another file called ‘client.py’ in the same directory.
By creating separate files for the server and client, we can keep the code
organized and focus on the specific functionality of each component. This
separation allows us to build and test them independently before
integrating them together for communication.
In the next steps, we’ll dive into writing code for both the server and
client components.
10 © All rights reserved to Lior Shalom
בס"ד
Step 2: Server Socket Setup
In the ‘server.py’ file, we’ll begin by setting up the server socket. The
server will listen for incoming client connections and handle the
communication with each connected client.
In this particular example we’ll create a TCP socket.
It should look like that:
In this code we set a TCP server socket. Alternatively, you could create a
UDP server socket by using the ‘socket.SOCK_DGRAM’ to specify the
socket type as UDP. Then we bind the server to a host and a port using
the ‘bind()’ method.
When using the ‘bind()’ method in the server project, you need to
specify the hostname (or IP address) and the port number to bind the
server socket to. Here are some considerations for determining the values
to use:
1. Hostname or IP Address:
- If you want the server to listen on a specific network interface, you
can use the IP address of that interface. For example, if you have
multiple network interfaces and you want the server to listen on a
specific one, you can use the corresponding IP address.
- If you want the server to listen on the local machine (the same
machine where the server code is running), you can use the
hostname ‘localhost’ or the IP address ‘127.0.0.1’. Both refer to
the loopback interface (Internal network communication interface),
which allows network communication within the same machine.
- If you want the server to listen on all network interfaces (all available
IP addresses), you can use an empty string as the hostname or the
IP address ‘0.0.0.0’. This allows the server to accept connections
from any available network interface.
11 © All rights reserved to Lior Shalom
בס"ד
2. Port Number:
- The port number specifies the communication endpoint on the host.
You can choose any available port number within the valid range (0
to 65535), except for well-known ports that are typically reserved for
specific services (for example: port 80 for HTTP, port 443 for HTTPS
etc).
- It’s recommended to use port numbers above 1024 to avoid conflicts
with well-known ports.
We finished setting the server socket in a listening state. but we
still can't run the server to accept incoming connections. We'll
cover this topic in step 5 (Receiving Data on the Server).
Step 3: Client Socket Setup
After the server socket is ready, we can move on to the ‘client.py’ file to
start configuring the client socket. The client socket will be responsible for
initiating a connection with the server socket and facilitating the
communication between the client and the server.
It should look like that:
In our code, we use ‘localhost’ as the host since ‘client.py’ and
‘server.py’ are running on the same machine (our computer). Modify the
values accordingly if the server is located on a different machine.
Great Job! You made so far and your client and server socket
projects look very good! You have established the server socket
and client successfully.
Now, Let’s move on to the next steps, where we’ll focus on
sending and receiving data between the client and the server.
12 © All rights reserved to Lior Shalom
בס"ד
Step 4: Sending Data from Client to Server
In this step, we’ll send data from the client to the server. Let’s add the
following code to the ‘client.py’ file:
In this code we created a variable ‘message’ that holds the data we want
to send to the server. Then, we use the ‘send()’ method of the client
socket to send the encoded version of the message to the server.
As we learned before, the ‘encode()’ method converts the string
message into bytes (binary), which is the format expected by the socket
for transmission.
Step 5: Receiving Data on the Server
In this step, we’ll modify the ‘server.py’ file to receive and process the
data sent by the client. Add the following code to the ‘server.py’ file:
In our ‘server.py’ code, the server socket uses the ‘accept()’ method to
accept a client connection. The method returns a new socket object
representing the client socket and the address of the client.
When the ‘accept()’ method is called, it waits for a client to establish a
connection then it returns the relevant information. Then, we use the
client socket to receive data using the ‘recv()’ method with the argument
‘1024’ specifying the maximum number of bytes to be received at once.
Next, we decode the received bytes back into string using the ‘decode()’
method. Finally, we print the received message.
13 © All rights reserved to Lior Shalom
בס"ד
Time to Test Your Code!
Before we move on, it’s time to witness the power of your code in action.
So far, we have been building the server side and the client side. Now,
let’s put our code to the test!
Run the server side first, and then run the client side to witness the magic
in action. You’ll see how the server and client communicate with each
other seamlessly.
If you using VS code:
On the right side of the terminal, you will find separate sections for the
server’s terminal and the client’s terminal. This allows you to switch
between the two and conveniently view their outputs and interact with
them.
This feature enables you to monitor the server’s activities, such as
incoming connections and received messages, while also observing the
client’s interactions with the server.
Step 6: Sending Data from Server to Client
In this step, we’ll modify the ‘server.py’ file to send data from the server
to the client. Add the following code to the ‘server.py’ file:
In this code, we define a variable ‘response’ that holds the data we want
to send to the client. Then, we use the ‘send()’ method of the
‘client_socket’ to send the encoded version of the response to the
client.
It’s important to note that the ‘send()’ method may not send the entire
data at once, especially if the data is large. Therefore, it’s a good practice
to check the return value of the ‘send()’ method, which indicates the
number of bytes sent. If the return value is less than the total length of
the data, you may need to send the remaining portion of the data in
subsequent ‘send()’ calls.
# (returned value of ‘send()’ = number of bytes sent)
14 © All rights reserved to Lior Shalom
בס"ד
Step 7: Receiving Data on the Client
In this step, we’ll modify the ‘client.py’ file to receive and process the
data sent by the server. Add the following code to the ‘client.py’ file:
The client socket uses the ‘recv()’ method to receive data from the
server. We specify the maximum number of bytes to be received at once
as ‘1024’. Then, we decode the received bytes back into a string using
the ‘decode()’ method. Finally, we print the received response.
Feel free to practice sending and receiving data between the
sockets to solidify your knowledge.
Step 8: Closing the Sockets
In this step, we’ll add code to properly close the server and client sockets
once the communication is complete. As we learned before, it’s important
to release the network resources and gracefully terminate the connection.
Respectively, add the following code to both the ‘server.py’ and
‘client.py’ files:
By calling the ‘close()’ method on the server and the client sockets, we
ensure that the sockets are closed, releasing any associated system
resources.
That’s it! You have learned how to send data from the server to
the client and receive it on the client side.
Make sure to add the respective code snippets to your ‘server.py’ and
‘client.py’ files, and once you have done that, you can run both files
simultaneously. The client will send a message to the server, and the
server will respond back to the client.
15 © All rights reserved to Lior Shalom
בס"ד
Introduction: Handling Multiple Connections
In our guide, we’ve covered the fundamentals of socket programming in
Python, including creating sockets, binding them to specific addresses and
ports, and establishing communication between a server socket and a
client socket. We’ve learned how to send and receive data, as well as how
to close the connection gracefully.
Now, we’re going to take your understanding of socket programming one
step further and explore the exciting world of handling multiple
connections. Handling multiple connections allows our server to respond
to multiple clients simultaneously, enabling concurrent communication
and efficient use of resources.
In this section, we’ll learn how to modify our server to accommodate
multiple client connections. We’ll use concepts like concurrency and
threading to achieve this. Additionally, we’ll introduce the ability to
broadcast messages to all connected clients, creating a more dynamic and
interactive server.
Without further ado, let’s delve into the step-by-step guide for
handling multiple connections with sockets!
Handling Multiple Client Connections
Imagine a scenario where our server needs to serve multiple clients
simultaneously. For example, a chat server that allows multiple users to
connect and exchange messages or a web server that handles requests
from multiple clients at the same time. This is where the power of
handling multiple connections comes into play.
To handle multiple connections, we’ll utilize the concept of concurrency or
multithreading. Concurrency allows us to execute multiple tasks
simultaneously, while multithreading enables different parts of a program
run at the same time.
In the upcoming step-by-step guide, we’ll modify our existing server code
to handle multiple client connections. Each connected client will be
assigned a separate thread, allowing independent communication with
each client. This approach ensures that the server can handle multiple
clients concurrency, without blocking the execution of other tasks.
Let’s dive into the code and explore how we can achieve this!
16 © All rights reserved to Lior Shalom
בס"ד
Step 1: Importing Modules & Server Setup
First, let’s start by importing the necessary modules in your ‘server.py’
file. Additionally for the ‘socket’ module, import the ‘threading’ module
that provides functionality for working with threads. Threads allows us to
achieve concurrency and handle multiple client connections concurrently.
After importing the necessary modules, you have already set up the
server socket in the previous section so you don’t need to change
anything. Here it is again for reference:
After you have set up the first building blocks of your server, you're ready
to move on to the next important step.
17 © All rights reserved to Lior Shalom
בס"ד
Step 2: Listening for Incoming Connections
In the previous section you’ve learned how to build a server that can
communicate with a single client. To expand its capabilities and handle
multiple client connections concurrently, you need to make a slight
change to your existing code ‘server.py’.
Change the parameter value of the ‘listen()’ method from ‘1’ to ‘5’:
With this modification, your server is now prepared to handle a maximum
number of ‘5’ client connections concurrently.
There is another option to remove the parameter value in the ‘listen()’
method. By removing the parameter value in the ‘listen()’ method, the
server socket will start listening for incoming connections without setting
a limit on the number of clients that can wait in the queue.
Step 3: Handling Client Communication
Now that our server socket is set up and listening, In this step we will
define a function ‘handle_client()’ that will take the ‘client_socket’
and ‘client_address’ as arguments that will handle the communication
on each individual client. (If you're not familiar with functions, I
recommend you to learn it before you go any further).
The function will look like that:
18 © All rights reserved to Lior Shalom
בס"ד
Let’s break down our function:
- The ‘handle_client’ function takes the ‘client_socket’ and
‘client_address’ as arguments. This function is responsible for
handling communication with an individual client.
- Inside the function, we set up a loop to continuously receive data
from the client using the ‘recv()’ method and decode the received
data into a string format:
- If no data is received, it means the client has disconnected, so we
print a message and break out of the loop. Otherwise, we print the
received message along with the client’s address:
- Next, we prepare a response message and send it back to the client
using the ‘send()’ method after encoding it into bytes.
- Finally, we close the client socket using the ‘close()’ method.
In summary, This function represents the logic to handle communication
with a specific client. Our function receives data from the client, prints the
received message along with the client’s address, sends a response back
to the client and finally closes the client socket. In the next part we will
bring it all together and accept incoming connections from multiple
clients.
19 © All rights reserved to Lior Shalom
בס"ד
Step 4: Accept Connections & Client Threads Creation
Before you delve into the details of handling multiple clients in your server
socket, let’s briefly summarize what you have accomplished so far. In the
previous step you have defined the ‘handle_client’ function, which plays
a crucial role in handling each client connection separately. This function
allows your server socket to receive data from clients and send responses
back.
In this step, we will introduce the concept of accepting connections and
creating client threads.
Let’s take a closer look at the following code snippet, which exemplifies
this process: (add this code to your ‘server.py’ file.)
In this code, we enter an infinite loop that continuously accepts incoming
client connections. Each time a new client connects, we create a new
thread specifically dedicated to handling that client’s communication. This
enables us to serve multiple clients concurrently without blocking the
main server socket.
Now, let’s break down the code to understand how it contributes to the
overall functionality of our server:
- The line ‘connect_details = (client_socket, client_address)’
creates a tuple that contains the client socket object and client
address. This tuple will be passed as arguments to the
‘hundle_client’ function.
- Then we create a new thread called ‘client_thread’ that will execute
the ‘hundle_client’ function. The ‘target’ parameter specifies the
function to be executed in the new thread, and the ‘args’ parameter
provides the arguments to be passed to the function:
- Finally, ‘client_thread.start()’ starts the execution of the new
thread, which will call the ‘handle_client’ function and handle
communication with the connected client independently.
This code enables concurrent client communication by using separate
threads for each client, ensuring uninterrupted interactions.
20 © All rights reserved to Lior Shalom
בס"ד
Thank You For Reading! 🎉
You’ve reached the end of our Python Socket Programming guide!
Whether you're just starting or you've been around the block with
coding, we hope this guide has been super helpful.
Think of sockets like a special language computers use to talk to
each other. You've learned how to make these 'conversation points,'
send messages, and get replies, all using Python. But guess what?
There's more to explore!
Keep on trying new things. Build cool stuff like chat systems, video
streaming apps, or anything that uses networking. Face challenges,
fix problems, and enjoy making cool connections in the digital world.
Now armed with this knowledge, it's time for your own adventures
with sockets. Create, learn, and share! Connect with others, work
together, and keep growing in the world of Python networking.
Stay curious, stay inspired, and keep coding!
For updates, to connect, or if you need help:
LinkedIn: https://www.linkedin.com/in/-lior-shalom/
Email: [email protected]
Thanks for joining me on this fun journey through Python
Socket Programming. Wishing you success in all your coding
adventures!
Best regards - Lior Shalom
Happy coding! 🐍✨
21 © All rights reserved to Lior Shalom