파이썬 소켓 프로그래밍 | 파이썬 네트워크 2

파이썬 소켓 프로그래밍

파이썬 소켓 프로그래밍

소켓이 무엇인지 개념적 설명에는 꽤 시간이 걸립니다. 사실 소켓은 복잡한 네트워크의 원리를 쉽게 이해하기 위해 만들어진 개념이지만 아이러니하게도 이런 신용어의 도입이 이해를 더 어렵게 할 때가 많습니다.

IT공학적 사고와 일상의 사고에는 거리가 있기 때문일지도 모르겠습니다. 우리가 공대와 인문대를 나누는 것은 사고의 차이가 분명히 있기 때문이죠.

조금 쉽게 접근하기 위해서 소켓을 상상할 때 이 콘센트를 생각하면 됩니다.

지금까지 배운 파이썬은 내컴퓨터 안에서 작동하는 프로그램이었습니다. 이제 컴퓨터 두대를 가지고 통신을 한다고 생각하는 것 입니다. 통신은 저절로 되는게 아니므로 양쪽에 콘센트를 꽂아야 서로 통신이 됩니다.

연결하는 양쪽에서 데이터를 주고 받음으로써 네트워크 통신이 이루어집니다. 이 때 양쪽에서 서로 통신 서비스를 담당하는 것을 종착점 (Endpoint) 이라 하고 소켓(Socket) 이라는 객체가 서비스를 담당합니다.

따라서 파이썬 소켓 프로그래밍이라는 것은 네트워크의 양쪽을 관리하여 서로 통신이 가능하게 하는 코드를 작성하는 것 입니다.

소켓 프로그래밍에서 작성하는 것은 기본적으로 서버 클라이언트 모델인데요. 우리가 인터넷으로 네이버나 구글에서 웹사이트를 다운로드 받고 온라인 채팅, 게임 등에 연결할 수 있는 것은 소켓에서 네트워크를 컨트롤 하기 때문입니다.

물론 네트워크 프로그래밍은 이보다 훨씬 복잡합니다. OSI 7계층이라는 복잡한 네트워크의 작동구조에 대한 별도의 학습이 필요합니다.

이러한 것들을 한번에 이해하는 것은 너무 어렵기 때문에 처음에는 코딩을 직접하면서 체감적으로 배우는 것을 추천합니다.

프로그래밍은 공부가 아니라 연습이라는 코딩도장의 표어를 자주 인용합니다만, 특히 어려운 프로그램일 수록 더 많은 연습이 필요합니다.

대체적으로 머리가 좋고 교육을 받을 사람들이 코딩을 잘합니다만 보통 사람도 연습량을 많이 늘리면 코딩을 잘 할 수 있습니다.

서버 클라이언트 모델

네트워크 통신을 하기 위해서는 서버 측 코드와 클라이언트 측 코드가 필요합니다.

소켓 프로그래밍을 할 때는 소스코드의 위에서부터 아래로 내려오는 순차적인 방식으로 코드와는 다르다는 점에 주의합니다.

특히 서버에 요청을 보내고 데이터를 주고받는 부분은 한쪽이라도 맞지 않으면 작동하지 않습니다.

서버 스크립트

import socket

server = socket.socket()
print('[소켓 생성완료]')
s_name = socket.gethostname()

print('서버 컴퓨터이름:', s_name)
server.bind((s_name, 999))

server.listen(3)
print('서버 리스닝...')

while True:
    client, address = server.accept()
    name = client.recv(1024).decode()
    print('클라이언트와 연결되었습니다,', address, name)

    client.send(bytes('서버에 연결되었습니다', 'utf-8'))
    client.close()

위의 코드는 서버측 코드입니다. 원래는 두대의 컴퓨터가 필요하지만 실습을 위해서는 한개의 컴퓨터로도 가능합니다. 서버에서 프로그래밍을 하기 전에 로컬호스트에서 먼저 실습을 하는 셈입니다.

서버를 구축하는 일은 또 다른 일 입니다. 그러니까 네트워크 프로그래밍이 좀 더 할일이 많습니다. 네트워크 프로그래밍은 한대의 컴퓨터가 아니라 여러대의 컴퓨터를 조작하는 일 입니다.

위의 코드에서는 우선 socket 모듈을 import 하고 있습니다. socket 모듈은 파이썬 기본 패키지 안에 들어있어서 별도 설치할 필요가 없습니다.

socket.socket() 으로 서버의 기본 소켓을 생성합니다.

다음 socket.gethostname()은 현재 컴퓨터 이름을 반환합니다. 네트워크 상에 식별되는 이름입니다.

bind() 에는 TCP 프로토콜로 컴퓨터 이름과 포트번호를 묶습니다. 컴퓨터 이름이라고 하지만 실제로는 IP주소에 포트번호를 열어줍니다. 포트 번호는 TCP 서비스를 구분하기 위해 사용합니다.

한 대의 서버에는 여러개의 포트번호를 사용합니다. 예를들어 웹서비스 http 는 보통 포트번호 80 을 사용합니다. 포트번호는 서버가 서비스의 종류를 구분하는데 사용되기 때무에 겹치면 안됩니다.

