소켓프로그래밍 구현 (1)

지환·2022년 9월 26일
0
post-thumbnail

연결하는 양쪽에서 데이터를 주고 받음으로써 네트워크 통신이 이루어진다. 이 때 양쪽에서 서로 통신 서비스를 담당하는 것을 종착점 (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) 스무디코딩

출처 | https://smoothiecoding.kr/python-socket-programming/

profile
아는만큼보인다.

0개의 댓글