socketserver --- 네트워크 서버를 위한 프레임워크

소스 코드: Lib/socketserver.py


socketserver 모듈은 네트워크 서버 작성 작업을 단순화합니다.

다음과 같은 네 가지 기본 구상 서버 클래스가 있습니다:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

클라이언트와 서버 간에 연속적인 데이터 스트림을 제공하는 인터넷 TCP 프로토콜을 사용합니다. bind_and_activate가 참이면, 생성자는 자동으로 server_bind()server_activate()를 호출하려고 시도합니다. 다른 매개 변수는 BaseServer 베이스 클래스로 전달됩니다.

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

순서가 잘못되거나 전송 중 손실될 수 있는 이산적 정보 패킷인 데이터 그램을 사용합니다. 매개 변수는 TCPServer와 같습니다.

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)

TCP와 UDP 클래스와 비슷하지만, 유닉스 도메인 소켓을 사용하는 자주 사용되지 않는 클래스입니다; 유닉스 이외의 플랫폼에서는 사용할 수 없습니다. 매개 변수는 TCPServer와 같습니다.

이 네 가지 클래스는 동기적으로 (synchronously) 요청을 처리합니다; 다음 요청을 시작하기 전에 각 요청을 완료해야 합니다. 계산이 많이 필요하거나 클라이언트가 처리하는 속도가 느리도록 데이터를 많이 반환하기 때문에 각 요청을 완료하는 데 시간이 오래 걸리면 적합하지 않습니다. 해결책은 각 요청을 처리하기 위해 별도의 프로세스나 스레드를 만드는 것입니다; ForkingMixInThreadingMixIn 믹스인 클래스를 사용하여 비동기 동작을 지원할 수 있습니다.

서버를 만들려면 몇 가지 단계가 필요합니다. 먼저, BaseRequestHandler 클래스를 서브 클래싱하고 handle() 메서드를 재정의하여 요청 처리기 클래스를 만들어야 합니다; 이 메서드는 들어오는 요청을 처리합니다. 둘째, 서버 주소와 요청 처리기 클래스를 전달하여 서버 클래스 중 하나를 인스턴스 화해야 합니다. with 문에서 서버를 사용하는 것이 좋습니다. 그런 다음 서버 객체의 handle_request()serve_forever() 메서드를 호출하여 하나 이상의 요청을 처리합니다. 마지막으로, (with 문을 사용하지 않았다면) server_close()를 호출하여 소켓을 닫습니다.

스레드 연결 동작을 위해 ThreadingMixIn에서 상속할 때, 갑작스러운 종료 시 스레드 작동 방식을 명시적으로 선언해야 합니다. ThreadingMixIn 클래스는 서버가 스레드 종료를 기다려야 하는지를 가리키는 daemon_threads 어트리뷰트를 정의합니다. 스레드가 자율적으로 동작하게 하려면 플래그를 명시적으로 설정해야 합니다; 기본값은 False인데, ThreadingMixIn으로 만들어진 모든 스레드가 종료될 때까지 파이썬이 종료되지 않음을 뜻합니다.

서버 클래스는 사용하는 네트워크 프로토콜과 관계없이 같은 외부 메서드와 어트리뷰트를 갖습니다.

서버 생성 노트

상속 다이어그램에는 5개의 클래스가 있으며, 그중 4개는 4가지 유형의 동기 서버를 나타냅니다:

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+

UnixDatagramServerUnixStreamServer가 아니라 UDPServer에서 파생됨에 유의하십시오 --- IP와 유닉스 스트림 서버의 유일한 차이점은 주소 패밀리이며, 두 유닉스 서버 클래스 모두에서 단순히 반복됩니다.

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn

이러한 믹스인 클래스를 사용하여 각 서버 유형의 포킹(forking)과 스레딩(threading) 버전을 만들 수 있습니다. 예를 들어, ThreadingUDPServer는 다음과 같이 만듭니다:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

UDPServer에 정의된 메서드를 재정의하므로, 믹스인 클래스가 먼저 옵니다. 다양한 어트리뷰트를 설정하면 하부 서버 메커니즘의 동작도 변경됩니다.

아래 언급된 ForkingMixIn과 Forking 클래스들은 fork()를 지원하는 POSIX 플랫폼에서만 사용할 수 있습니다.

socketserver.ForkingMixIn.server_close()socketserver.ForkingMixIn.block_on_close 어트리뷰트가 거짓인 경우를 제외하고 모든 자식 프로세스가 완료될 때까지 대기합니다.