윈도우의 명령프롬프트에서 netstat -an 을 입력하면 서버가 생성되서 Listening – 듣고있다는 상태가 체크됩니다. 또 netstat 에는 미리 점유한 포트번호를 볼 수 있습니다. 만약 소켓이 이미 사용중인 포트번호를 사용하려고 하면 오류가 발생합니다.

TCP    127.0.0.1:999      0.0.0.0:0              LISTENING

listening 은 커넥션 개수를 설정합니다. 클라이언트에 따라 서버와 여러가지 작업을 해야하는 경우가 있습니다. 그런 경우 너무 많은 클라이언트가 동시 접속하는 것을 막기 위해서 접속가능한 수를 제한합니다.

서버를 맥도날드의 계산대 숫자라고 하고 클라이언트를 햄버거를 주문하는 손님이라고 하면 listening(3)은 계산대가 3개 열려있는 것 입니다. 나머지는 나중에 계산대가 비었을 때 이용할 수 있습니다.

서버의 자원에도 한계가 있기 때문에 클라이언트를 처리하는 정책을 만들 수 있습니다.

while True 문은 무한루프입니다. 서버가 24시간 돌아가는 것은 기본적으로 무한루프하기 때문입니다. 이제 언제라도 클라이언트의 요청이 오면 답할 수 있는 상태가 되었습니다.

이제 while 문으로 들어가기 전에 클라이언트의 요청을 먼저 보겠습니다.

클라이언트 스크립트

import socket

client = socket.socket()
c_name = socket.gethostname()

client.connect((s_name, 999))

name = input("이름을 입력하세요")
client.send(bytes(name, 'utf-8'))

print(client.recv(1024).decode())

위 코드는 클라이언트 측의 코드입니다. 당연히 서버의 파일과는 분리해야 되고요.

클라이언트 측도 소켓을 만들어줍니다. client 라고 변수에 의미를 적어놓으면 좋습니다. 일반적인 업계 관행은 서버는 s 클라이언트는 c 라는 변수를 사용합니다만, 의미를 부각하기 위해 단어를 다 적었습니다.

이 예제는 localhost 를 사용하므로 connect 메소드에서 서버와 같은 컴퓨터 이름에 접속하지만 원거리 접속을 위해서는 IP나 도메인이름을 사용해서 접속합니다.

IP와 포트번호가 일치해야 한다는 점도 중요합니다. 클라이언트가 접속하려는 서버의 번지수는 맞더라도 서비스가 다르면 접속이 되지 않습니다.

포트번호를 다르게 지정하면 아래와 같은 메시지를 볼 수 있습니다.

ConnectionRefusedError: [WinError 10061] 대상 컴퓨터에서 연결을 거부했으므로 연결하지 못했습니다

클라이언트의 콘솔에서 입력받은 데이터는 문자열입니다. 문자열을 받아서 utf-8 인코딩으로 서버측에 보냅니다. 한편 클라이언트가 서버에서 받는 메소드도 보이는데요. 기본적으로 문자열을 보내고 받는 동작이 이루어지 것입니다. 그러면 다시 서버측으로 돌아가서 while 문을 살펴보겠습니다.

while True:
    client, address = server.accept()
    name = client.recv(1024).decode()
    print('클라이언트와 연결되었습니다,', address, name)

    client.send(bytes('서버에 연결되었습니다', 'utf-8'))
    client.close()

클라이언트에서 전송된 정보를 받습니다. 서버는 while 무한루프로 24시간 대기하고 있는 상태죠. 언제라도 클라이언트의 접속 요청을 받습니다.

메소드를 보면 accept 에서 클라이언트의 정보를 받고 recv 로 클라이언트의 데이터를 해독합니다. 1024는 버퍼 사이즈입니다.

서버측의 메시지를 표시하고 다시 클라이언트에게 메시지를 보냅니다. utf-8형태의 bytes 로 인코딩한 이유는 네트워크상의 전송을 위해서 입니다. 한글 유니코드로 보내는 것입니다.

마지막으로 클라언트와의 접속을 종료합니다. close 를 하지 않으면 서버의 자원 관리에 누수가 생길 수 있습니다. close 로 클라이언트와 접속을 끓어줍니다.

클라이언트와 연결되었습니다, ('127.0.0.1', 10787) 스무디코딩

요약

파이썬 소켓 프로그래밍에 대하여 알아봤습니다. 아래 외부참조의 파이썬 문서에서 말하는 것 처럼 소켓은 거의 모든 곳에서 사용되지만 가장 심하게 오해된 기술 중 하나라고 말합니다.

소켓의 기능을 충분히 활용하기 위해서는 스레딩이나 콜백과 같은 복잡한 기술들도 필요합니다.

도전적인 기술이지만 열정을 가지고 노력한다면 분명 멋진 네트워크 프로그램을 만들 수 있을 것입니다.

외부참조문서

소켓 프로그래밍 HOWTO — Python 3.9.1 문서

Leave a Comment