본문 바로가기

23년 2학기 학교공부/컴퓨터네트워크

[CN] Fast HTTP Server

목차

    728x90
    반응형
    SMALL

    HTTP Server의 성능을 높이는 방법

     

    1. backlog 조정

     

    backlog란 ,,

     

    backlog가 낮으면 서버가 왜 느릴까?

     

     

    📁 Apache MPM (Multi-Processing Modules)

     

    Apache 웹 서버의 성능을 높이는 결정적 요소 중 하나이다.

     

     

    multi processing / threading server를 파이썬으로 구현해보자.

     

    1. Multi processing server

    import os
    import socket
    import sys
    import time
    import multiprocessing
    
    # HOST가 공백인 것은 루프백 주소로 자기자신을 의미.
    HOST = ''
    PORT = 8080
    
    def worker(conn, addr) :
        with conn:
            print(f'Connected by {addr}')
            # conn.recv() : 기존의 소켓 s가 아닌, 새로운 소켓 conn으로 수신.
            data = conn.recv(1500)
            ptr = data.find('\r\n'.encode('utf-8'))
            header = data[:ptr]
            left = data[ptr:]
    
            # 표준에서는, header가 utf-8이 아니라 아스키로 디코딩되어야함.
            # 데이터 송신 시 encode()가 이루어지므로 수신하여 출력할 때 decode()가 필요함.
            request = header.decode('utf-8')
    
            if request == "" :
                sys.exit("페이지 종료")
    
            method, path, protocol = request.split(' ')
            print(f'Received: {method} {path} {protocol}')
            if not data:
                return
            if path == '/':
                path = '/index.html'
            path = f'.{path}'
    
            # os.path.splitext(path)[1] : path의 확장자를 얻어옴
            # 위에서 얻은 확장자로 contentType에 들어갈 문자열 배정
            contentType = ""
            extension = os.path.splitext(path)[1]
    
            if extension == '.html' :
                contentType = "text/html"
            elif extension == '.css' :
                contentType = "text/css"
            elif extension == '.js' :
                contentType = "text/javascript"
            elif extension == '.jpg' :
                contentType = "image/jpeg"
            elif extension == '.png' :
                contentType = "image/png"
    
            # path가 존재하지 않는 경우 404 Not Found.
            if not os.path.exists(path):
                header = 'HTTP/1.1 404 Not Found\r\n'
                header = f'{header}Server: Our server\r\n'
                header = f'{header}Connection: close\r\n'
                header = f'{header}Content-Type: {contentType};charset=utf-8\r\n'
                header = f'{header}\r\n'
                header = header.encode('utf-8')
                body = ''.encode('utf-8')
    
            # 만약 그림파일일 경우 바이너리 모드로 읽어와야 하므로 'rb' 사용.
            else:
                if extension == '.jpg' :
                    with open(path, 'rb') as f:
                        body = f.read()
                    
                    # 성공적으로 읽어왔으므로 200 OK
                    # Content-Type에 위에서 미리 설정한 문자열 배치.
                    header = 'HTTP/1.1 200 OK\r\n'
                    header = f'{header}Server: Our server\r\n'
                    header = f'{header}Connection: close\r\n'
                    header = f'{header}Content-Type: {contentType}\r\n'
                    header = f'{header}Content-Length: {len(body)}\r\n'
                    header = f'{header}\r\n'
                    header = header.encode('utf-8')
                
                elif extension == '.png' :
                    with open(path, 'rb') as f:
                        body = f.read()
                    
                    # 성공적으로 읽어왔으므로 200 OK
                    # Content-Type에 위에서 미리 설정한 문자열 배치.
                    header = 'HTTP/1.1 200 OK\r\n'
                    header = f'{header}Server: Our server\r\n'
                    header = f'{header}Connection: close\r\n'
                    header = f'{header}Content-Type: {contentType}\r\n'
                    header = f'{header}Content-Length: {len(body)}\r\n'
                    header = f'{header}\r\n'
                    header = header.encode('utf-8')
                    
                else :
                    with open(path, 'r', encoding="UTF8") as f:
                        body = f.read()
                        body = body.encode('utf-8')
                        
                    header = 'HTTP/1.1 200 OK\r\n'
                    header = f'{header}Server: Our server\r\n'
                    header = f'{header}Connection: close\r\n'
                    header = f'{header}Content-Type: {contentType}\r\n'
                    header = f'{header}Content-Length: {len(body)}\r\n'
                    header = f'{header}\r\n'
                    # 데이터를 송신할 때 encode가 필요함.
                    # 파이썬 내부에서 생성된 문자열은 파이썬에서 생성된 객체이므로 전송 계층에 문자열을 그대로 보내면 오류가 발생한다.
                    header = header.encode('utf-8')
    
            # sendall() : 데이터 송신.
            response = header + body
            conn.sendall(response)
    
    
    if __name__ == '__main__' :
        # socket.socket() : 소켓 네트워크 통신을 위해서 소켓을 생성.
        # with~as~ : 파일 및 소켓을 with as 구문으로 생성하면 close()를 호출하지 않아도 소멸 가능
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # bind() : 소켓을 포트와 맵핑. OS안의 process가 os에게 포트번호를 쓰겠다고 하는것.
            s.bind((HOST, PORT))
            # listen() : 서버가 클라이언트의 연결 요청을 기다리는 상태.
            s.listen(1024)
            print(f'Start server with {sys.argv}')
            while True:
                try:
                    # accept() : 소켓이 누군가와 접속하여 연결되었을 때 '새로운 소켓'과 상대방의 Address Family를 전달한다.
                    # 이때부터, 클라이언트는 기존의 소켓으로, 서버는 새로운 소켓으로 송수신한다.
                    conn, addr = s.accept()
                    process = multiprocessing.Process(target=worker, args=(conn, addr))
                    process.start()
                    print(f'Start child worker {process}')
    
                except KeyboardInterrupt:
                    print('Shutdown server')
                    for process in multiprocessing.active_children() :
                        print('Terminate process {0}'.format(process))
                        process.terminate()
                        process.join()
                    break

     

    2. Multi threading server

    import os
    import socket
    import sys
    import time
    import threading
    
    # HOST가 공백인 것은 루프백 주소로 자기자신을 의미.
    HOST = ''
    PORT = 8080
    
    def worker(conn, addr) :
        with conn:
            print(f'Connected by {addr}')
            # conn.recv() : 기존의 소켓 s가 아닌, 새로운 소켓 conn으로 수신.
            data = conn.recv(1500)
            ptr = data.find('\r\n'.encode('utf-8'))
            header = data[:ptr]
            left = data[ptr:]
    
            # 표준에서는, header가 utf-8이 아니라 아스키로 디코딩되어야함.
            # 데이터 송신 시 encode()가 이루어지므로 수신하여 출력할 때 decode()가 필요함.
            request = header.decode('utf-8')
    
            if request == "" :
                sys.exit("페이지 종료")
    
            method, path, protocol = request.split(' ')
            print(f'Received: {method} {path} {protocol}')
            if not data:
                return
            if path == '/':
                path = '/index.html'
            path = f'.{path}'
    
            # os.path.splitext(path)[1] : path의 확장자를 얻어옴
            # 위에서 얻은 확장자로 contentType에 들어갈 문자열 배정
            contentType = ""
            extension = os.path.splitext(path)[1]
    
            if extension == '.html' :
                contentType = "text/html"
            elif extension == '.css' :
                contentType = "text/css"
            elif extension == '.js' :
                contentType = "text/javascript"
            elif extension == '.jpg' :
                contentType = "image/jpeg"
            elif extension == '.png' :
                contentType = "image/png"
    
            # path가 존재하지 않는 경우 404 Not Found.
            if not os.path.exists(path):
                header = 'HTTP/1.1 404 Not Found\r\n'
                header = f'{header}Server: Our server\r\n'
                header = f'{header}Connection: close\r\n'
                header = f'{header}Content-Type: {contentType};charset=utf-8\r\n'
                header = f'{header}\r\n'
                header = header.encode('utf-8')
                body = ''.encode('utf-8')
    
            # 만약 그림파일일 경우 바이너리 모드로 읽어와야 하므로 'rb' 사용.
            else:
                if extension == '.jpg' :
                    with open(path, 'rb') as f:
                        body = f.read()
                    
                    # 성공적으로 읽어왔으므로 200 OK
                    # Content-Type에 위에서 미리 설정한 문자열 배치.
                    header = 'HTTP/1.1 200 OK\r\n'
                    header = f'{header}Server: Our server\r\n'
                    header = f'{header}Connection: close\r\n'
                    header = f'{header}Content-Type: {contentType}\r\n'
                    header = f'{header}Content-Length: {len(body)}\r\n'
                    header = f'{header}\r\n'
                    header = header.encode('utf-8')
                
                elif extension == '.png' :
                    with open(path, 'rb') as f:
                        body = f.read()
                    
                    # 성공적으로 읽어왔으므로 200 OK
                    # Content-Type에 위에서 미리 설정한 문자열 배치.
                    header = 'HTTP/1.1 200 OK\r\n'
                    header = f'{header}Server: Our server\r\n'
                    header = f'{header}Connection: close\r\n'
                    header = f'{header}Content-Type: {contentType}\r\n'
                    header = f'{header}Content-Length: {len(body)}\r\n'
                    header = f'{header}\r\n'
                    header = header.encode('utf-8')
                    
                else :
                    with open(path, 'r', encoding="UTF8") as f:
                        body = f.read()
                        body = body.encode('utf-8')
                        
                    header = 'HTTP/1.1 200 OK\r\n'
                    header = f'{header}Server: Our server\r\n'
                    header = f'{header}Connection: close\r\n'
                    header = f'{header}Content-Type: {contentType}\r\n'
                    header = f'{header}Content-Length: {len(body)}\r\n'
                    header = f'{header}\r\n'
                    # 데이터를 송신할 때 encode가 필요함.
                    # 파이썬 내부에서 생성된 문자열은 파이썬에서 생성된 객체이므로 전송 계층에 문자열을 그대로 보내면 오류가 발생한다.
                    header = header.encode('utf-8')
    
            # sendall() : 데이터 송신.
            response = header + body
            conn.sendall(response)
    
    
    if __name__ == '__main__' :
        # socket.socket() : 소켓 네트워크 통신을 위해서 소켓을 생성.
        # with~as~ : 파일 및 소켓을 with as 구문으로 생성하면 close()를 호출하지 않아도 소멸 가능
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # bind() : 소켓을 포트와 맵핑. OS안의 process가 os에게 포트번호를 쓰겠다고 하는것.
            s.bind((HOST, PORT))
            # listen() : 서버가 클라이언트의 연결 요청을 기다리는 상태.
            s.listen(1024)
            print(f'Start server with {sys.argv}')
            while True:
                try:
                    # accept() : 소켓이 누군가와 접속하여 연결되었을 때 '새로운 소켓'과 상대방의 Address Family를 전달한다.
                    # 이때부터, 클라이언트는 기존의 소켓으로, 서버는 새로운 소켓으로 송수신한다.
                    conn, addr = s.accept()
                    thread = threading.Thread(target=worker, args=(conn, addr))
                    thread.start()
                    print(f'Start child worker {thread}')
    
                except KeyboardInterrupt:
                    print('Shutdown server')
                    for thread in threading.enumerate() :
                        if thread.getName() == 'MainThread' :
                            continue
                        print('Join thread {0}'.format(thread))
                        thread.join(timeout = 1)
                    break
    728x90
    반응형
    LIST