socketserver.ThreadingMixIn.server_close()socketserver.ThreadingMixIn.block_on_close 어트리뷰트가 거짓인 경우를 제외하고 모든 비 데몬 스레드가 완료될 때까지 대기합니다. 스레드가 완료될 때까지 기다리지 않도록 하려면 ThreadingMixIn.daemon_threadsTrue로 설정하여 데몬 스레드를 사용하십시오.

버전 3.7에서 변경: socketserver.ForkingMixIn.server_close()socketserver.ThreadingMixIn.server_close()는 이제 모든 자식 프로세스와 비 데몬 스레드가 완료될 때까지 대기합니다. 3.7 이전의 동작을 옵트인 하기 위해 새 socketserver.ForkingMixIn.block_on_close 클래스 어트리뷰트를 추가합니다.

class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer

이 클래스들이 믹스인 클래스를 사용하여 미리 정의됩니다.

서비스를 구현하려면, BaseRequestHandler에서 클래스를 파생시키고 handle() 메서드를 재정의해야 합니다. 그런 다음 서버 클래스 중 하나와 여러분의 요청 처리기 클래스를 결합하여 다양한 버전의 서비스를 실행할 수 있습니다. 요청 처리기 클래스는 데이터 그램과 스트림 서비스에서 달라야 합니다. 처리기 서브 클래스 StreamRequestHandlerDatagramRequestHandler를 사용하여 이 차이를 숨길 수 있습니다.

물론, 여전히 머리를 사용해야 합니다! 예를 들어, 서비스가 다른 요청으로 수정될 수 있는 메모리상의 상태를 포함할 때 포킹 서버를 사용하는 것은 의미가 없습니다. 자식 프로세스에의 수정은 부모 프로세스에 유지된 초기 상태에 도달하여 각 자식에 전달되지 못하기 때문입니다. 이 경우, 스레딩 서버를 사용할 수 있지만, 아마도 공유 데이터의 무결성을 보호하기 위해 록을 사용해야 합니다.

반면, 모든 데이터가 외부(예를 들어, 파일 시스템)에 저장되는 HTTP 서버를 구축한다면, 동기 클래스는 하나의 요청이 처리되는 동안 실질적으로 서비스가 "듣지 못하게" 만듭니다 -- 클라이언트가 요청한 모든 데이터를 받는 속도가 느리다면 매우 오랜 시간이 걸릴 수 있습니다. 이럴 때는 스레딩이나 포킹 서버가 적합합니다.

때에 따라, 요청의 일부를 동기적으로 처리하는 것이 좋지만, 요청 데이터에 따라 포크 된 자식에서 처리를 완료하는 것이 적절할 수 있습니다. 이는 동기 서버를 사용하고 요청 처리기 클래스 handle() 메서드에서 명시적 포크를 수행하여 구현할 수 있습니다.

스레드도 fork()도 지원하지 않는 (또는 이것들이 서비스에 너무 비싸거나 부적절한) 환경에서 여러 동시 요청을 처리하는 또 다른 방법은 부분적으로 완료된 요청의 명시적인 테이블을 유지하고 selectors를 사용하여 다음에 작업할 요청을 (또는 새로 들어온 요청을 처리할지를) 결정하는 것입니다. 이는 (스레드나 서브 프로세스를 사용할 수 없다면) 각 클라이언트가 오랫동안 연결될 수 있는 스트림 서비스에 특히 중요합니다. 이를 관리하는 다른 방법은 asyncore를 참조하십시오.

서버 객체

class socketserver.BaseServer(server_address, RequestHandlerClass)

이것은 모듈에 있는 모든 서버 객체의 슈퍼 클래스입니다. 아래에 주어진 인터페이스를 정의하지만, 대부분의 메서드를 구현하지 않고, 서브 클래스에서 구현됩니다. 두 개의 매개 변수는 각각 server_addressRequestHandlerClass 어트리뷰트에 저장됩니다.

fileno()

서버가 리스닝 중인 소켓의 정수 파일 디스크립터를 반환합니다. 이 함수는 가장 일반적으로 같은 프로세스에서 여러 서버를 모니터링할 수 있도록 selectors에 전달됩니다.

handle_request()

