[Python] UDP 전송 구현

2023. 10. 19. 03:23네트워크

728x90
반응형

 

 

UDP 전송을 파이썬으로 구현해보았다.

Requester.py(Client)

"""
Name: Changjae Han
Date: Oct 10 2023
Email: chan82@wisc.edu
cslogin: changjae
File: requester.py
"""


import socket
import struct
import argparse
import datetime as dt
import time



"""Request_data
    1. Create the requester and the sender socket
    2. Pack the packet
    3. Send it to the sender
"""
def request_data(req_port, file_option, row, is_first):

    #Create a requester socket for UDP transmission
    requester_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    requester_ip_addr = socket.gethostbyname(socket.gethostname())
    requester_addr_port = (requester_ip_addr, req_port)
    #Bind the socket to the requester's address
    requester_socket.bind(requester_addr_port)
    
    #Create a sender socket for UDP transmission
    sender_ip_addr = socket.gethostbyname(socket.gethostname())
    sender_addr_port = (sender_ip_addr, int(row[3]))


    #Create packet header  
    header_info = struct.pack("!cII", b'R', 0, 0)
    #Combine packet header with payload
    packet = header_info + row[0].encode()
    #Send the request packet to the sender
    requester_socket.sendto(packet, sender_addr_port)
    
    #Processing received data
    process_data(file_option, is_first, requester_socket)
    
    
    
    
"""Process_data
    1. Process the received data by unpacking
    2. Calculate time and bytes
    3. Write the data to the file
"""
def process_data(file_option, is_first, requester_socket):
    
    sum_bytes = 0
    sum_packets = 0
    ptype = {b'D':"DATA", b'E':"END"}
    
    #Check if it's first data
    if is_first:
        file = open(file_option, "w")
    else: 
        file = open(file_option, "a")
    
    
    #When the requester gets data from the sender
    while True:
        check_time = time.time()
        received_packet, (sender_ip, sender_port) = requester_socket.recvfrom(1024)
        wait_time = time.time()
        packet_time = round(wait_time - check_time)
        
        #Classify header and payload
        received_header = received_packet[:9]
        received_payload = received_packet[9:]
        unpacked_header = struct.unpack("!cII", received_header)

        
        #Print out packet information received 
        print(ptype[unpacked_header[0]] + " Packet")
        print("recv time:  ", dt.datetime.now(), sep='')
        print("sender addr:  ", sender_ip, ":", sender_port, sep='')
        print("sequence:  ", socket.ntohl(unpacked_header[1]))
        print("length:  ", unpacked_header[2])
        
        sum_bytes += unpacked_header[2]
        
        #If packet type is DATA
        if unpacked_header[0] == b'D':
            print("payload:  " + str(received_payload[0:4].decode()))
            print("")
            sum_packets += 1
            file.write(received_payload.decode())
        else:#END
            print("payload:  0")
            print("")
            print("Summary")
            print("sender addr:  ", sender_ip, ":", sender_port, sep='')
            print("Total Data packets:  ", sum_packets, sep='')
            print("Total Data bytes:  ", sum_bytes, sep='')
            print("Average packets/second:  ", packet_time, sep='')
            break
        
    file.close()




