Skip to content
Snippets Groups Projects
client.py 8.14 KiB
import socket
import sys
import utility.segment as segment
import utility.hexa as hexa
import utility.receiver as receiver
import time
from utility.connection import Connection
import pickle

'''
Pseudocode
1. Program mengambil port dari input user (args)
2. Program melakukan koneksi dengan server (mengirim request, isinya port client dengan broadcast address)
3. Client tinggal mengikuti prosedur protokol dari server (3 way handshake)
4. Dalam keberlangsungan pengiriman (Go back n arq) lakukan
		- pemeriksaan apakah segmen yang diperoleh rusak atau tidak
		- jika tidak rusak, kirim acknowledgement kepada server
		- jika rusak abaikan
5. Menyudahi koneksi dengan server
6. Menutup program
'''

#--- Client compile file
def saveFile(segmentPool, folderpath):
	filename = input("\nSaving file...\n\nFile name (without extension): ")
	if ("." in filename):
		filename = filename[0:filename.index(".")+1]
	
	res = ""
	for s in segmentPool:
		res += s

	res = hexa.byte(res,'latin-1')

	res = pickle.loads(res)
	res.name = filename
	extension = res.extension

	f = open(folderpath + "/" + res.name + extension, "wb")
	f.write(res.content)
	f.close()

#--- Initialize broadcast socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
host = socket.gethostname()
print("Host: "+str(host))

if (len(sys.argv) > 1):
	port = sys.argv[1]
	if (int(port) > 1400 or int(port) < 1300):
		print("Port number must be between 1300 and 1400")
		sys.exit()
	s.bind(('127.0.0.1', int(port)))
else:
	print("Port number not specified. Run: 'python client.py <port-number> </path/to/folder>'")
	print("Note: Due to program limitations. Port must be between 1300 and 1400.")
	exit()

if (len(sys.argv) > 2):
	folderpath = sys.argv[2]
else:
	print("Folder not specified. Run: 'python client.py <port-number> </path/to/folder>'")
	exit()

#--- Send broadcast

port_found = False
for port_ in range(1300,1401):
	try:
		s.sendto(str(port_).encode(), ('127.0.0.1', port_))
		data, addr = s.recvfrom(1024)
		if (data.decode() == "Yes" and int(port_) != int(port)):
			# Found server
			print("Server found at port "+str(port_))
			port_found = True
			time.sleep(0.5)
			print("Awaiting server actions... Please wait.")
			break
	except:
		pass

if (not port_found):
	print("No server responded. Please try again later.")
	exit()

#--- TCP over UDP, receive file from server

fin = False
serverConnection = Connection('127.0.0.1', 1337, 0)
stage = "RECEIVE_SYN_SEND_SYN_ACK"
received_data = None

segmentPool = []

