###### 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.")
```
- 執行結果

### 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.")
```
- 執行結果:

## 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。
- 操作前

- 使用Server/Client傳送檔案


- 操作後




