2010년 8월 1일 일요일

SocketServer Thread 구현.

프로그래밍 언어로 서버를 구현한다는 것은 대단한 기술이다. 왜냐하면 고려해야할 사항들이 너무나도 많기 때문이다. 서버를 구현하기위해서 일차적으로 Socket 라이브러리를 알아야 하고 시그널(Signal)과 데몬(Daemon)구현, 그리고 로깅, 내부 처리 로직(Handler 라고도 한다.)등을 모두 갖춰야지 제대로 된 서버라고 말할수 있다.

하지만 제일 어려운 과제는 서버의 운영으로 외부 접속에 동시접속을 어떻게 구현하느냐가 문제가 된다. 다중접속은 서버의 기본기능이지만 이를 구현하는 것은 쉬운일이 아니어서 실제 구현에 있어 이부분을 집중적으로 연구하고 구현하게 된다. 보통 Fork 방식과 Select 방식등으로 나뉘는데(epoll, kqueue등도 있지만 여기서는 생략~) 이것을 실제로 구현하는 것도 만만치 않다.

Python 2.6 에서도 이러한 서버를 구현하기 위해서 Socket 라이브러리를 제공한다. Python 언어의 특성이 Socket 라이브러리에도 잘 나타나는데, Select 방식을 Thread로 구현해준다.

Socket 클래스의 상속된 'Socket.ThreadingTCPServer' 가 바로 그것이다. 이것을 이용하면 곧바로 서버가 Select 방식으로 구동된다.

Python의 Socket 프로그래밍은 서버의 운영방법과 외부의 요청을 처리하는 Handler 로 나뉜다. 서버의 운영방법은 'Socket' 에서 담당하고 Handler 는 'Socket.BasesRequestHandler'나 'Socket.StreamRequestHandler' 등을 상속받아 사용자가 구현한다.

거두 절미하게 기본 골격을 보자.

[code python]
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import time
import SocketServer

class EchoRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
       data = self.request.recv(1024).strip()
       if data:
          self.request.send(data)
       else:
          self.request.close()

if __name__ == '__main__':
    HOST, PORT = 'localhost', 9999   
    server = SocketServer.ThreadingTCPServer((HOST, PORT), EchoRequestHandler)
    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()
[/code]

'SocketServer.ThreadingTCPServer' 클래스를 사용함으로서 간단하게 Select 서버를 구현한 수 있다. 위의 예제에서 서버운영을 클래스로 다음과 같이 교체해보자.

[code python]
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import time
import SocketServer

class EchoRequestHandler(SocketServer.BaseRequestHandler):
    # Called before the handle()  method to perform any initialization actions required.
    def setup(self):
        print self.client_address[0], 'connected!!'

    def handle(self):
        data = self.request.recv(1024).strip()
        if data:
            self.request.send(data)
        else:
            self.request.close()

    # Called after the handle()  method to perform any clean-up actions required.
    def finish(self):
        self.request.close()

class SocksServer(SocketServer.ThreadingTCPServer):
    def __init__(self, listen_addr):
        SocketServer.ThreadingTCPServer.__init__(self, listen_addr, EchoRequestHandler)
        # Whether the server will allow the reuse of an address.
        self.allow_reuse_address = True
        # 
        self.daemon_threads = True
        # request_queue_size
        self.request_queue_size = 50

    def start(self):
        self.serve_forever()

if __name__ == '__main__':
    HOST, PORT = 'localhost', 9999
    server = SocksServer((HOST, PORT))
    server.start()
[/code]

'ThreadingTCPServer'를 상속받은 'SocksServer' 클래스를 제작한다. 그 안에서 부모클래스의 생성자를 호출하면서 해들러도 같이 지정해준다.

여기서 추가된 내용이 클래스 멤버 변수들인데, 주목해야 할 것은 'request_queue_size' 변수로 이것은 쓰레드의 개수를 지정해주는 것이다. 디폴트로는 5이다.

또, 'EchoRequestHandler' 핸들러들에 'setup','finish' 메소드를 추가했다. 코멘트에도 달았지만 'setup'은 'handle'메소드가 시작되기전에 먼저 호출되는 것이고, 'finish'는 'handle'메소드가 끝나고나서 실행되는 메소드이다.

'EchoRequestHandler' 핸들러 클래스도 'SocketServer.BaseRequestHandler'를 상속받은 것이다. 'SocketServer.BaseRequestHandler' 클래스는 'setup','handle','finish' 메소드가 정의만 되어 있어 이를 오버라이딩(overriding)해서 사용하는 것이다. 이 클래스에는 특징이 있는데 데이터를 주고 받을때 읽은 버퍼의 크기를 정해줘야 한다.

'SocketServer.BaseRequestHandler'는 그야말로 기본적인 소켓 핸들러 클래스이다. 이에 비해서 자주 사용되는 것이 'SocketServer.StreamRequestHandler' 이다. 이 클래스를 쓰면 버퍼 크기를 정하지 않아도 된다.

[code python]
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import time
import SocketServer

class SocksHandler(SocketServer.StreamRequestHandler):
   
    def handle(self):
        self.data = self.rfile.readline().strip()
   
        if self.data:
            print "%s" % str(self.client_address[0])
            print self.data
            self.request.send(self.data.upper())
        else:
            self.request.close()
   
class SocksServer(SocketServer.ThreadingTCPServer):
   
    def __init__(self, listen_addr):
        SocketServer.ThreadingTCPServer.__init__(self, listen_addr, SocksHandler)
        self.allow_reuse_address = True
        self.daemon_threads = True
        self.request_queue_size = 50
   
    def start(self):
        self.serve_forever()

if __name__ == '__main__':
    HOST, PORT = 'localhost', 9999
    obj = SocksServer((HOST, PORT))
    obj.start()
[/code]

이상으로 ThreadingTCPServer를 구현해봤다. 역시나 Python은 간단하게 구현이 가능하구나.


0 개의 댓글:

댓글 쓰기