In this tutorial we'll learn how to code a TCP and UDP client and server
in python using the socket
module in python.
TCP or Transmission Control Protocol is a connection-oriented protocol
which provides a reliable connection between the server and the client. While
UDP or User Datagram Protocol is termed as a connectionless protocol.
TCP Server
In order to make a TCP server the following process needs to be involved:
- Create a socket
- Bind the socket
- Make the socket listen for connections
- Accept a connection
- Send/Recieve data
- Close the socket
Let's first import the socket module and declare some varaibles
# Our host, 0.0.0.0 means use any
# interface available on our device
host = '0.0.0.0'
# The port number to listen on
port = 8080
Next we create the server socket
# Create the server socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("[$] Socket created successfully!")
The socket() method of socket is use for creating the socket.
The socket.AF_INET is the address family, it means we want our socket to use IPv4 address,
if you want to use IPv6 address, use socket.AF_INET6
The socket.SOCK_STREAM is the socket type, it means we want to use TCP.
Next we bind our socket.
# bind the socket
try:
server_socket.bind((host, port))
except socket.error as err:
print(f"[-] Bind failed, {str(err)}")
exit()
print("[$] Bind complete")
The bind() method is use to bind the socket, we use it to
associate host/IP address and port number.
Next we listen for connections.
server_socket.listen(10)
print(f"[$] Server started, listening on {host}:{port}")
As you can see, the listen() method is use to put a
socket in a listening mode.
Next we accept any incoming connection and recv some data.
client_socket, client_addr = server_socket.accept()
print(f"[$] Got a new connection from {client_addr[0]}:{client_addr[1]}")
# receive data from client
# maximum buffer size
buffer_size = 4096
data = client_socket.recv(4096).decode()
print(f"[$] Data received: {data}")
The accept() method is use for accepting connections.
The accept() method returns the client socket and
the client address as a tuple.
Next we used the recv() method on the client_socket
to receive data from the client, then we'll use the decode() method
to decode the data.
It is very important that you always encode data before sending it out on the network.
And decode it after receiving it from the network.
Since this is just an echo server, we'll just convert
the data received from the client then send it back to the client.
After that we'll just close the connection.
client_socket.send(data.upper().encode())
server_socket.close()
TCP Client
Steps of making a TCP client
- Create a socket
- Connect to another socket
- Send/Recieve data
- Close the socket
Here's our TCP client code:
# host to connect to
host = '127.0.0.1'
# port number to connect on
port = 8080
print('[$] Creating socket...')
# Create the client socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('[$] Socket created successfully!')
print(f'[$] Connecting {host}:{port}...')
# Connect to the server
try:
client_socket.connect((host, port))
except socket.error as err:
print(f'[-] Connection to server failed, {err}')
exit()
print('[$] Connected')
# send some data to the server
data = 'Hello Server'
client_socket.send(data.encode())
# receive data from the server
buffer_size = 4096
data_received = client_socket.recv(buffer_size)
print(f'[$] Data received: {data_received.decode()}')
By now you should be familiar with all the methods we used in the client code,
well except for connect() which you can tell what it does,
it just connect our socket to the specified host/IP on a port.
UDP Server & Client
In order to code a UDP server or client we don't have to write
everything we just have to make some small modification to our
TCP server and client code.
UDP Server
Steps:
- Create a socket
- Bind the socket
- Send/Recieve data
- Close the socket
You can see in the steps we did not include 'listen for connection' and 'accept connection
because UDP is a connectionless protocol, connection is needed not in order
to send/receive data.
Let's start coding the server now.
# Our host, 0.0.0.0 means use any
# interface available on our device
host = "0.0.0.0"
# The port number to listen on
port = 8080
print("[$] Creating socket...")
# Create the server socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print("[$] Socket created successfully!")
print("[$] Binding socket...")
# bind the socket
try:
server_socket.bind((host, port))
except socket.error as err:
print(f"[-] Bind failed, {str(err)}")
exit()
print("[$] Bind complete")
Note that this time we used socket.SOCK_DGRAM instead of socket.SOCK_STREAM
because we're no longer gonna be using TCP so we used socket.SOCK_DGRAM which
idicates the socket type to be Datagram(or UDP).
Next we'll receive some data from a client then echo back
the same data to the client.
buffer_size = 4096
data = server_socket.recvfrom(buffer_size)
message = data[0].decode()
address = data[1]
print(f"[$] Data received: {message} from {address[0]}:{address[1]}")
# send back the same message to the server
server_socket.sendto(message.upper().encode(), address)
server_socket.close()
Since this is a UDP socket, so we'll not be using the recv()
function to recv data, we'll be using the recvfrom()
function.
The recvfrom() function is used for receiving data
from any client. It returns a 2 tuple, the first one is the actual
data from the client, then the second one is also a tuple of the client
address, which the first one is the client's IP address and the second is the client's
port number.
we also used the sendto() function to send data to a specific address.
So here we're echoing back the data to the client that sends us the data
from the first place.
UDP Client
Steps:
- Create a socket
- Send/Recieve data
- Close the socket
In UDP, we don;t actually have to connect to the server in order
to send data that's why in the list of our steps there's no 'connect to the server'.
Client code:
# host to connect to
host = "127.0.0.1"
# port number to connect on
port = 8080
print("[$] Creating socket...")
# Create the client socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print("[$] Socket created successfully!")
data = "Hello Server"
client_socket.sendto(data.encode(), ((host, port)))
# receive data from the server
buffer_size = 4096
data = client_socket.recvfrom(buffer_size)
message = data[0].decode()
address = data[1]
print(f"[$] Data received: {message} from {address[0]}:{address[1]}")
client_socket.close()