뇌를 자극하는 파이썬 3 - 네크워크 프로그래밍
by Yoonkh
네트워크 프로그래밍
네트워크 프로그래밍에 앞서 알아둬야 할 기초
인터넷의 유래
네트워크(Network)는 그물(Net)에서 파생된 단어로, 어떤 물건이나 사람 등의 상호 연결되어 있는 체계를 말한다. 보통 우리가 이야기하는 네트워크는 컴퓨터들이 상호 연결되어 있는 ‘컴퓨터 통신 네트워크’이다.
냉전시대 미국은 소련과의 우주경쟁에서 뒤쳐졌다는 위기감에 DARPA라는 기관을 설립했다. DARPA의 각 연구기관에서는 컴퓨터를 이용해서 연구를 수행했고, 네트워크와 네트워크를 연결하는 새로운 구상을 하게 되었다. 이 방법을 이용하면 연구기관들을 잇는 회선을 일일히 구축하지 않고도 최소한의 비용으로 연구소들의 컴퓨터를 연결 할 수 있었다.
필요한 자료들을 ‘빛의 속도’로 획득할 수 있게 되었다
엄청난 혁신이었다
- 이른바 DARPANET이라고 불리던 이 네트워크는 더 많은 대학과 연구기관으로, 또한 세계의 연구기관과 민간으로 연결되기 시작하더니 1980년대 말에 이르러서는 인터넷이라고하는 국제 통신망을 형성하게 되었다.
TCP/IP 스택
컴퓨터끼리 네트워크에서 데이터를 주고 받으려면 그 네트워크에서 통용되는 ‘프로토콜(Protocol)’을 따라야한다. 프로토콜은 규약, 규칙이라는 뜻의 낱말로써, 여기에서는 컴퓨터들이 네트워크를 통해 데이터를 주고받기 위한 ‘통신규약’을 말한다.
- TCP/IP는 다음과 같이 크게 네 개의 계층으로 구성되어 있으며, 한 계층 위에 다른 계층이 포개진 형태 때문에 이것을 TCP/IP 스택(stack)이라고 부르기도 한다.
Application Layer |
---|
Transport Layer |
Internet Layer |
Link Layer |
- 단순해 보이는 이 네 단계의 프로토콜 모음이 오늘날 인터넷을 떠받치고 있다
링크 계층
TCP/IP는 네트워크의 물리적인 구성으로부터 독립적인 프로토콜이다. 컴퓨터가 네트워크에 전화전에 모뎀으로 연결되어 있든, LAN에 이더넷 케이블로 연결되어 있든, 혹은 와이파이에 연결되어 있든 전혀 신경 쓰지 않는다.
- 이것은 링크 계층에서 네트워크의 물리적인 연결 매체를 통해 패킷을 주고 받는 작업을 담당해주기 때문이다.
인터넷 계층
인터넷 계층은 패킷을 수신해야 할 상대의 주소를 지정하고, 나가는 패킷에 대해서는 적절한 크기로 분할하며 들어오는 패킷에 대해서는 재조립을 수행한다! 이 계층에서 사용되는 규약이 바로 인터넷 프로토콜(Internet Protocol), 즉, IP이다. TCP/IP에서 IP가 바로 이것이다.
전송 계층
전송 계층(Transport Layer)에는 이름 그대로 패킷의 ‘운송’을 담당하는 프로토콜들이 정의되어 있다. 그 중에서도 전송 제어 프로토콜(TCP, Transmission Control Protocol)는 송신측과 수신측 간의 연결성을 제공하며, 신뢰할 수 있는 패킷 전송 서비스를 제공한다. 여러 개의 패킷을 송신하는 경우 패킷 사이의 순서를 보장하며, 패킷이 유실되기라도 하면 재전송을 해주기까지 한다. TCP/IP 프로토콜에서 TCP가 바로 이 프로토콜을 가리키는 것이며, TCP는 IP가 제공하지 않는 연결성, 신뢰성을 제공한다. 웹 문서를 전달하는 기능을 하는 HTTP를 비롯한 수많은 응용 프로토콜들이 바로 이 TCP와 IP 프로토콜 위에서 동작한다.
애플리케이션 계층
이 계층은 각 응용 프로그램 나름의 프로토콜들이 정의되는 곳이다. 웹 문서를 주고 받기 위한 HTTP, 파일 교환을 위한 FTP, 네트워크 관리를 위한 SNMP 등이 애플리케이션 계층에서 정의된 프로토콜의 대표적인 예이다. 애플리케이션의 계층의 프로토콜들은 전송 계층의 프로토콜 중 TCP에 기반할 수도 있고, UDP에 기반할 수도 있다.
- HTTP와 FTP는 상대적으로 큰 데이터를 처리해야 하기 때문에 연결성과 신뢰성을 제공하는 TCP에 기반하고 있고, SNMP는 단순한 정보만을 다루는 데다 패킷을 일부 유실한다 해도 임무에 지장을 주지 않기 때문에 비용이 저렴한 UDP에 기반한다.
TCP/IP의 주소 체계: IP 주소
우편 배달부가 우편물을 배달하기 위해서는 ‘주소’가 필요하다. 인터넷에서도 패킷을 배달하려면 이것을 어디에서 보냈는지, 또 어디로 보낼지에 대한 정보 즉 주소가 필요하다. 그리고 인터넷에서 사용하는 이 주소를 일컬어 ‘IP 주소(Address)’라고 부른다.
- 현재는 주소 고갈이 임박한 IPv4 체계에서 IPv6체계로 전환이 전 세계적으로 진행되고 있다.
포트
큰 빌딩은 대개 출입구가 여러 개 있다. O마트의 예를 보면 주차장 출입구만 해도 일반 고객 승용차와 수화물 차량 출입구가 나눠져 있고, 살마들이 드나드는 출입구도 여러 곳이 있다. 이 출입구를 통해 차도, 사람도, 물건도 드나든다.
컴퓨터도 네트워크 패킷이 드나들려면 ‘주소’뿐만아니라 출입문이 필요하다.
이 출입문을 일컬어 포트(Port)라고 한다.
TCP/IP의 동작 과정
TCP/IP는 서버/클라이언트 방식으로 동작한다. 통신을 수행하는 양단 중 한쪽에서는 한쪽에게 서비스를 제공해야 한다는 것이다. 서버/클라이언트 방식으로 만들어진 TCP/IP 서비스의 예를 들면, FTP서버와 FTP클라이언트, SMTP 메일 서버와 메일 클라이언트 등등 이들 모두가 TCP/IP 기반으로 만들어졌다.
socket과 TCPServer를 이용한 TCP/IP 프로그래밍
파이썬의 라이브러리들이 공통으로 사용하는 라이브러리가 있다. 바로 socket 모듈이다.
socket 모듈은 TCP/IP, UDP/IP를 지원하는 버클리 소켓 인터페이스를 여러 가지 함수와 socket클래스를 통해 제공한다.
socket 클래스를 이용하면 거의 모든 인터넷 프로토콜을 구현할 수 있다는 장점이 있는 반면, 익히고 사용하기가 간단하지 않다는 단점도 있다.
클래스 | 메소드 | 설명 |
---|---|---|
TCPServer | serve_forever() | 클라이언트의 접속 요청을 수신 대기한다. 접속 요청이 있을 경우 수락하고 BaseRequestHandler의 handler() 메소드를 호출한다. |
BaseRequestHandler | handler() | 클라이언트 접속 요청을 처리한다. |
socket | connect() | 서버에 접속 요청을 한다. |
send() | 데이터를 상대방에게 전송한다. | |
recv() | 데이터를 수신한다. |
ex) TCPServer와 BaseRequestHandler와 socket 클래스의 사용법 예
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self.client_address[0]) # 클라이언트의 IP주소 출력
buffer = self.request.recv(1024).strip() # 데이터 수신
self.request.send (buffer) # 데이터 송신
- BaseRequestHandler로부터 상속을 받은 MyTCPHandler는 handle() 메소드를 재정의한다. handle() 메소드는 클라이언트의 연결 요청을 서버가 수락했을 때 호출된다. 다시 말하면 handle() 메소드가 호출되었다는 것은 통신을 수행할 준비가 됐다는 것이다!
ex) 클라이언트가 보내오는 메세지를 서버가 그대로 ‘메아리’쳐 돌려보내는 프로그램 예
import socketserver
import sys
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
print('클라이언트 접속 : {0}'.format(self.client_address[0]))
sock = self.request
rbuff = sock.recv(1024) # 데이터를 수신하고 그 결과를 rbuff에 담는다. rbuff는 bytes 형식이다.
received = str(rbuff, encoding="utf-8")
print('수신 : {0}'.format(received))
# 수신한 데이터를 그대로 돌려보냄.
sock.send(rbuff) # 수신한 데이터를 그대로 클라이언트에게 다시 송신한다.
print('송신 : {0}'.format(received))
sock.close()
if __name__ == '__main__':
if len(sys.argv) < 2:
print('{0} <Bind IP>'.format(sys.argv[0]))
sys.exit()
bindIP = sys.argv[1]
bindPort = 5425 #
server = socketserver.TCPServer((bindIP, bindPort), MyTCPHandler)
print('메아리 서버 시작...')
server.serve_forever() # 클라이언트로부터 접속 요청을 받아들일 준비를 한다.
ex) 메아리 클라이언트 예
import socket
import sys
if __name__ == '__main__':
if len(sys.argv) < 4:
print("{0} <Bind IP> <Server IP> <Message>".format(sys.argv[0]))
sys.exit()
bindIP = sys.argv[1]
serverIP = sys.argv[2]
message = sys.argv[3]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM = TCP socket
sock.bind((bindIP, 0))
try:
sock.connect((serverIP, 5425)) # 연결 요청을 수행한다.
# 메아리 송신
sbuff = bytes(message, encoding="utf-8")
sock.send(sbuff) # 메세지를 송신한다.
print("송신 : {0}".format(message))
# 메아리 수신
rbuff = sock.recv(1024) # 메세지를 수신한다.
received = str(rbuff, encoding="utf-8")
print("수신 : {0}".format(received))
finally:
sock.close()
흐르는 패킷
TCP 프로토콜은 연결 지향, 흐름 지향 프로토콜이다. 전기처럼 양쪽이 연결되어 있어야 하고 보내는 쪽에서 받는 쪽으로 패킷을 흘려 보낸다.
하지만 TCP 프로토콜은 전기와 달리 흐름 속에서 각 개별 패킷의 경계를 구분해야 한다. 시작이 어디고 끝이 어디인지를 파악해야 한다.
프로토콜 설계와 네트워크 애플리케이션 프로그래밍 예제
파일 업로드 프로토콜
파일 업로드 프로토콜(FUP)은 헤더와 바디의 두 부분으로 나뉜다. 바디에는 실제로 전달하고자 하는 데이터를 담고, 헤더에는 본문 길이를 비롯해 메세지의 속성 몇 가지를 담는다. 바디의 길이는 담는 데이터에 따라 달라지지만 헤더의 길이는 16바이트로 항상 일정하다. 따라서 수신한 패킷을 분석할 때는 가장 먼저 16바이트를 먼저 확인해서 (바디의 길이를 포함한) 메세지의 속성을 확인하고, 그 다음에 바디의 길이 만큼을 또 읽어 하나의 메세지 끝을 끊어내야 한다.
파일 업로드 서버와 클라이언트 구현
예제 프로그램의 구현은 세 가지 부분으로 나눠서 진행한다.
-
서버/클라이언트 공용 모듈 구현
-
서버 구현
-
클라이언트 구현
서버/클라이언트가 같이 사용할 모듈 만들기
REQ_FILE_SEND = 0x01 # 메세지 타입 상수 정의
REP_FILE_SEND = 0x02
FILE_SEND_DATA = 0x03
FILE_SEND_RES = 0x04
NOT_FRAGMENTED = 0x00 # 파일 분할 여부 상수 정의
FRAGMENTED = 0x01
NOT_LASTMSG = 0x00 # 분할된 메세지의 마지막 여부 상수 정의
LASTMSG = 0x01
ACCEPTED = 0x00 # 파일 전송 수락 여부 상수 정의
DENIED = 0x01
FAIL = 0x00 # 파일 전송 성공 여부 상수 정의
SUCCESS = 0x01
class ISerializable:
def GetBytes(self): # 메세지, 헤더, 바디는 모두 이 클래스를 상속한다. 즉, 이들은 자신의 데이터를 바이트 배열로 변환하고 그 바이트 배열의 크기를 반환해야 한다.
pass
def GetSize():
pass
class Message(ISerializable):
def __init__(self):
self.Header = ISerializable()
self.Body = ISerializable()
def GetBytes(self):
buffer = bytes(self.GetSize())
header = self.Header.GetBytes()
body = self.Body.GetBytes()
return header + body
def GetSize(self):
return self.Header.GetSize() + self.Body.GetSize()
- 메세지의 헤더
from message import ISerializable
import struct
class Header(ISerializable):
def __init__(self, buffer):
self.struct_fmt = '=3I2BH' # 3 unsigned int, 2 byte, 1 unsigned short
self.struct_len = struct.calcsize(self.struct_fmt)
if buffer != None:
unpacked = struct.unpack(self.struct_fmt, buffer)
self.MSGID = unpacked[0]
self.MSGTYPE = unpacked[1]
self.BODYLEN = unpacked[2]
self.FRAGMENTED = unpacked[3]
self.LASTMSG = unpacked[4]
self.SEQ = unpacked[5]
def GetBytes(self):
return struct.pack(
self.struct_fmt,
*(
self.MSGID,
self.MSGTYPE,
self.BODYLEN,
self.FRAGMENTED,
self.LASTMSG,
self.SEQ
))
def GetSize(self):
return self.struct_len
- 메세지 본문(body)를 표현하는 클래스
from message import ISerializable
import message
import struct
class BodyRequest(ISerializable): # 파일 전송 요청 메세지(0x01)에 사용할 본문 클래스이다. FILESIZE와 FILENAME 데이터 속성을 갖는다.
def __init__(self, buffer):
if buffer != None:
slen = len(buffer)
# 1 unsigned long long, N character
self.struct_fmt = str.format('=Q{0}s', slen-8)
self.struct_len = struct.calcsize(self.struct_fmt)
if slen > 4: # unsigned long long의 크기
slen = slen - 4
else:
slen = 0
unpacked = struct.unpack(self.struct_fmt, buffer)
self.FILESIZE = unpacked[0]
self.FILENAME = unpacked[1].decode(
encoding='utf-8').replace('\x00', '')
else:
self.struct_fmt = str.format('=Q{0}s', 0)
self.struct_len = struct.calcsize(self.struct_fmt)
self.FILESIZE = 0
self.FILENAME = ''
def GetBytes(self):
buffer = self.FILENAME.encode(encoding='utf-8')
# 1 unsigned long long, N character
self.struct_fmt = str.format('=Q{0}s', len(buffer))
return struct.pack(
self.struct_fmt,
*(
self.FILESIZE,
buffer
))
def GetSize(self):
buffer = self.FILENAME.encode(encoding='utf-8')
# 1 unsigned long long, N character
self.struct_fmt = str.format('=Q{0}s', len(buffer))
self.struct_len = struct.calcsize(self.struct_fmt)
return self.struct_len
class BodyResponse(ISerializable): # 파일 전송 요청에 대한 응답 메세지(0x02)에 사용할 본문 클래스이다. 요청 메세지의 MSGID와 수락 여부를 나타내는 RESPONSE 데이터 속성을 갖는다.
def __init__(self, buffer):
# 1 unsigned int, Byte
self.struct_fmt = '=IB'
self.struct_len = struct.calcsize(self.struct_fmt)
if buffer != None:
unpacked = struct.unpack(self.struct_fmt, buffer)
self.MSGID = unpacked[0]
self.RESPONSE = unpacked[1]
else:
self.MSGID = 0
self.RESPONSE = message.DENIED
def GetBytes(self):
return struct.pack(
self.struct_fmt,
*(
self.MSGID,
self.RESPONSE
))
def GetSize(self):
return self.struct_len
class BodyData(ISerializable): # 실제 파일을 전송하는 메세지(0x03)에 사용할 본문 클래스이다. 앞서 프로토콜 정의에서 언급되었던 것처럼 DATA 필드만 갖고 있다.
def __init__(self, buffer):
if buffer != None:
self.DATA = buffer
def GetBytes(self):
return self.DATA
def GetSize(self):
return len(self.DATA)
class BodyResult(ISerializable): # 파일 전송 결과 메세지, 메세지(0x04)에 사용할 본문 클래스이다. 요청 메세지의 MSGID와 성공 여부를 나타내는 RESULT 데이터 속성을 갖는다.
def __init__(self, buffer):
# 1 unsigned int, Byte
self.struct_fmt = '=IB'
self.struct_len = struct.calcsize(self.struct_fmt)
if buffer != None:
unpacked = struct.unpack(self.struct_fmt, buffer)
self.MSGID = unpacked[0]
self.RESULT = unpacked[1]
else:
self.MSGID = 0
self.RESULT = message.FAIL
def GetBytes(self):
return struct.pack(
self.struct_fmt,
*(
self.MSGID,
self.RESULT
))
def GetSize(self):
return self.struct_len
- MessageUtil 클래스의 구현
import socket
import message
from message import Message
from message_header import Header
from message_body import BodyRequest
from message_body import BodyResponse
from message_body import BodyData
from message_body import BodyResult
class MessageUtil:
@staticmethod
def send(sock, msg): # send() 메소드는 msg 매개변수가 담고 있는 모든 바이트를 내보낼 때까지 반복해서 socket.send() 메소드를 호출한다.
sent = 0
buffer = msg.GetBytes()
while sent < msg.GetSize():
sent += sock.send(buffer)
@staticmethod
def receive(sock):
totalRecv = 0
sizeToRead = 16 # 헤더의 크기
hBuffer = bytes() # 헤더 버퍼
# 헤더 읽기
while sizeToRead > 0: # 첫 반복문에서는 스트림으로부터 메세지 헤더의 경계를 끊어낸다.
buffer = sock.recv(sizeToRead)
if len(buffer) == 0:
return None
hBuffer += buffer
totalRecv += len(buffer)
sizeToRead -= len(buffer)
header = Header(hBuffer)
totalRecv = 0
bBuffer = bytes()
sizeToRead = header.BODYLEN
while sizeToRead > 0: # 첫 반복문에서 얻은 헤더에서 본문의 길이를 뽑아내어 그 길이만큼 다시 스트림으로부터 본문을 읽는다.
buffer = sock.recv(sizeToRead)
if len(buffer) == 0:
return None
bBuffer += buffer
totalRecv += len(buffer)
sizeToRead -= len(buffer)
body = None
if header.MSGTYPE == message.REQ_FILE_SEND:
body = BodyRequest(bBuffer)
elif header.MSGTYPE == message.REP_FILE_SEND:
body = BodyResponse(bBuffer)
elif header.MSGTYPE == message.FILE_SEND_DATA:
body = BodyData(bBuffer)
elif header.MSGTYPE == message.FILE_SEND_RES:
body = BodyResult(bBuffer)
else:
raise Exception(
"Unknown MSGTYPE : {0}".
format(header.MSGTYPE))
msg = Message()
msg.Header = header
msg.Body = body
return msg
- 파일 업로드 서버의 구현
import os
import sys
import socket
import socketserver
import struct
import message
from message import Message
from message_header import Header
from message_body import BodyData
from message_body import BodyRequest
from message_body import BodyResponse
from message_body import BodyResult
from message_util import MessageUtil
CHUNK_SIZE = 4096
upload_dir = ''
class FileReceiveHandler(socketserver.BaseRequestHandler):
def handle(self):
print("클라이언트 접속 : {0}".format(self.client_address[0]))
client = self.request # client socket
reqMsg = MessageUtil.receive(client) # 클라이언트가 보내온 파일 전송 요청 메세지를 수신한다.
if reqMsg.Header.MSGTYPE != message.REQ_FILE_SEND:
client.close()
return
reqBody = BodyRequest(None)
print(
"파일 업로드 요청이 왔습니다. 수락하시겠습니까? yes/no")
answer = sys.stdin.readline()
rspMsg = Message()
rspMsg.Body = BodyResponse(None)
rspMsg.Body.MSGID = reqMsg.Header.MSGID
rspMsg.Body.RESPONSE = message.ACCEPTED
rspMsg.Header = Header(None)
msgId = 0
rspMsg.Header.MSGID = msgId
msgId = msgId + 1
rspMsg.Header.MSGTYPE = message.REP_FILE_SEND
rspMsg.Header.BODYLEN = rspMsg.Body.GetSize()
rspMsg.Header.FRAGMENTED = message.NOT_FRAGMENTED
rspMsg.Header.LASTMSG = message.LASTMSG
rspMsg.Header.SEQ = 0
if answer.strip() != "yes": # 사용자가 'yes'가 아닌 답을 입력하면 클라이언트에게 '거부'응답을 보낸다.
rspMsg.Body = BodyResponse(None)
rspMsg.Body.MSGID = reqMsg.Header.MSGID
rspMsg.Body.RESPONSE = message.DENIED
MessageUtil.send(client, rspMsg)
client.close()
return
else:
MessageUtil.send(client, rspMsg) # 물론'yes'를 입력하면 클라이언트에게 '승낙'응답을 보낸다.
print("파일 전송을 시작합니다...")
fileSize = reqMsg.Body.FILESIZE
fileName = reqMsg.Body.FILENAME
recvFileSize = 0
with open(upload_dir + "\\" + fileName, 'wb') as file: # 업로드 받을 파일을 생성한다.
dataMsgId = -1
prevSeq = 0
while True:
reqMsg = MessageUtil.receive(client)
if reqMsg == None:
break
print("#", end='')
if reqMsg.Header.MSGTYPE != message.FILE_SEND_DATA:
break
if dataMsgId == -1:
dataMsgId = reqMsg.Header.MSGID
elif dataMsgId != reqMsg.Header.MSGID:
break
if prevSeq != reqMsg.Header.SEQ: # 메세지 순서가 어긋나면 전송을 중단한다.
print("{0}, {1}".format(prevSeq, reqMsg.Header.SEQ))
break
prevSeq += 1
recvFileSize += reqMsg.Body.GetSize() # 전송받은 파일의 일부를 담고 있는 bytes 객체를 서버에서 생성한 파일에 기록한다.
file.write(reqMsg.Body.GetBytes())
if reqMsg.Header.LASTMSG == message.LASTMSG: # 마지막 메세지만 반복문을 빠져나온다.
break
file.close()
print()
print("수신 파일 크기 : {0} bytes".format(recvFileSize))
rstMsg = Message()
rstMsg.Body = BodyResult(None)
rstMsg.Body.MSGID = reqMsg.Header.MSGID
rstMsg.Body.RESULT = message.SUCCESS
rstMsg.Header = Header(None)
rstMsg.Header.MSGID = msgId
msgId += 1
rstMsg.Header.MSGTYPE = message.FILE_SEND_RES
rstMsg.Header.BODYLEN = rstMsg.Body.GetSize()
rstMsg.Header.FRAGMENTED = message.NOT_FRAGMENTED
rstMsg.Header.LASTMSG = message.LASTMSG
rstMsg.Header.SEQ = 0
if fileSize == recvFileSize: # 파일 전송 요청에 담겨온 파일 크기와 실제로 받은 파일의 크기를 비교하여 같으면 성공 메세지를 보낸다.
MessageUtil.send(client, rstMsg)
else:
rstMsg.Body = BodyResult(None)
rstMsg.Body.MSGID = reqMsg.Header.MSGID
rstMsg.Body.RESULT = message.FAIL
MessageUtil.send(client, rstMsg) # 파일 크기에 이상이 있다면 실패 메세지를 보낸다.
print("파일 전송을 마쳤습니다.")
client.close()
if __name__ == '__main__':
if len(sys.argv) < 2:
print("사용법 : {0} <Directory>".format(sys.argv[0]))
sys.exit(0)
upload_dir = sys.argv[1]
if os.path.isdir(upload_dir) == False:
os.mkdir(upload_dir)
bindPort = 5425
server = None
try:
server = socketserver.TCPServer(
('', bindPort), FileReceiveHandler)
print("파일 업로드 서버 시작...")
server.serve_forever()
except Exception as err:
print(err)
print("서버를 종료합니다.")
- 클라이언트의 구현
import os
import sys
import socket
import struct
import message
from message import Message
from message_header import Header
from message_body import BodyData
from message_body import BodyRequest
from message_body import BodyResponse
from message_body import BodyResult
from message_util import MessageUtil
CHUNK_SIZE = 4096
if __name__ == '__main__':
if len(sys.argv) < 3:
print("사용법 : {0} <Server IP> <File Path>".
format(sys.argv[0]))
sys.exit(0)
serverIp = sys.argv[1]
serverPort = 5425
filepath = sys.argv[2]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP 소켓을 생성한다.
try:
print("서버:{0}/{1}".format(serverIp, serverPort))
sock.connect((serverIp, serverPort)) # 접속 요청을 수락한다.
msgId = 0
reqMsg = Message()
filesize = os.path.getsize(filepath)
reqMsg.Body = BodyRequest(None)
reqMsg.Body.FILESIZE = filesize
reqMsg.Body.FILENAME = filepath[filepath.rindex('\\')+1:]
msgId += 1
reqMsg.Header = Header(None)
reqMsg.Header.MSGID = msgId
reqMsg.Header.MSGTYPE = message.REQ_FILE_SEND
reqMsg.Header.BODYLEN = reqMsg.Body.GetSize()
reqMsg.Header.FRAGMENTED = message.NOT_FRAGMENTED
reqMsg.Header.LASTMSG = message.LASTMSG
reqMsg.Header.SEQ = 0
MessageUtil.send(sock, reqMsg) # 클라이언트는 서버에 접속하자마자 파일 전송 요청 메세지를 보낸다.
rspMsg = MessageUtil.receive(sock) # 그리고 서버의 응답을 받는다.
if rspMsg.Header.MSGTYPE != message.REP_FILE_SEND:
print("정상적인 서버 응답이 아닙니다.{0}".
format(rspMsg.Header.MSGTYPE))
exit(0)
if rspMsg.Body.RESPONSE == message.DENIED:
print("서버에서 파일 전송을 거부했습니다.")
exit(0)
with open(filepath, 'rb') as file: # 서버에서 전송 요청을 수락했다면, 파일을 열어 서버로 보낼 준비를 한다.
totalRead = 0
msgSeq = 0 #ushort
fragmented = 0 #byte
if filesize < CHUNK_SIZE:
fragmented = message.NOT_FRAGMENTED
else:
fragmented = message.FRAGMENTED
while totalRead < filesize:
rbytes = file.read(CHUNK_SIZE)
totalRead += len(rbytes)
fileMsg = Message()
fileMsg.Body = BodyData(rbytes) # 모든 파일의 내용이 전송될 때까지 파일을 0x03 메세지에 담아 서버로 보낸다.
header = Header(None)
header.MSGID = msgId
header.MSGTYPE = message.FILE_SEND_DATA
header.BODYLEN = fileMsg.Body.GetSize()
header.FRAGMENTED = fragmented
if totalRead < filesize:
header.LASTMSG = message.NOT_LASTMSG
else:
header.LASTMSG = message.LASTMSG
header.SEQ = msgSeq
msgSeq += 1
fileMsg.Header = header
print("#", end = '')
MessageUtil.send(sock, fileMsg)
print()
rstMsg = MessageUtil.receive(sock) # 서버에서 파일을 제대로 받았는지에 대한 응답을 받는다.
result = rstMsg.Body
print("파일 전송 성공 : {0}".
format(result.RESULT == message.SUCCESS))
except Exception as err:
print("예외가 발생했습니다.")
print(err)
sock.close()
print("클라이언트를 종료합니다.")
Subscribe via RSS