###### tags: `network` `hw` `thu` # Computer Network Project ## 1. TCP - TCP比較單純,因為他的下層自己就有實作RDT的協定了。 - 只要開檔後使用迴圈,把檔案一點一點讀出和使用socket送出即可。 ### a. Client - 我們先使用外部指令下載測試圖檔 ```bash wget -O test.jpg https://picsum.photos/200 ``` - 接下來使用變數設定好傳送的目標,以及可顯示出圖檔的函式。 ```python= # Import dependencies for TCP/UDP import socket HOST = '140.128.101.141' PORT = 7000 server_addr = (HOST, PORT) # Setup renderImage function import cv2 from matplotlib import pyplot as plt def renderImage(imagePath): imageObject = cv2.imread(imagePath, -1) plt.imshow(imageObject) plt.axis("off") plt.show() ``` - 接下來就可以讀檔並使用Python的Socket函式庫進行TCP的發送 ```python= def client(): print("Client: Client starting...") print("Client: Sending the following image:") renderImage("test.jpg") # Create connection print("Client: Sending file to: %s:%s" % (HOST, PORT)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(server_addr) # Load file and send testFile = open('test.jpg','rb') counter = 0 print("Client: Sending...", counter) buf = testFile.read(1024) while (buf): counter += 1 print("Client: Sending...", counter) s.send(buf) buf = testFile.read(1024) # Stopping the client. print("Client: Image sent with ", counter, " iterations.") s.shutdown(socket.SHUT_WR) s.close() print("Client: Connection closed. Client stopped.") ``` - 執行結果 ![](https://i.imgur.com/ZTB1KVD.png) ### b. Server - Server的前面也進行了類似的設定,好讓知道Server要在哪個address聽取進來的連線。 ```python= # Import dependencies for TCP/UDP import socket HOST = '140.128.101.141' PORT = 7000 server_addr = (HOST, PORT) # Setup renderImage function import cv2 from matplotlib import pyplot as plt def renderImage(imagePath): imageObject = cv2.imread(imagePath, -1) plt.imshow(imageObject) plt.axis("off") plt.show() ``` - 接下來跟Client很相近,等到連線後只要開檔再分次接收並寫入就好。 ```python= def server(): print("Server: Starting server...") # Start Listening s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(server_addr) s.listen(5) print('Server: Server started at: %s:%s' % (HOST, PORT)) print('Server: Waiting for connection...') # Handle incoming data while True: # Establish connection with client. connection, addr = s.accept() print("Server: Got connection from", addr) # Receive the file outputFile = open('received.jpg','wb') counter = 0 print("Server: Receiving...", counter) buf = connection.recv(1024) while (buf): counter+= 1 print("Server: Receiving...", counter) outputFile.write(buf) buf = connection.recv(1024) outputFile.close() print("Server: Done Receiving") # Close the connection connection.close() # Render the Image print("Server: Image received.") # Break from loop break # Stop listening s.close() print("Server: Server Stopped.") ``` - 執行結果: ![](https://i.imgur.com/tUYp1JX.png) ## 2. UDP ### a. Library - 因為UDP的功能較為複雜,因此我們使用了另外兩個檔案來存放我們的主要功能 - 其中一個是`Packet.py`,用來存放Packet相關的函式,以將不同的邏輯分開。 ```python! import pickle class Packet: seq = None binary_data = None def __init__(self, seq_input, data_input): self.seq = seq_input self.binary_data = data_input def __str__(self): return "Packet: SEQ=" + str(self.seq) + ", DATA=" + str(self.binary_data) # Utility Methods def encode(self): packet = (self.seq, self.binary_data) packet_bytes = pickle.dumps(packet) return packet_bytes @staticmethod def decode(binary_packet): packet = pickle.loads(binary_packet) seq, binary_data = packet return Packet(seq, binary_data) # Userspace methods def send(self, assigned_socket, addr): packet = self.encode() assigned_socket.sendto(packet, addr) @staticmethod def receive(assigned_socket): binary_packet = assigned_socket.recvfrom(1024 * 2)[0] return Packet.decode(binary_packet) ``` - 另外一個叫`RDT_LIBRARY.py`的檔案來存放我們會在UDP的可靠傳輸中使用的的函式: ```python! import socket import threading import time from tqdm import tqdm from Packet import Packet class RDTUtility: # General variables timeout = None server_addr = None server_socket = None client_addr = None sequence_number = None client_socket = None # GBN variables window_size = None base_ptr = None failed = None @classmethod def __init__(cls, server_addr, client_addr): cls.server_addr = server_addr cls.client_addr = client_addr cls.server_socket = cls.create_socket() cls.client_socket = cls.create_socket() cls.sequence_number = 0 cls.timeout = 5 # Set timeout value in seconds cls.window_size = 100 @staticmethod def create_socket(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) return sock @classmethod def send_ack(cls, addr, seq): ack_packet = Packet(seq, "ACK".encode()) ack_packet.send(cls.server_socket, addr) @classmethod def is_expected_seq(cls, seq): return seq == cls.sequence_number def receive_ack(self, list_length): print("RDT: Start listening for ACK packets...") while self.base_ptr < list_length: try: self.client_socket.settimeout(self.timeout) ack_packet = Packet.receive(self.client_socket) if ack_packet.binary_data.decode() == "ACK": print("RDT: Correct ACK SEQ received, shifting the window...") self.sequence_number = ack_packet.seq self.base_ptr = ack_packet.seq else: print("RDT: Incorrect ACK SEQ received,", ack_packet.seq, ", resending the window...") time.sleep(1) break # Set flags to resend the window self.failed = True except TimeoutError: print("RDT: Timeout occurred, resending the window...") self.failed = True continue except UnicodeDecodeError: pass return # Userspace methods def rdt_send(self, packets_list): list_length = len(packets_list) self.base_ptr = 0 self.failed = True # Start listening for ACK packets ack_thread = threading.Thread(target=self.receive_ack, args=(list_length,)) ack_thread.start() # Send packets in the window on request while self.base_ptr < list_length-1: if self.failed: self.failed = False # Get current window local_base_ptr = self.base_ptr print("RDT: Sending the packets with SEQ=", local_base_ptr, ", to ", min(local_base_ptr + self.window_size, list_length-1), "...") for i in range(local_base_ptr, local_base_ptr + self.window_size): if i >= list_length: break packets_list[i].send(self.client_socket, self.server_addr) # Wait for the ACK thread to finish ack_thread.join() @classmethod def rdt_receive(cls): while True: print("RDT: Receiving the packet with SEQ=", cls.sequence_number, "...") try: cls.server_socket.settimeout(cls.timeout) packet = Packet.receive(cls.server_socket) if cls.is_expected_seq(packet.seq) and packet.binary_data is not None: print("RDT: Correct SEQ received,", packet.seq, ", saving...") cls.sequence_number += 1 print("RDT: Sending ACK with SEQ=", packet.seq, ", to the client...") cls.send_ack(cls.client_addr, cls.sequence_number - 1) return packet.binary_data else: if packet.binary_data is None: print("RDT: Incorrect data received,", packet.seq, ", resending the last ack...") else: print("RDT: Incorrect SEQ received,", packet.seq, ", resending the last ack...") cls.send_ack(cls.client_addr, cls.sequence_number - 1) except TimeoutError: print("RDT: Timeout occurred, waiting for the packet...") continue @classmethod def start_server(cls): print("Server: Server starting...") cls.server_socket.bind(cls.server_addr) print("Server: Listening at: ", str(cls.server_addr)) print("Server: Waiting for data...") outputFile = open('received.jpg', 'wb') while True: data = cls.rdt_receive() try: if data.decode() == "stop": print("Server: Stop signal received.") break print("Server: Writing to file...") outputFile.write(data) except UnicodeDecodeError: print("Server: Writing to file...") outputFile.write(data) pass except AttributeError: print(data) break print("Server: File closed.") outputFile.close() # Resending ack to make sure the client is stopped print("Server: Stopping the client...") cls.send_ack(cls.client_addr, cls.sequence_number - 1) print("Server: Server stopped.") cls.server_socket.close() def start_client(self): print("Client: Client starting at...", str(self.client_addr)) self.client_socket.bind(self.client_addr) print("Client: Sending the following image: test.jpg") # Read the binary data from file and buffer into packets list testFile = open('test.jpg', 'rb') data_list = [] buf = testFile.read(1024) print("Client: Buffering data...") while buf: data_list.append(buf) buf = testFile.read(1024) # Create packets in packets_list counter = 0 packets_list = [] print("Creating packets...") for data in tqdm(data_list): packets_list.append(Packet(counter, data)) counter += 1 # Attach stop signal and data length packets_list.append(Packet(counter, "stop".encode())) # Send the buffered packets self.rdt_send(packets_list) print("Client: File sent.") testFile.close() print("Client stopped.") ``` ### b. Client - 相較`RDT_LIBRARY.py`之下,客戶端跟Server端都苗條了許多: ```python! from RDT_LIBRARY import RDTUtility # Setup Client server_addr = ('140.128.101.141', 7000) client_addr = ('172.23.8.60', 7000) client = RDTUtility(server_addr, client_addr) # run client client.start_client() ``` ### c. Server ```python! from RDT_LIBRARY import RDTUtility # Setup server server_addr = ('140.128.101.141', 7000) client_addr = ('172.23.8.60', 7000) server = RDTUtility(server_addr, client_addr) # run server server.start_server() ``` ### d. 實作細節 - 我們在這個程式裡實作了下列功能: - 收到DATA->傳ACK的流程 - DATA跟ACK都有sequence_number,如果不對,就重傳上一個正常的資料(含data/ack),讓對方知道從哪裡重新開始。 - 實作timeout重發的功能,目前設定5s。 - 實作GBN,目前Window Size設定100。 - 如果seq不對或是timeout的應對策略都是一樣的。 ### e. 操作流程 - 概要: - 打開Server跟Client,Client就會打開test.jpg檔讀出,並傳給Server。 - Server會將資料存入received.jpg。 - 操作前 ![](https://hackmd.io/_uploads/HkBzp19Ih.png) - 使用Server/Client傳送檔案 ![](https://hackmd.io/_uploads/S1LQG_VU3.png) ![](https://i.imgur.com/AegcUXB.png) - 操作後 ![](https://hackmd.io/_uploads/Bk6BaJ9Uh.png) ![](https://hackmd.io/_uploads/rJStT1cUn.png) ![](https://hackmd.io/_uploads/SkuiIdNIh.png) ![](https://hackmd.io/_uploads/SkVAId4In.png) ![image alt](https://i.imgur.com/Lk4qrKX.png)