Introduction to network programming with Python #0002 TCP (2020 Tutorial)
Table of Contents
1. Introduction to TCP
2. TCP client
3. TCP server
4. TCP application file download
1. Introduction to TCP
You have probably heard the TCP/IP protocol suite. TCP stands for Transmission Control Protocol; IP stands for Internet Protocol. TCP/IP provides apps a way to deliver an ordered and error-checked stream of information packets over the internet. While UDP provides apps a way to deliver a fast stream of information packets over the internet. UDP is faster than TCP, but it may lose data during data transmission.
Both TCP and UDP are built on top of IP. No matter which protocol is used, data packets are delivered to the IP address. Packets sent to the routers and then to the hosts.
TCP and UDP are not the only protocols that built on top of IP protocol, but they are the most widely used.
For networking, TCP is the most widely used protocol across the internet. When you download a movie from a Bitorrent webserver, your Bitorrent client receives the data packets from the server and sends back a response telling the server that packet received successfully.
TCP guarantees a reliable transmission of data packets. TCP orders every packet, so no packet is lost or corrupt during transmission; and also it error checks by having the recipient send back a response saying it has received the packet correctly.
As for UDP, it does not do the error checking work; data lost during transit are just lost. Because back and forth transmission of data reduces speed and increase the latency, so UDP is used when speed is at priority and error-checking is unnecessary, such as live broadcast, USB data transmission, online gaming.
2. TCP client
When use TCP, we clearly separate the client and server. A client is just like your Bitorrent client on your computer, whereas a server is the Bitorrent server from that we retrieve data. But for UDP in the previous topic, we didn't mention anything about the UDP client and UDP server. When you receive data, it is a UDP client and when you send data, it is a UDP server.
2.1 How to write a TCP client
To create a TCP client, you have the four basic steps:
- create the TCP socket
- establish the connection
- send and receive data packets
- close the socket and connection
# tcp_basic_client.py import socket def main(): # create socket object tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # bind local address local_ip = '' # leave it empty local_port = 7878 tcp_socket.bind((local_ip, local_port)) # destination IP and port dest_ip = input('Enter remote IP: ') dest_port = int(input('Enter remote port: ')) # establish the connection tcp_socket.connect((dest_ip, dest_port)) # send data send_data = input('Enter your message: ') tcp_socket.send(send_data.encode('utf-8')) # close the socket and connection tcp_socket.close() if __name__ == '__main__': main()
As always, we import socket. We use if __name__ == "__main__" in this program, if you don't know its usage, please check my blog article.
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
local_ip = '' # leave it empty
local_port = 7878
tcp_socket.bind((local_ip, local_port))
Then we bind the tcp_socket to a local address, if we do not bind the local address, a random port will be picked for this socket. Usually, we do not bind, you could omit this step.
dest_ip = input('Enter remote IP: ')
dest_port = int(input('Enter remote port: '))
Next, we ask the user to enter destination IP and port.
tcp_socket.connect((dest_ip, dest_port))
Then, before we want to send message to the server, we need to establish the connection to the server first by using socket.connect() method which takes the destination address as a tuple.
send_data = input('Enter your message: ')
tcp_socket.send(send_data.encode('utf-8'))
After establishing the connection, we just need to send the data whatever we want. Remember, we could only send bytes instead of pure string.
Finnaly, we close the socket.
Before we run the program, we open our Net Assistant and start a TCP server on port 8080.
Then we run our program, type in TCP server's IP and port number. Once you have done that, connection is established if there's no errors happend.
Next, we just need to enter the message and return. Ther server will receive the message.
Let's move on to TCP server
3. TCP server
As for TCP server, it is more complex than a TCP client. But it also has a basic structure.
- create a socket object
- bind socket to local address (important)
- turn the socket into listening mode
- waiting for the connection from clients
- receive data from clients and response
- close connection and socket
# tcp_basic_server import socket def main(): # create a socket object tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # bind to a local address local_addr = ('', 7878) tcp_server_socket.bind(local_addr) # turn socket into listening mode tcp_server_socket.listen(128) # waiting for incoming connections from client client_socket, client_addr = tcp_server_socket.accept() # receive data from the client using client_socket data_recv = client_socket.recv(1024) print(data_recv) # respond to client saying ok indicating message received client_socket.send(b'OK') # close both sockets client_socket.close() tcp_server_socket.close() if __name__ == "__main__": main()
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind to a local address
local_addr = ('', 7878)
tcp_server_socket.bind(local_addr)
We first create a tcp socket and bind to a local address which is very import for TCP servers. This step could not be omitted otherwise client will not find the correct server.
tcp_server_socket.listen(128)
Then we put our socket into listening mode by invoking socket.listen() method. Remember this listening socket could only listen the incoming connection and it is not responsible for receiving from and sending data to clients.
client_socket, client_addr = tcp_server_socket.accept()
Next, our listening socket waits for incoming client connections. After running the code, our program or server will block here waiting for incoming connections. Once a client connected to the server, socket.accept() method returns a tuple consisting the client socket object which we use to receive data and send data back to client and the client's address.
data_recv = client_socket.recv(1024)
print(data_recv)
Then, we invoke client_socket.recvfrom() method to receive data and print the data.
client_socket.send(b'OK')
client_socket.close()
tcp_server_socket.close()
Finnaly, we close both sockets.
After running this program (we must run this server first), we lauch the Net Assistant, open a TCP client connected to our server.
Our server received the message 'hello' and send back 'OK' to the client.
Our basic TCP server could only accept one client and quit, let's build a TCP sever that could serve multiple clients one by one.
# tcp_basic_server_serving_multiple_clients import socket def main(): # create a socket object tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # bind to a local address local_addr = ('', 7878) tcp_server_socket.bind(local_addr) # turn socket into listening mode tcp_server_socket.listen(128) while True: # waiting for incoming connections from client print('Waiting for clients:') client_socket, client_addr = tcp_server_socket.accept() print(str(client_addr)+' connected') # receive data from the client using client_socket data_recv = client_socket.recv(1024) print(data_recv) # respond to client saying ok indicating message received client_socket.send(b'OK') # close both sockets client_socket.close() tcp_server_socket.close() if __name__ == "__main__": main()
We just added a while True loop, the server will constantly listen to incoming connections and serve each client one at a time.
Then run this program first, lauch two Net Assistant TCP clients.
Both clients will be connected to our server, but only the first one will be served. After serving the first one, the second client is served.
First client on port 50523 will be served first.
Then second client on port 50524 will be served next.
The program above could only serve one client once, namely our client could only send message to the server once. So we could revise the program and it could serve our client multiple times until we close the client.
# tcp_basic_server_serving_multiple_clients_revised import socket def main(): # create a socket object tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # bind to a local address local_addr = ('', 7878) tcp_server_socket.bind(local_addr) # turn socket into listening mode tcp_server_socket.listen(128) while True: # waiting for incoming connections from client print('Waiting for clients:') client_socket, client_addr = tcp_server_socket.accept() print(str(client_addr)+' connected') while True: # receive data from the client using client_socket data_recv = client_socket.recv(1024) if data_recv: print(data_recv) # respond to client saying ok indicating message received client_socket.send(b'OK') else:
print('Client closed!')
break
# close both sockets
client_socket.close()
tcp_server_socket.close()
if __name__ == "__main__":
main()
while True:
# receive data from the client using client_socket
data_recv = client_socket.recv(1024)
if data_recv:
print(data_recv)
# respond to client saying ok indicating message received
client_socket.send(b'OK')
else:
print('Client closed!')
break
We just added another while True to contain socket.recv() method. If your client send message to the server, it wil receive the message and respond. If your client is closed, data_recv will be None and breaks out of the loop.
Let's run this server first, and then lauch the Net Assistant as a TCP client.
As you could see, our client could constantly send message to the TCP server. Once we close the client, Our server close the connection, waiting for another incoming client.
4. TCP Application File Download
So far we have touched the topics about TCP client and server. Now we could write an TCP client that retrieve a file from a TCP server. Let's write the client first.
# tcp_client_file_downloader import socket def main(): # create a socket object tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # enter server address server_ip = input('Enter server IP: ') server_port = int(input('Enter server port: ')) # connect to the server tcp_client_socket.connect((server_ip, server_port)) # enter file name that you want to retrieve file_name = input('Enter the file name that you want to download: ') # send the file_name to the server tcp_client_socket.send(file_name.encode('utf-8')) # receive file data from the client if any data_recv = tcp_client_socket.recv(1024) if data_recv: with open('new_'+file_name, 'wb') as file: file.write(data_recv) # close the socket tcp_client_socket.close() if __name__ == '__main__': main()
file_name = input('Enter the file name that you want to download: ')
As always, we create a TCP socket, this time we ask the user to type in the name of the file to be downloaded.
tcp_client_socket.send(file_name.encode('utf-8'))
Then send the file name to the server. After that our client will wait for the response from the server by invoking socket.recv() method.
data_recv = tcp_client_socket.recv(1024)
if data_recv:
with open('new_'+file_name, 'wb') as file:
file.write(data_recv)
# close the socket
tcp_client_socket.close()
If there is data sent by the server, we create a file and write all the data into the file. Finally close the socket.
Let's test this client using Net Assistant.
We start a TCP server and then run the client; type in the server ip and port number; then our client is connected to the server.
We enter the file name we want to download and then our server will receive the file name. For our Net Assistant is not designed to fetch the file and send it back to the client, we just put some text 'this is the content of info.txt' into the response and click send.
Then, our client receive the data sent by the server and create a file named 'new_info.txt' and write all the data into it.
If there is nothing wrong with the client above, now let's write another TCP server that could retrieve the file on the server's local machine and send it back to the client.
# tcp_server_file_downloader import socket def open_file_and_send_to_client(client_socket, client_addr): # receive data from client_file_name = client_socket.recv(1024).decode('utf-8') # make sure file exists, otherwise return nothing file_content = None try: file = open(client_file_name, 'rb') file_content = file.read() except Exception as exc: print('Error on opening file: ', client_file_name) if file_content: client_socket.send(file_content) def main(): # create a socket object tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # bind local address local_ip = '' local_port = 7878 tcp_server_socket.bind((local_ip, local_port)) # put the tcp_client_socket into listening mode tcp_server_socket.listen(128) while True: # wait for incoming connections print('Waiting for clients...') client_socket, client_addr = tcp_server_socket.accept() print(str(client_addr)+' connected...') # open the file and send back to client open_file_and_send_to_client(client_socket, client_addr) # close the client socket client_socket.close() # close the listening mode socket tcp_server_socket.close() if __name__ == '__main__': main()
As always for writing a TCP server, we must bind it to a local address; put the TCP socket into listening mode.
# bind local address
local_ip = ''
local_port = 7878
tcp_server_socket.bind((local_ip, local_port))
# put the tcp_client_socket into listening mode
tcp_server_socket.listen(128)
Then we constantly accept connections from clients and handle all the requests one by one.
client_socket, client_addr = tcp_server_socket.accept()
The core of this server is the open_file_and_send_to_client() function.
def open_file_and_send_to_client(client_socket, client_addr):
# receive data from
client_file_name = client_socket.recv(1024).decode('utf-8')
# make sure file exists, otherwise return nothing
file_content = None
try:
file = open(client_file_name, 'rb')
file_content = file.read()
except Exception as exc:
print('Error on opening file: ', client_file_name)
if file_content:
client_socket.send(file_content)
It receive the filename from the client and search the file in the current working directory. If anything go wrong, exception will be raised, nothing will be sent back to the client.
client_socket.close()
Then we close the client_socket object.
Let's test our TCP client and server together. Run the server first and then run the client.
Then we type the server IP and port in client terminal and server and client are connected.
Next, we type in the file name we want to download from the server. If server could find the file, it will send its content back to the client; after that server close the connection waiting for next connection.
If server could not find the file, errors will be printed on server's terminal, and nothing will be sent. Client will not create the file.
In summary, we could use both UDP and TCP to transmitt data over networks. UDP is fast but not as reliable as TCP. For most web applications, we always use TCP.
TCP server and client takes their own specific role in transmitting data over networks. TCP server must bind a local address while a TCP client may or may not bind a local port. Pay attention to the socket that receive and send data on the server side; it is the socket returned by the accept() method not the server socket itself.
Comments
Post a Comment