목차
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