목차
📁 Network Socket
network socket이란 네트워크로 데이터를 송수신하는 컴퓨터의 통신의 접점에 위치한 통신 객체다. 양 쪽의 소켓을 연결함으로써 서로 다른 프로세스끼리 데이터를 전달할 수 있다.
소켓을 이용한 네트워크 프로그래밍은 소켓 연결 및 데이터 송수신을 위한 소켓 함수 몇가지를 사용한다.
일반적인 서버의 흐름은 다음과 같다.
- socket() : 소켓을 생성한다.
- bind() : ip와 port번호를 설정하여 소켓에 적용시킨다.
- listen() : 클라이언트 요청을 대기하는 상태가 된다. 클라이언트 요청에 대기열을 만들어 몇 개의 클라이언트를 대기시킬지 결정한다.
- accept() : 클라이언트와 연결한다. 서버에 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성한다.
- send(), recv() : 데이터를 송수신한다.
- close() : 소켓을 닫는다.
일반적인 클라이언트의 흐름은 다음과 같다.
- socket() : 소켓을 생성한다.
- connect() : 서버에서 설정한 ip, port번호로 연결한다. 즉 서버에 접속한다.
- send(), recv() : 데이터를 송수신한다.
- 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
# 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>