소켓 프로그래밍 with Python - 간단한 서버/클라이언트 구현
21 Mar 2021BSD socket 인터페이스에 대한 액세스를 제공
socket()
함수는 소켓 객체 (socket object)를 반환하고, 메서드로 다양한 소켓 시스템 호출을 구현
이때 얻어지는 소켓 객체는 File API와 유사한 socket descriptor다. 때문에 with ~ as
구문도 사용 가능
소켓 객체 생성
with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as s
family
값에는 다양한 소켓 패밀리가 지원되는데, 쉽게 말해 소켓 객체를 만들 때 사용할 주소 형식이다.
가장 많이 사용되는 socket.AF_INET
은 (host, port)
튜플이 주소 패밀리로 사용된다.
host
: 호스트(여기서는 서버)의 인터넷 도메인 주소나 IPv4 주소를 나타내는 문자열post
: 포트 번호를 나타내는 정수
이때 IPv4 주소의 경우, ''
는 시스템에 있는 모든 주소로 소켓에 연결할 수 있음을 나타낸다.
지금은 같은 호스트 내에서 구현하는 소켓 프로그램이므로 간단하게 루프 백 주소(127.0.0.1)를 지정하는 데 사용할 수 있다.
보통 다른 호스트와 연결할 시 socket.gethostname()
으로 지정할 수 있으며, 'localhost'
나 '127.0.0.1'
로 루프 백 주소를 지정할 수도 있다.
type
은 소켓 유형을 지정하는 매개 변수로 SOCK_STREAM
(기본값), SOCK_DGRAM
, SOCK_RAW
또는 기타 SOCK_
상수 중 하나여야 한다.
SOCK_STREAM
: TCP 통신 소켓SOCK_DGRAM
: UDP 통신 소켓
사용할 IP address와 Port number 등록
host = ''
port = 8888
s.bind((host, port))
소켓을 address에 바인드하며, 이미 바인드 되어 있는 소켓은 안 된다.
이때 address의 형식은 위에서 지정한 주소 패밀리의 형식을 따른다.
주의해야 할 점은 address를 튜플 형식으로 지정해 넘겨줘야 한다.
요청 수신 대기 모드로 전환
s.listen(1)
생성된 소켓 중 아직 연결되지 않은 소켓을 다른 클라이언트의 요청을 수신 대기 중인 상태로 만든다. 매개 변수 값으로 외부 연결을 거부하기 전에 최대 n개의 연결 요청을 큐에 넣겠다라고 소켓 라이브러리에 알린다.
이때 이 소켓을 사용한 프로세스(또는 스레드)는 Block 상태가 된다.
client의 요청 수락 후 실질적인 소켓 연결
conn, addr = s.accept()
클라이언트의 요청을 수락한 후, 반환 값으로 (conn, address)
튜플을 반환한다.
conn
: 연결된 클라이언트(소켓)과 데이터를 송수신할 수 있는 새로운 소켓 객체address
: 연결된 소켓에 바인드 된 주소 패밀리
소켓에서 데이터를 수신
msg = conn.recv(1024)
r_msg = msg.decode()
반환 값은 수신된 데이터를 나타내는 바이트열 객체이므로 따로 디코드 작업이 필요하다.
매개변수 값으로 한 번에 수신할 수 있는 최대 데이터 양을 지정한다.
소켓에서 데이터를 송신
conn.sendall(r_msg.encode())
모든 데이터가 전송되거나 에러가 발생할 때까지 인자 값 데이터를 계속 전송하며 성공하면 None
이 반환
소켓 닫기
conn.close()
연결된 소켓을 닫아 할당된 자원을 해제한다.
전체 코드
## sever.py
import socket
import argparse
def run_server(port=8888):
host = '' # 127.0.0.1 Loop back
with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as s:
s.bind((host, port))
s.listen(1) # max 1 client
conn, addr = s.accept()
msg = conn.recv(1024)
r_msg = msg.decode() # msg is a binary data, so we need to decode it
print(f'client addr: {addr[0]}\nmsg: {r_msg}')
conn.sendall(r_msg.encode())
conn.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Echo server -p port")
parser.add_argument('-p', help="port_number", required=True)
args = parser.parse_args()
run_server(port=int(args.p))
클라이언트는 서버와 거의 동일하다.
단지 connect
에 넘겨줄 서버의 host
와 port
를 입력받고, 전송할 문자열을 입력받으면 된다.
## client.py
import socket
import argparse
def run(host, port, str_list):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, port))
print(f'Connection with {host} succeeded ')
msg = " ".join(str_list)
s.sendall(msg.encode())
print(f'send to msg: {msg.encode()}')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Echo client -p port -i host -s string")
parser.add_argument('-p', help="port_number", required=True)
parser.add_argument('-i', help="host_name", required=True)
parser.add_argument('-s', help="input_string", nargs='+', required=True)
args = parser.parse_args()
run(host=args.i, port=int(args.p), str_list=args.s)
server
clinet
Reference
https://docs.python.org/ko/3/library/socket.html
https://docs.python.org/ko/3/howto/sockets.html#socket-howto