while (not(fin)):
	# 1. client menerima SYN dari server, lalu mengirim SYN-ACK
	if (stage == "RECEIVE_SYN_SEND_SYN_ACK"):
		data, address = s.recvfrom(32777)
	
		r = receiver.Receiver()
		rec_packet = segment.Segment()

		rec_packet.build(r.receiveSegment(data))

		if (r.isSynSegment(rec_packet) and not(r.isAckSegment(rec_packet))):
			#if server send SYN untuk memulai koneksi
			seq_num0 = rec_packet.getSeqNum()
			serverConnection.server_seq_num = seq_num0

			print("\nSYN received with seq num: "+str(seq_num0))

			ack_packet = segment.Segment()

			#--- Nanti set seq_num sama ack_num pake go-back-n?? ---#
			serverConnection.n_data_received += 1

			seq_num = serverConnection.server_seq_num #paket pertama, random sequence number
			ack_num = seq_num0 + serverConnection.n_data_received #ekspektasi: seq_num berikut adalah seq_num + 1

			serverConnection.n_data_sent += 1
			#--- END ---#

			ack_packet.switchFlag("SYN")
			ack_packet.switchFlag("ACK")
			ack_packet.setSeqNum(seq_num)
			ack_packet.setAckNum(ack_num)
			ack_packet.compileCheckSum()

			message = hexa.byte(ack_packet.construct(),'utf-8')
			s.sendto(message, (address[0], address[1]))

			print("\nSYN-ACK sent with seq_num: "+str(seq_num)+" and ack num: "+str(ack_num))

			stage = "RECEIVE_ACK"

	# 2. client menerima ACK dari server
	elif (stage == "RECEIVE_ACK"):
		data, address = s.recvfrom(32777)
	
		r = receiver.Receiver()
		rec_packet = segment.Segment()

		rec_packet.build(r.receiveSegment(data))

		if (not(r.isSynSegment(rec_packet)) and r.isAckSegment(rec_packet)):
			#if server mengirim ACK untuk acknowledge client SYN,
			seq_num0 = rec_packet.getSeqNum()
			ack_num0 = rec_packet.getAckNum()

			print("\nACK received with seq num: "+str(seq_num0)+" and ack num: "+str(ack_num0))

			if (seq_num0 == serverConnection.server_seq_num + serverConnection.n_data_received):
				print("\nConnection established, receiving data...")

				stage = "RECEIVE_DATA"

	# 3. client menerima data dari server
	elif (stage == "RECEIVE_DATA"):
		repeat = True

		while repeat:
			data, address = s.recvfrom(65536)
		
			r = receiver.Receiver()
			rec_packet = segment.Segment()
			rec_packet.build(r.receiveSegment(data))

			if (not r.isNotBroken(r.receiveSegment(data))):
				print("\nPacket received is broken, resending...")
				continue

			if (r.isDataSegment(rec_packet)):
				seq_num0 = rec_packet.getSeqNum()
				ack_num0 = rec_packet.getSeqNum()

				print("\nData received with seq num: "+str(seq_num0)+" and ack num: "+str(ack_num0))
				# print(seq_num0)
				# print(serverConnection.server_seq_num)
				# print(serverConnection.n_data_received)

			#if (seq_num0 == serverConnection.server_seq_num + serverConnection.n_data_received):
				received_data = rec_packet.getPayLoad()
				print("\nReceived data from server")

				# Save received data for file construction later
				segmentPool.append(received_data)

				ack_packet = segment.Segment()
				serverConnection.n_data_received += len(received_data)
				seq_num = serverConnection.server_seq_num + serverConnection.n_data_sent
				ack_num = serverConnection.server_seq_num + serverConnection.n_data_received
				ack_packet.switchFlag("ACK")
				ack_packet.setSeqNum(seq_num)
				ack_packet.setAckNum(ack_num)
				ack_packet.compileCheckSum()

				message = hexa.byte(ack_packet.construct(),'utf-8')
				s.sendto(message, (address[0], address[1]))
				print("\nACK sent with seq_num: "+str(seq_num)+" and ack num: "+str(ack_num))

			elif (r.isFinSegment(rec_packet)):
				saveFile(segmentPool, folderpath)
				stage = "CLOSE_CONNECTION"
				repeat = False
				break

	# 4. server menutup koneksi, client mengikuti
	elif (stage == "CLOSE_CONNECTION"):
		if (r.isFinSegment(rec_packet)):
			#if server close connection
			
			seq_num0 = rec_packet.getSeqNum()
			ack_num0 = rec_packet.getAckNum()

			print("\nFIN received with seq num: "+str(seq_num0)+" and ack num: "+str(ack_num0))

			if (seq_num0 == serverConnection.server_seq_num + serverConnection.n_data_received):
				ack_packet = segment.Segment()

				#--- Nanti set seq_num sama ack_num pake go-back-n ---#
				serverConnection.n_data_received += 1

				seq_num = serverConnection.server_seq_num + serverConnection.n_data_sent
				ack_num = serverConnection.server_seq_num + serverConnection.n_data_received
				#--- END ---#

				ack_packet.switchFlag("ACK")
				ack_packet.setSeqNum(seq_num)
				ack_packet.setAckNum(ack_num)
				ack_packet.compileCheckSum()

				message = hexa.byte(ack_packet.construct(),'utf-8')
				s.sendto(message, (address[0], address[1]))

				print("\nACK sent with seq num: "+str(seq_num)+" and ack num: "+str(ack_num))

				fin_packet = segment.Segment()

				#--- Nanti set seq_num sama ack_num pake go-back-n ---#
				seq_num = serverConnection.server_seq_num + serverConnection.n_data_sent
				ack_num = serverConnection.server_seq_num + serverConnection.n_data_received

				serverConnection.n_data_sent += 1
				#--- END ---#

				fin_packet.switchFlag("FIN")
				fin_packet.setSeqNum(seq_num)
				fin_packet.setAckNum(ack_num)
				fin_packet.compileCheckSum()

				message = hexa.byte(fin_packet.construct(),'utf-8')
				s.sendto(message, (address[0], address[1]))

				print("\nFIN sent with seq num: "+str(seq_num)+" and ack num: "+str(ack_num))

				data, address = s.recvfrom(32777)
	
				r = receiver.Receiver()
				rec_packet = segment.Segment()

				rec_packet.build(r.receiveSegment(data))
				if (r.isAckSegment(rec_packet)):
					#client receive ACK of client's FIN
					
					seq_num0 = rec_packet.getSeqNum()
					ack_num0 = rec_packet.getAckNum()

					print("\nACK received with seq num: "+str(seq_num0)+" and ack num: "+str(ack_num0))

					fin = True #close connection

					print("\nConnection with server closed.")