단일 요청을 처리합니다. 이 함수는 다음 메서드들을 차례로 호출합니다: get_request(), verify_request()process_request(). 처리기 클래스의 사용자 제공 handle() 메서드에서 예외가 발생하면, 서버의 handle_error() 메서드가 호출됩니다. timeout 초 내에 요청이 수신되지 않으면, handle_timeout()이 호출되고 handle_request()는 반환합니다.

serve_forever(poll_interval=0.5)

명시적인 shutdown() 요청이 있을 때까지 요청을 처리합니다. poll_interval 초마다 shutdown을 확인합니다. timeout 어트리뷰트를 무시합니다. 또한 service_actions()를 호출하는데, 서브 클래스나 믹스인이 주어진 서비스에 특정한 동작을 제공하기 위해 사용할 수 있습니다. 예를 들어, ForkingMixIn 클래스는 service_actions()를 사용하여 좀비 자식 프로세스를 정리합니다.

버전 3.3에서 변경: serve_forever 메서드에 service_actions 호출을 추가했습니다.

service_actions()

serve_forever() 루프에서 호출됩니다. 이 메서드는 서브 클래스나 믹스인 클래스에서 재정의되어 정리 조치와 같은 지정된 서비스에 특정한 조치를 수행할 수 있습니다.

버전 3.3에 추가.

shutdown()

serve_forever() 루프가 정지하도록 하고 정지할 때까지 기다립니다. serve_forever()가 다른 스레드에서 실행되는 동안 shutdown()을 호출해야 합니다. 그렇지 않으면 교착 상태가 됩니다.

server_close()

서버를 정리합니다. 재정의될 수 있습니다.

address_family

서버 소켓이 속한 프로토콜 패밀리. 일반적인 예는 socket.AF_INETsocket.AF_UNIX입니다.

RequestHandlerClass

사용자 제공 요청 처리기 클래스; 요청마다 이 클래스의 인스턴스가 만들어집니다.

server_address

서버가 리스닝 중인 주소. 주소 형식은 프로토콜 패밀리에 따라 다릅니다; 자세한 내용은 socket 모듈 설명서를 참조하십시오. 인터넷 프로토콜의 경우, 주소를 제공하는 문자열과 정수 포트 번호를 포함하는 튜플입니다: ('127.0.0.1', 80), 예를 들어.

socket

서버가 들어오는 요청을 리스닝 할 소켓 객체.

서버 클래스는 다음 클래스 변수를 지원합니다:

allow_reuse_address

서버가 주소를 재사용하도록 허락하는지 여부. 기본값은 False이며, 정책을 변경하기 위해 서브 클래스에서 설정할 수 있습니다.

request_queue_size

요청 큐의 크기. 단일 요청을 처리하는 데 시간이 오래 걸리면, 서버가 바쁠 때 도착한 요청은 최대 request_queue_size 요청까지 큐에 배치됩니다. 큐가 가득 차면, 클라이언트의 추가 요청은 "연결 거부(Connection denied)" 에러를 받게 됩니다. 기본값은 일반적으로 5이지만, 서브 클래스가 재정의할 수 있습니다.

socket_type

서버가 사용하는 소켓의 유형. socket.SOCK_STREAMsocket.SOCK_DGRAM은 두 가지 흔한 값입니다.

timeout

초 단위의 시간제한 기간, 또는 시간제한이 필요하지 않으면 None. handle_request()가 timeout 기간 내에 들어오는 요청을 받지 못하면, handle_timeout() 메서드가 호출됩니다.

TCPServer와 같은 베이스 서버 클래스의 서브 클래스가 재정의할 수 있는 다양한 서버 메서드가 있습니다; 이러한 메서드는 서버 객체의 외부 사용자에게는 유용하지 않습니다.

finish_request(request, client_address)

RequestHandlerClass를 인스턴스화하고 handle() 메서드를 호출하여 실제로 요청을 처리합니다.

get_request()

소켓으로부터의 요청을 받아들이고, 클라이언트와 통신하는 데 사용될 소켓 객체와 클라이언트 주소를 포함하는 2-튜플을 반환해야 합니다.

handle_error(request, client_address)

RequestHandlerClass 인스턴스의 handle() 메서드에서 예외가 발생하면 이 함수가 호출됩니다. 기본 액션은 표준 에러로 트레이스백을 인쇄하고 추가 요청을 계속 처리하는 것입니다.

버전 3.6에서 변경: 이제 Exception 클래스에서 파생된 예외에 대해서만 호출됩니다.

handle_timeout()

