본문 바로가기

app/python

[python] 서버를 만들어보자(1) echo-server

소켓

 

프로그램이 네트워크에서 데이터를 송수신할 수 있도록, “네트워크 환경에 연결할 수 있게 만들어진 연결부”가 소켓이며, 소켓은 일반적으로 클라이언트 및 서버 상호작용에 사용됩니다.  

 

보통 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, 이를 "TCP 소켓" 또는 "TCP/IP 소켓"이라고 부릅니다. (UDP에서 동작하는 소켓은 "UDP 소켓"이라고 합니다.)

 

 - 해당 포스트에서는  TCP 소켓을 다룹니다. 

 

소켓의 실행 흐름

 

 

서버 

1.  socket()  API가 통신을 위한 종료점을 작성하고 소켓 객체를 리턴한다. 
2. Bind()     네트워크 엑세스를 위해 소켓에 바인드 한다. 

3. Listen()  서버가 연결을 수락하도록 합니다.

4. accept() 연결을 받아들입니다. 소켓은 주소에 바인드 되어 연결을 리스닝하고 있어야 합니다. 

5. Send()    소켓에 데이터를 보냅니다. 

   Recv()    소켓에서 데이터를 수신합니다. 

6. close()  소켓 파일 기술자를 닫습니다.

 

클라이언트

1. socket()  API가 통신을 위한 종료점을 작성하고 소켓 객체를 리턴한다. 

2. connect() 파라미터 값인 address에 있는 원격 소켓에 연결합니다.

3. Send()    소켓에 데이터를 보냅니다. 

    Recv()    소켓에서 데이터를 수신합니다. 

4. close()  소켓 파일 기술자를 닫습니다.

 

 

파이썬 소켓 프로그래밍

파이썬 소켓을 통해 에코 서버를 만들면 다음과 같습니다. 

 

Echo-server

# -*- coding: utf-8 -*-
import socket

HOST = '127.0.0.1'
PORT = 65432

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    while True:
        conn, addr = s.accept()
        with conn:
            print('connected by', addr)
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                conn.sendall(data)

 

socket.socket() :

새로운 소켓 객체를 만듭니다. 

socket.AF_INET : 2 (상수값), IPv4 인터넷 프로토콜 (IPv6는 AF_INET6)

Socket.SOCK_STREAM : 1 (상수값) , 스트림, TCP 프로토콜 전송방식 ( UDP는 SOCK_DGRAM)

즉 socket로 소켓 객체를 생성하는데 ipv4의 프로토콜과 TCP통신을 한다고 선언하였습니다. 

 

bind()

해당 소켓 객체로 .bind(ip, port)를 통해 소켓에 주소, 프로토콜, 포트를 할당합니다. 

클라이언트가 서버에 접속하기 위해서 서버의 소켓은 지정된 내부 주소와 포트를 가지고 있어야 하는데 이를 bind()함수가 해줍니다.

클라이언트가 서버의 주소를 connect()함수를 이용해 제공하는데, 서버는 자신의 주소를 bind()에 알려주어야 합니다.

 

Listen() 

서버가 연결을 수락하도록 합니다.

소켓에 들어오는 연결 시도를 듣도록 합니다. 파라미터는 대기되는 연결의 최대수이며 시스템에 따라 다르지만 보통 5가 최댓값입니다. 

 

 

Accept() 

연결을 수락합니다.  bind와 listen이 미리 되어 있어야 하며 반환값은 튜플 형식의 연결된 새 소켓 객체와 상대편 주소입니다. 

동기입니다!! 소켓으로 연결이 있을 때까지 대기합니다. 즉 block 되어 다른 행동은 할 수 없습니다.  (이에 웹 프레임워크에서는 스레딩 or 멀티 프로세스로 처리합니다. )

여기서 conn는 연결에서 데이터를 보내고 받을 수 있는 새로운 소켓 객체이고, address는 연결의 다른 끝에 있는 소켓에 바인드 된 주소입니다.

 

Recv() 

소켓으로 수신된 데이터를 받습니다. 

버퍼 사이즈를 통해 한번에 받게 되는 데이터의 최대 양을 설정합니다. 

 

sendall()

소켓으로 데이터를 송신합니다. 

 

 

echo-client

# -*- coding: utf-8 -*-
import socket

HOST = '127.0.0.1'
PORT = 65432

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b'hello, world')
    data = s.recv(1024)

print('received', repr(data))

 

Connect()

address(host, port)로 원격 소켓에 연결합니다. 

 

 

Pycallgraph를 통해 본 흐름도

 

서버의 실행 흐름도 

 

 

 

클라이언트 흐름도 

 

흐름도를 보면 socket를 통해 accept()와 close() 부분을 볼 수 있습니다.  (나머지 부분은 왜 안 나올까요??ㅎㅎ)

 

중요한 점은 소켓을 통해 TCP / UDP 통신을 애플리케이션단에서 행할 수 있으며, 소켓 위에 다양한 프로토콜을 통해 서로간의 통신을 할수 있습니다. 

 

또한 모든 통신(유닉스 계열)은 fd(file descripter)를 통해 전달되며, 서버에서 accept()를 통해 클라이언트에 메시지를 전달할 때에도 새로운 fd를 생성하여 통신하게 됩니다. ( 입/출력이 있다면 fd가 쓰였다고 보면 됩니다. )

 

소켓 객체 생성시엔 fd6번이 accept()엔 fd7번이 사용됨을 볼 수 있습니다. 

참고

 

https://docs.python.org/ko/3/library/socket.html

 

https://www.ibm.com/support/knowledgecenter/ko/ssw_ibm_i_73/rzab6/howdosockets.htm

 

https://docs.python.org/ko/3/howto/sockets.html

 

https://www.slideshare.net/dahlmoon/20160210

 

https://docs.python.org/ko/3/howto/sockets.html

 

https://12bme.tistory.com/228