def main():

    parser = argparse.ArgumentParser(description="requester data")
    parser.add_argument('-p', type=int, metavar='port', help= 'requester port num')
    parser.add_argument('-o', metavar='file_option', help = 'filename')
    args = parser.parse_args()

    is_first = True
    
    tracker_file = open("tracker.txt", "r")
    
    #Tracket_list to handle the order and unrelavant files
    tracker_list = []
    while True:
        cur_line = tracker_file.readline()
        if not cur_line: break
        each_data = cur_line.split(" ")
        each_data[-1] = each_data[-1].strip() #Remove each space in the end of cur_line
        tracker_list.append(each_data)
        
    #Sorted by num not to find the order over the list
    tracker_list = sorted(tracker_list, key=lambda t: t[1])

    print("Requester's print information")
    print("-----------------------------------------------------------------------------")
    
    
    for row in tracker_list:
        if row[0] == args.o:#if it is requested file
            startTime = time.perf_counter()
            request_data(args.p, args.o, row, is_first)
            endTime = time.perf_counter()
            print("Duration of the test:  ", round((endTime-startTime)*1000), " ms", sep='')
            print("")
            is_first = False

 
    print("")
    print("-----------------------------------------------------------------------------")
    print("In addition, a file ", args.o, " will be generated in the directory requester/.", sep='') 
    print("The content should be:")
    print("")
    
    with open(args.o, encoding="UTF8") as data :
        contents = data.read()
        print(contents)
    
    print("")
    print("-----------------------")
    

    

if __name__ == "__main__":
    main()

 

Sender.py(Server)

"""
Name: Changjae Han
Date: Oct 10 2023
Email: chan82@wisc.edu
cslogin: changjae
File: sender.py
"""


import argparse
import socket
import os
import struct
import datetime as dt
import time



"""Read_data
    1. Create the sender socket
    2. Read requested data by unpacking
    3. Pass on to send_data
"""
def read_data(send_port, req_port, rate, seq_no, length):
    
    #Create a sender socket for UDP transmission
    sender_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
    sender_ip_addr = socket.gethostbyname(socket.gethostname())
    sender_addr_port = (sender_ip_addr, send_port)
    #Bind the socket to the sender's address
    sender_socket.bind(sender_addr_port)


    print("UDP server is waiting for messages..")
        
    
    #When the sender gets the request from the requester
    while True:
        
        data_received, requester_ip_addr = sender_socket.recvfrom(1024)
        if not data_received: break #if requester disconnected 

        #Classify header and filename
        header = data_received[:9]
        decoded_header = struct.unpack("!cII", header)
        filename = data_received[9:]
        
        #If it is the requested packet
        if decoded_header[0] == b'R': 
            send_data(filename, rate, seq_no, length, requester_ip_addr, sender_socket)

    


"""Send_data
    1. Open file and send DATA packet to the requester
    2. Also send the END packet to the requester after sending all packets
"""
def send_data(filename, rate, seq_no, length, requester_ip_addr, sender_socket):
    
    requester_ip, requester_port = requester_ip_addr
    
    print("-----------------------------------------------------------------------------")
    print("sender's print information:")
    
    #Read file 
    try:
        file = open(filename, "r")
        file_size = os.path.getsize(filename)
    #Fail to read file
    except FileNotFoundError:
        print("It doesn't contain such a file")
        pass
    #Succeed to read file, send the data to the requester
    else:
        while (file_size > 0): #Decided by max_length, may not send the data all at once
            if file_size > length:
                data_length = length
            else:
                data_length = file_size
            payload = file.read(data_length)
            file_size -= length
            
            #Pack and send the DATA packet to the requester
            data_header = struct.pack("!cII", b'D', socket.htonl(seq_no), data_length)
            packet = data_header + payload.encode()
            sender_socket.sendto(packet, requester_ip_addr)
            
            print("DATA Packet")
            print("send time:  ", dt.datetime.now(), sep='')
            print("requester addr:  ", requester_ip, ":", requester_port, sep='')
            print("Sequence num:  ", seq_no, sep='')
            print("length:  ", data_length, sep='')
            print("payload:  " + payload[0:4])
            print("")       
            seq_no += data_length
            time.sleep(1/rate) #Time delay by rate
        file.close()


    #Once it is done sending all data, send END packet to the requester
    end_header = struct.pack("!cII", b'E', socket.htonl(seq_no), 0)
    sender_socket.sendto(end_header, requester_ip_addr)
    
    print("END Packet")
    print("send time:  ", dt.datetime.now(), sep='')
    print("requester addr:  ", requester_ip, ":", requester_port, sep='')
    print("Sequence num:  ", seq_no, sep='')
    print("length:  0")
    print("payload:  ")
    print("")
    print("")
    
    


