본문 바로가기

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

[CN] Socket을 사용해 HTTP Server 열기 (파이썬)

목차

    728x90
    반응형
    SMALL

    📁 Network Socket

    network socket이란 네트워크로 데이터를 송수신하는 컴퓨터의 통신의 접점에 위치한 통신 객체다. 양 쪽의 소켓을 연결함으로써 서로 다른 프로세스끼리 데이터를 전달할 수 있다.

     

    소켓을 이용한 네트워크 프로그래밍은 소켓 연결 및 데이터 송수신을 위한 소켓 함수 몇가지를 사용한다.

     

     

    일반적인 서버의 흐름은 다음과 같다.

     

    1. socket() : 소켓을 생성한다.
    2. bind() : ip와 port번호를 설정하여 소켓에 적용시킨다.
    3. listen() : 클라이언트 요청을 대기하는 상태가 된다. 클라이언트 요청에 대기열을 만들어 몇 개의 클라이언트를 대기시킬지 결정한다.
    4. accept() : 클라이언트와 연결한다. 서버에 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성한다.
    5. send(), recv() : 데이터를 송수신한다.
    6. close() : 소켓을 닫는다.

     

    일반적인 클라이언트의 흐름은 다음과 같다.

     

    1. socket() : 소켓을 생성한다.
    2. connect() : 서버에서 설정한 ip, port번호로 연결한다. 즉 서버에 접속한다.
    3. send(), recv() : 데이터를 송수신한다.
    4. close() : 소켓을 닫는다.

     

     

    소켓은 실용성에 따라 TCP와 UDP로 구분하며, 해당 포스팅에서는 TCP를 사용한다.

    TCP/IP 소켓은 IP와 Port로 프로세스를 구분한다.

     

     

     

     

    📁 HTTP Server 열기

     

    # py3_http_server.py
    
    import http.server
    import socketserver
    
    PORT = 8080
    
    Handler = http.server.SimpleHTTPRequestHandler
    
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        print("serving at port", PORT)
        httpd.serve_forever()

    위 서버는 Python3 표준 라이브러리에서 제공하는 간단한 HTTP Server이다.

     

     

    <!-- sample.html -->
    
    <!DOCTYPE html>
    <html>
    <head>
    
    </head>
    <body>
    
    <h1>This is a heading</h1>
    
    <p>This is a paragraph.</p>
    
    </body>
    </html>

    py3_http_server.py 파일과 같은 경로 내에 위 html 파일을 배치 후 "localhost:8080/sample.html" 링크에 접속하면 아래와 같은 화면을 확인할 수 있다.

     

     

    이때 py3_http_server.py 파일에서 PORT를 8080으로 설정했으므로 8080포트로 접속하고 있음을 알 수 있다.

     

     

    위 경우에는 html 파일의 이름을 "sample.html"로 설정했으므로, url의 포트번호 이후에 파일 이름을 넣어야 접속이 가능하다.

     

    HTTP URL이 "/"인 경우, 이는 "/index.html"을 반환하는 것을 의미한다. 만약 위 sample.html 파일의 이름을 index.html으로 변경하면 "localhost:8080"만으로도 응답을 받을 수 있다.

     

     

     

     

    📁 TCP Communication

    https://digiconfactory.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%86%8C%EC%BC%93socket-%EB%AA%A8%EB%93%88

    # tcp_echo_server.py
    
    import socket
    
    HOST = ''
    PORT = 8080
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen(1)
        print('Start server')
        while True:
            try:
                conn, addr = s.accept()
                with conn:
                    print('Connected by', addr)
                    while True:
                        data = conn.recv(1500)
                        if not data:
                            break
                        conn.sendall(data)
            except KeyboardInterrupt:
                print('Shutdown server')
                break

     

     

     

     

    🌱 코드 구성

    socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    소켓을 이용하여 네트워크 통신을 하기 위해서는 서버 측과 클라이언트 측 모두 소켓을 먼저 생성해야한다. 파이썬에서는 socket 라이브러리의 socket 함수를 이용하여 소켓을 생성할 수 있다.

     

    1) 첫번째 인자 : 패밀리라고 부른다. 소켓의 주소 체계를 결정하며, IP4v의 경우 "AF_INET", IP6v의 경우 "AF_INET6"을 사용한다.

    2) 두번째 인자 : 소켓 타입을 결정한다. SOCK_STREAM이란 TCP 소켓을 생성함을 의미한다.

     

    위 코드에서 설정된 인자값은 socket 함수의 기본 인자값이므로, socket.socket() 과 같은 형태로 소켓을 생성한 것과 같다.

     

     

    s.bind((HOST, PORT))

    위에서 생성한 소켓으로 통신하기 위해서는 소켓을 포트와 맵핑하고 상대측 포트와 연결해야한다. 이때 서버가 소켓을 포트와 맵핑하는 행위를 바인딩이라고 하며, 파이썬에서는 bind() 함수를 이용하여 할 수 있다.

     

    - (HOST, PORT) : bind() 함수에는 호스트 주소와 포트번호를 담은 튜플을 인자로 받는다.

     

     

    s.listen(1)

    서버가 소켓과 포트를 바인딩한 이후에는, 클라이언트 측에서 그 바인딩된 포트로 연결 요청이 올 때까지 기다려야한다. 파이썬에서는 listen() 함수로 클라이언트의 연결을 대기한다.

     

     

    listen() 함수는 클라이언트로부터 연결 요청이 오면 리턴한다.

    conn, addr = s.accept()

    클라이언트로부터 연결 요청이 오면, 서버는 이 연결 요청을 

     

     

    위 코드에서 TCP의 통신 순서는 다음과 같다.

     

    1. bind

    2. listen

    3. accept

    4. recv-send

    5. close

     

     

    # tcp_echo_client.py
    
    import socket
    
    HOST = '127.0.0.1'
    PORT = 8080
    
    text = input('Input text: ').strip()
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((HOST, PORT))
        s.sendall(text.encode('utf-8'))
        data = s.recv(1500)
        data = data.decode('utf-8')
    
    print(f'Received {data}')

     

    위 코드에서 TCP 통신 순서는 다음과 같다.

     

    1. connect

    2. send-recv

    3. close

     

     

     

    📁 HTTP server using Socket

     

    # tcp_http_server.py
    
    import os
    import socket
    
    HOST = ''
    PORT = 8080
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen(1)
        print('Start server')
        while True:
            try:
                conn, addr = s.accept()
                with conn:
                    print(f'Connected by {addr}')
                    data = conn.recv(1500)
                    ptr = data.find('\r\n'.encode('utf-8'))
                    header = data[:ptr]
                    left = data[ptr:]
                    request = header.decode('utf-8')
                    method, path, protocol = request.split(' ')
                    print(f'Received: {method} {path} {protocol}')
                    if not data:
                        break
                    if path == '/':
                        path = '/index.html'
                    path = f'.{path}'
                    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: text/html;charset=utf-8\r\n'
                        header = f'{header}\r\n'
                        header = header.encode('utf-8')
                        body = ''.encode('utf-8')
                    else:
                        with open(path, 'r') 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: text/html;charset=utf-8\r\n'
                        header = f'{header}Content-Length: {len(body)}\r\n'
                        header = f'{header}\r\n'
                        header = header.encode('utf-8')
                    response = header + body
                    conn.sendall(response)
    
            except KeyboardInterrupt:
                print('Shutdown server')
                break

     

    <!-- index.html -->
    
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>내 TCP 웹 서버 테스트</title>
      </head>
      <body>
        <h1>202301234 성이름</h1>
        <h2></h2>
    
      </body>
    </html>

     

     

    728x90
    반응형
    LIST