In the previous topic, we learned how a client socket connects to an address, sends data, and receives the response. But a client is only half of the story: someone has to be on the other end, waiting for the connection. This "someone" is a server socket. In this topic, we'll learn how to create a server in Python, make it wait for clients and talk to them.
The server side of the connection
Let's go back to our information office analogy. The client socket was a visitor who comes to the office and approaches one of the tables. Now let's look at the same situation from the other side. The server socket is a consultant who has to do several things before the first visitor arrives:
Choose a table and sit down at it (bind the socket to an address and a port);
Announce that the table is open for visitors (listen for incoming connections);
Wait until somebody comes up (accept a connection);
Talk to the visitor: listen to the question and give the answer (receive and send the data);
Say goodbye to the visitor (close the connection) and be ready for the next one.
As you can see, a server socket has a few more stages than a client socket. Let's go through them one by one.
Preparing a server socket
A server socket is created in the same way as a client socket, with the socket() function. This time, however, we will pass two arguments explicitly:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)Here, socket.AF_INET means that the socket will use IPv4 addresses (like 127.0.0.1), and socket.SOCK_STREAM means that the socket will use TCP — a protocol that delivers data reliably and in the correct order. These two values are the defaults, so when we wrote socket.socket() in the client example, we actually created exactly the same kind of socket.
When a server stops, the operating system may keep its address and port busy for a short time. If you try to restart the server immediately, you may get the OSError: [Errno 98] Address already in use error. To avoid it during development, we can ask the system to allow reusing the address with the setsockopt() method:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)This line is optional, but it is a good habit to include it in your servers.
A client socket receives an address in order to connect to it. A server socket, in contrast, has to bind to an address, that is, to claim it for itself. This is done with the bind() method, which takes the same kind of tuple as the connect() method: a hostname and a port.
hostname = '127.0.0.1'
port = 9090
server_socket.bind((hostname, port))The hostname you choose determines who can reach your server:
'127.0.0.1'(or'localhost') — only the programs running on your own computer will be able to connect;'0.0.0.0'— the server will accept connections on all network interfaces of your computer, so clients from other devices in the network can connect as well.
Remember that the port must be free: if another program is already bound to it, bind() will raise an error. Also, as we mentioned earlier, it's better to choose ports starting from 1024 as ports from 0 to 1023 are system ports.
After binding, the socket is still not ready to accept clients. We have to switch it to the listening mode with the listen() method:
server_socket.listen(5)The argument is called a backlog. It is the maximum number of connections that can wait in the queue while the server is busy with another client. If you call listen() without an argument, Python will choose a reasonable value itself.
Working with clients
Now the server is open for visitors. To wait for the next client, we use the accept() method:
client_socket, client_address = server_socket.accept()This method is blocking: the program stops on this line and waits until some client connects. When this happens, accept() returns a tuple of two elements:
a new socket that is connected to this particular client — we will use it to communicate with the client;
the address of the client, which is also a tuple of a hostname and a port.
Pay attention that there are two different sockets now. The original server_socket continues to listen for new clients, while client_socket is used for the conversation with the one client who has just connected.
The new client_socket works just like the client socket from the previous topic. We use recv() to receive the data and send() to send the response, and, as always, the data must be in bytes:
data = client_socket.recv(1024)
print(data.decode())
client_socket.send('Hello, client!'.encode())There is also the sendall() method. While send() may, in some cases, send only a part of the data and return the number of bytes it managed to send, sendall() keeps sending until all the data is transmitted (or an error occurs). That is why sendall() is often a safer choice:
client_socket.sendall('Hello, client!'.encode())How does the server know that the client has finished the conversation and closed the connection? In this case, recv() returns an empty byte string b''. This is the same situation we described in the previous topic: an empty message appears only when the connection is closed. So, a server can check the result of recv() and stop the conversation if there is no data:
data = client_socket.recv(1024)
if not data:
print('The client has disconnected')When the conversation is over, we close the client socket with close(). If we don't need the server anymore, we close the server socket as well:
client_socket.close()
server_socket.close()Overview
Let's put all the stages together. Here is a simple server that receives a message from a client, prints it, and sends back a reply:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# binding and listening
server_socket.bind(('127.0.0.1', 9090))
server_socket.listen(5)
print('Server is waiting for a client...')
# waiting for a client to connect
client_socket, client_address = server_socket.accept()
print('Connected by', client_address)
# receiving the request
data = client_socket.recv(1024)
message = data.decode()
print('Received:', message)
# sending the response
response = 'Follow the white rabbit'
client_socket.sendall(response.encode())
# closing the connections
client_socket.close()
server_socket.close()Now run this server first, and then run the client from the previous topic in another terminal window. The client will send Wake up, Neo, and the server will answer with Follow the white rabbit. Notice that the order matters: the server must be started before the client; otherwise, the client gets ConnectionRefusedError.
Serving several clients
The server above serves exactly one client and then stops. Usually a server should keep working and serve the clients one after another. For that, we put accept() into a loop. Inside it, we use another loop to talk to the connected client until it disconnects:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 9090))
server_socket.listen(5)
while True:
client_socket, client_address = server_socket.accept()
print('Connected by', client_address)
while True:
data = client_socket.recv(1024)
if not data:
break # the client has closed the connection
# sending the same data back
client_socket.sendall(data)
client_socket.close()This is a classic echo server: it sends back everything it receives. The outer loop is responsible for accepting new clients, while the inner one handles the data exchange with the current client. When recv() returns an empty string, the inner loop ends, the client socket is closed, and the server goes back to wait for the next client.
Note that this server handles clients strictly one at a time: while it is busy with one client, the others wait in the queue defined by the backlog. Serving many clients simultaneously requires additional tools, such as threads, but this is a story for another topic.
with ... as
Just like the client sockets, server sockets and the sockets returned by accept() can be used as context managers. This way, we don't have to worry about calling close():
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 9090))
server_socket.listen(5)
client_socket, client_address = server_socket.accept()
with client_socket:
data = client_socket.recv(1024)
client_socket.sendall(data)With this setup, we have a server that can serve many clients. However, if you look closely this server never stops. We mentioned that accept() is a blocking method, which means the program waits until a client connects, and so is recv() a blocking method. It will also block until some data is available. So this server will always wait for a client to connect and for the client to send data without stopping for as long as the program isn't manually closed.
There is no need to worry though as Python gives us a handy method to stop the server. But this is something we'll look into with greater detail in a future topic.
Conclusion
To conclude, let's go over the main points of the topic:
a server socket is created with
socket.socket(socket.AF_INET, socket.SOCK_STREAM), andSO_REUSEADDRhelps to avoid the "Address already in use" error when restarting the server;bind()attaches the server socket to an address (a hostname and a port), where'127.0.0.1'makes the server available only locally and'0.0.0.0'makes it available on all network interfaces;listen()turns the socket into a listening one and sets the size of the waiting queue;accept()blocks until a client connects and returns a new socket for communication with this client and the client's address;data is exchanged through the client socket with
recv()andsend()/sendall(), and it must be in bytes;an empty result of
recv()means that the client has closed the connection;to serve clients continuously, the server accepts connections in a loop and closes each client socket when the conversation is over.