이 함수는 timeout 어트리뷰트가 None 이외의 값으로 설정되고 요청이 수신되지 않은 채로 시간제한 기간이 지나면 호출됩니다. 포킹 서버에서의 기본 액션은 종료한 모든 자식 프로세스의 상태를 수집하는 것이고, 반면에 스레딩 서버에서는 이 메서드가 아무 작업도 수행하지 않습니다.

process_request(request, client_address)

finish_request()를 호출하여 RequestHandlerClass 의 인스턴스를 만듭니다. 원한다면, 이 함수는 요청을 처리하기 위해 새 프로세스나 스레드를 만들 수 있습니다; ForkingMixInThreadingMixIn 클래스가 그렇게 합니다.

server_activate()

서버를 활성화하기 위해 서버의 생성자가 호출합니다. TCP 서버의 기본 동작은 단지 서버의 소켓에 대해 listen()을 호출합니다. 재정의될 수 있습니다.

server_bind()

소켓을 원하는 주소에 바인딩하기 위해 서버의 생성자가 호출합니다. 재정의될 수 있습니다.

verify_request(request, client_address)

불리언 값을 반환해야 합니다; 값이 True이면, 요청이 처리되고, False이면, 요청이 거부됩니다. 서버에 대한 액세스 제어를 구현하기 위해 이 함수를 재정의할 수 있습니다. 기본 구현은 항상 True를 반환합니다.

버전 3.6에서 변경: 컨텍스트 관리자 프로토콜에 대한 지원이 추가되었습니다. 컨텍스트 관리자를 벗어나는 것은 server_close()를 호출하는 것과 동등합니다.

요청 처리기 객체

class socketserver.BaseRequestHandler

이것은 모든 요청 처리기 객체의 슈퍼 클래스입니다. 아래에 주어진 인터페이스를 정의합니다. 구상 요청 처리기 서브 클래스는 새 handle() 메서드를 정의해야 하며, 다른 메서드를 재정의할 수 있습니다. 각 요청에 대해 서브 클래스의 새 인스턴스가 만들어집니다.

setup()

필요한 초기화 액션을 수행하기 위해 handle() 메서드 전에 호출됩니다. 기본 구현은 아무것도 수행하지 않습니다.

handle()

이 함수는 요청을 서비스하는 데 필요한 모든 작업을 수행해야 합니다. 기본 구현은 아무것도 수행하지 않습니다. 몇 가지 인스턴스 어트리뷰트를 사용할 수 있습니다; 요청은 self.request로 제공됩니다; 클라이언트 주소는 self.client_address로 제공됩니다; 서버별 정보에 액세스해야 하는 경우를 위해 서버 인스턴스는 self.server로 제공됩니다.

self.request의 형은 데이터 그램과 스트림 서비스에서 다릅니다. 스트림 서비스의 경우, self.request는 소켓 객체입니다; 데이터 그램 서비스의 경우, self.request는 문자열과 소켓 쌍입니다.

finish()

필요한 정리 액션을 수행하기 위해 handle() 메서드 이후에 호출됩니다. 기본 구현은 아무것도 수행하지 않습니다. setup()에서 예외가 발생하면, 이 함수가 호출되지 않습니다.

class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler

BaseRequestHandler 서브 클래스는 setup()finish() 메서드를 재정의하고, self.rfileself.wfile 어트리뷰트를 제공합니다. 요청 데이터를 가져오거나 클라이언트로 데이터를 반환하기 위해 self.rfileself.wfile 어트리뷰트를 각각 읽거나 쓸 수 있습니다.

두 클래스의 rfile 어트리뷰트는 io.BufferedIOBase 읽기 가능 인터페이스를 지원하고, DatagramRequestHandler.wfileio.BufferedIOBase 쓰기 가능 인터페이스를 지원합니다.

버전 3.6에서 변경: StreamRequestHandler.wfileio.BufferedIOBase 쓰기 가능 인터페이스를 지원합니다.

socketserver.TCPServer

