Opening a TCP Socket
Create a socket, bind it to a port and start listening.
On the last page, we saw that Python’s HTTP server is just a process waiting for connections, and that the operating system does the TCP work for it.
Now we are going to build a TCP socket that can listen for connections. A TCP socket is an endpoint that allows two programs to communicate over a network. Let's build functionality for this TCP socket step by step. Our server needs to:
- Create a socket.
- Bind it to a port.
- Listen for incoming connections.
So go back to the bytestack folder from before and create a file for the server:
touch server.pyIf the python3 -m http.server command from before is still running, stop it with Ctrl+C first. Only one program can listen on a specific port at a time, and we are about to use port 8000 ourselves.
Create the socket
Python provides the built-in socket module. A socket is the object our program uses to talk to the network. The operating system owns it, which gives us access to the networking functionality.
Let's start with the simplest possible program, open the server.py file that we just created in your preferred text editor:
import socket
server = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
print("TCP socket created")Save it and run python3 server.py. That's it, we created a TCP socket, although you will only get the print statement in the output.
Before we modify our code, let's understand the two arguments we passed to socket.socket():
AF_INETmeans the address family is IPv4, so addresses look like127.0.0.1.SOCK_STREAMmeans a TCP stream: the reliable, ordered byte stream from the last page.
At this point, we've only created a socket object. We haven't assigned it a port, started listening for connections, or accepted any requests. Nothing has touched the network yet.
Let's fix this.
Bind the socket to a port
A server needs a local address and port where clients can reach it. We can assign our socket to localhost on port 8000 using the bind() method.
Open server.py file and modify our code:
import socket
HOST = "127.0.0.1"
PORT = 8000
server = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
server.bind((HOST, PORT))
print(f"Socket bound to {HOST}:{PORT}")Note the double parentheses: bind takes one argument, which is a (host, port) pair.
Run it with python3 server.py. If the port is available, you will see the print statement as the output. Otherwise, there is going to be an error saying that the address is already in use.
bind((HOST, PORT)) asks the operating system to reserve that local endpoint for this socket. Only one active listener can normally own the same address and port at a time.
The port is just a number we pick. Ports below 1024 are reserved on many systems and need administrator rights, so we use 8000.
Notice that we haven't started listening for connections yet. Binding a socket reserves its local endpoint, but it doesn't make the socket ready to accept incoming TCP connections.
Begin listening
Now that our socket is bound to a port, we can tell the operating system that it should listen for incoming TCP connections.
We do this by calling listen():
import socket
HOST = "127.0.0.1"
PORT = 8000
server = socket.socket(
socket.AF_INET,
socket.SOCK_STREAM
)
server.bind((HOST, PORT))
server.listen()
print(f"Server is listening on {HOST}:{PORT}")Run the code using python3 server.py.
bind reserved the address, and listen switches the socket on. From this moment the operating system will complete the TCP handshake for anyone who connects to 127.0.0.1:8000, and hold those connections in a queue until our program asks for them.
The program exits right away, so there is nothing to connect to yet. We’ll keep it alive in a moment.
However, our program still doesn't accept those connections or communicate with clients. That's the step we are going to learn in next page.
Putting it all together
Here is the whole file. We wrap the socket code in main() function using with block so it is closed automatically when the program ends.
import socket
HOST = "127.0.0.1"
PORT = 8000
def main() -> None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind((HOST, PORT))
server.listen()
print(f"listening on http://{HOST}:{PORT}")
input("Press Enter to stop the server...\n")
if __name__ == "__main__":
main()The input(...) line is only a placeholder that keeps the program alive so we can poke at it. If it were missing, the program would reach the end and exit, taking the socket with it. On the next page we replace it with a loop that accepts connections.
Run the program, then open a second terminal:
curl -v --max-time 2 http://127.0.0.1:8000/The TCP connection can reach the socket, but curl times out because our process never accepts it and never sends an HTTP response.
Press Enter to stop the server.py program.