def main():
    
    parser = argparse.ArgumentParser(description="sender data")

    parser.add_argument('-p', type=int, metavar='port', help = 'sender port num')
    parser.add_argument('-g', type=int, metavar='requester_port', help = 'requester port num')
    parser.add_argument('-r', type=int, metavar='rate', help = 'packet rate per second')
    parser.add_argument('-q', type=int, metavar='seq_no', help = 'sequence number')
    parser.add_argument('-l', type=int, metavar='length', help = 'length of payload')
    args = parser.parse_args()

    read_data(args.p, args.g, args.r, args.q, args.l)




if __name__ == "__main__":
	main()

 

 

 

 

구현 방법(리눅스):

한개 이상의 서버에 파일을 요청할 경우, 각 서버폴더에 있는 파일 이름과 순서, 호스트 네임과 포트번호를 Tracker.txt 파일에 적는다.

예시(tracker.txt)

 

 

 

각각의 서버폴더에 파일과 sender.py를 복사한 후, Sender 와 Requester 폴더에서 다음과 같이 실행한다.

sender 실행 예시

 

Requester 실행 예시

 

 

 

 

 

아웃풋 예시: 

 

 

-----------------------------------------------------------------------------
sender1’s print information:
DATA Packet
send time:        2023-01-26 01:26:05.428
requester addr:   128.105.37.145:5001
Sequence num:     100
length:           23
payload:          *Thi

END Packet
send time:        2023-01-26 01:26:06.429
requester addr:   128.105.37.145:5001
Sequence num:     123
length:           0
payload:         	


-----------------------------------------------------------------------------
sender2’s print information:
DATA Packet
send time:        2023-01-26 01:26:05.428
requester addr:   128.105.37.145:5001
Sequence num:     100
length:           20
payload:          This

END Packet
send time:        2023-01-26 01:26:06.429
requester addr:   128.105.37.145:5001
Sequence num:     120
length:           0
payload:         


-----------------------------------------------------------------------------
Requester’s print information:	
DATA Packet
recv time:        2023-01-26 01:26:05.428
sender addr:      128.105.37.145:5000
sequence:         100
length:           23
payload:          *Thi

DATA Packet
recv time:        2023-01-26 01:26:05.428
sender addr:      128.105.37.145:5002
sequence:         100
length:           20
payload:          This

End Packet
recv time:        2023-01-26 01:26:06.429
sender addr:      128.105.37.145:5000
sequence:         123
length:           0
payload:          0

Summary
sender addr:              128.105.37.145:5000
Total Data packets:       1
Total Data bytes:         23
Average packets/second:   1
Duration of the test:     1001  ms

End Packet
recv time:        2023-01-26 01:26:06.429
sender addr:      128.105.37.145:5002
sequence:         120
length:           0
payload:          0

Summary
sender addr:              128.105.37.145:5002
Total Data packets:       1
Total Data bytes:         20
Average packets/second:   1
Duration of the test:     1001  ms


-----------------------------------------------------------------------------
In addition, a file split.txt will be generated in the directory requester/. 
The content should be:

*This is split1.txt.
*
This is split2.txt.

-----------------------

 

 

그러나 실행시켜보면 위와 똑같은 UDP 통신 구현은 아직 되지 않은 것을 알 수 있다. Tracker_list에 있는 라인을 읽는 과정에서 동시적으로 파일을 request하지 않기 때문이다. (전송이 끝난 후 다음 전송) 멀티쓰레딩을 활용할 수도 있지만, 그렇지 않고도 구현할 수 있다고 하니, 이번에 파이썬으로 감을 잡았으니 다음엔 C++로 동시에 파일을 request하는 UDP를 구현해보겠다

 

 

 

 

 

 

반응형