이것은 서버 쪽입니다:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    서버의 요청 처리기 클래스.

    서버 연결마다 한 번 인스턴스화되며, 클라이언트와의 통신을 구현하기 위해 handle()
    메서드를 재정의해야 합니다.
    """

    def handle(self):
        # self.request는 클라이언트에 연결된 TCP 소켓입니다
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # 단지 같은 데이터를 다시 보내지만, 대문자로 변환합니다
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # 포트 9999에서 localhost에 바인딩하여 서버를 만듭니다
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        # 서버를 활성화합니다; Ctrl-C를 사용하여 프로그램을 중단할 때까지 계속
        # 실행됩니다.
        server.serve_forever()

스트림(표준 파일 인터페이스를 제공하여 통신을 단순화하는 파일류 객체)을 사용하는 대체 요청 처리기 클래스:

class MyTCPHandler(socketserver.StreamRequestHandler):

    def handle(self):
        # self.rfile은 처리기에 의해 만들어진 파일류 객체입니다;
        # 이제 예를 들어 원시 recv() 호출 대신 readline()을 사용할 수 있습니다
        self.data = self.rfile.readline().strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # 마찬가지로, self.wfile은 클라이언트로 다시 쓰는 데 사용되는 파일류 객체입니다
        self.wfile.write(self.data.upper())

차이점은 첫 번째 처리기에서는 단일 recv() 호출이 클라이언트에서 한 번의 sendall() 호출로 보낸 것을 반환하는 반면, 두 번째 처리기의 readline() 호출은 줄 바꿈 문자를 만날 때까지 recv()를 여러 번 호출한다는 것입니다.

이것은 클라이언트 쪽입니다:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# 소켓을 만듭니다 (SOCK_STREAM은 TCP 소켓을 의미합니다)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    # 서버에 연결하고 데이터를 전송합니다
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))

    # 서버에서 데이터를 수신하고 종료합니다
    received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

예제의 결과는 다음과 같아야 합니다:

서버:

$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'

클라이언트:

$ python TCPClient.py hello world with TCP
Sent:     hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent:     python is nice
Received: PYTHON IS NICE

socketserver.UDPServer

이것은 서버 쪽입니다:

import socketserver

class MyUDPHandler(socketserver.BaseRequestHandler):
    """
    self.request가 데이터와 클라이언트 소켓의 쌍으로 구성되고 연결이 없기 때문에 sendto()를
    통해 데이터를 다시 보낼 때 클라이언트 주소를 명시적으로 제공해야 한다는 점을 제외하면, 이
    클래스는 TCP 처리기 클래스와 유사하게 작동합니다.
    """

    def handle(self):
        data = self.request[0].strip()
        socket = self.request[1]
        print("{} wrote:".format(self.client_address[0]))
        print(data)
        socket.sendto(data.upper(), self.client_address)

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
        server.serve_forever()

이것은 클라이언트 쪽입니다:

import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# SOCK_DGRAM은 UDP 소켓으로 사용할 소켓 유형입니다
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 보시다시피, connect() 호출은 없습니다; UDP는 연결이 없습니다.
# 대신, sendto()를 통해 데이터를 수신자에게 직접 보냅니다.
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")

print("Sent:     {}".format(data))
print("Received: {}".format(received))

예제의 출력은 TCP 서버 예제와 정확히 같아야 합니다.

비동기 믹스인

비동기 처리기를 구축하려면, ThreadingMixInForkingMixIn 클래스를 사용하십시오.

ThreadingMixIn 클래스의 예:

import socket
import threading
import socketserver

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        data = str(self.request.recv(1024), 'ascii')
        cur_thread = threading.current_thread()
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
        self.request.sendall(response)

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

def client(ip, port, message):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((ip, port))
        sock.sendall(bytes(message, 'ascii'))
        response = str(sock.recv(1024), 'ascii')
        print("Received: {}".format(response))

if __name__ == "__main__":
    # 포트 0은 임의의 미사용 포트를 선택하는 것을 의미합니다
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # 서버로 스레드를 시작합니다 -- 그런 다음 그 스레드가 요청마다 스레드를 하나씩 더
        # 시작합니다
        server_thread = threading.Thread(target=server.serve_forever)
        # 메인 스레드가 종료할 때 서버 스레드를 종료합니다
        server_thread.daemon = True
        server_thread.start()
        print("Server loop running in thread:", server_thread.name)

        client(ip, port, "Hello World 1")
        client(ip, port, "Hello World 2")
        client(ip, port, "Hello World 3")

        server.shutdown()

예제의 결과는 다음과 같아야 합니다:

$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3

ForkingMixIn 클래스는 서버가 요청마다 새 프로세스를 생성한다는 점을 제외하고 같은 방식으로 사용됩니다. fork()를 지원하는 POSIX 플랫폼에서만 사용 가능합니다.