on1ystar 머릿속을 정리, 기록하는 곳

소켓 프로그래밍 with Python - 간단한 서버/클라이언트 구현


의문점이나 지적 등의 관심 및 조언을 위한 댓글이나 메일은 언제나 환영이고 감사합니다.
python socket 모듈

BSD socket 인터페이스에 대한 액세스를 제공

socket() 함수는 소켓 객체 (socket object)를 반환하고, 메서드로 다양한 소켓 시스템 호출을 구현

이때 얻어지는 소켓 객체는 File API와 유사한 socket descriptor다. 때문에 with ~ as 구문도 사용 가능

server

소켓 객체 생성

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))
client

클라이언트는 서버와 거의 동일하다.

단지 connect에 넘겨줄 서버의 hostport를 입력받고, 전송할 문자열을 입력받으면 된다.

## 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