9.12. UDP 소켓¶
Python에서 UDP 트래픽은 데이터그램 소켓의 두 메서드로 송수신합니다. 선택한 목적지로 데이터그램을 발사하는 sendto() 와, 데이터그램을 받고 그것이 어디서 왔는지 알아내는 recvfrom() 입니다. 각 호출은 하나의 자체 완결적인 메시지를 옮기며, 연결 상태는 없습니다.
9.12.1. 데이터그램 보내기¶
가장 단순한 UDP 전송은 소켓 생성자 위에 한 줄의 Python으로 이루어집니다:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"hello", ("192.168.1.20", 9000))
s.close()
이 코드는 192.168.1.20 의 포트 9000 으로 b"hello" 를 보내고 그냥 떠납니다. MicroPython이 임시(ephemeral) 소스 포트를 고르므로, 스크립트는 아무것도 바인딩할 필요가 없습니다.
동일한 페이로드를 여러 목적지로 보내는 것은 그저 루프일 뿐입니다. 소켓은 전송 사이에 재사용 가능하며, 설정할 연결이 없습니다:
targets = [
("192.168.1.20", 9000),
("192.168.1.21", 9000),
("192.168.1.22", 9000),
]
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
for addr in targets:
s.sendto(b"hello", addr)
9.12.2. 데이터그램 받기¶
데이터그램을 받으려면 소켓이 송신자가 목적지로 사용할 알려진 포트를 점유해야 합니다. 그것이 bind() 호출입니다:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
data, src = s.recvfrom(1024)
print("from", src, "got", data)
"0.0.0.0" 주소는 “카메라의 모든 IPv4 인터페이스”를 의미합니다. 어떤 Wi-Fi 또는 이더넷 인터페이스가 패킷을 들여오든, 포트 9000 은 이 소켓에 속합니다.
recvfrom() 의 1024 인자는 반환되는 버퍼로 읽어 들일 최대 바이트 수입니다. 이 크기를 초과하는 UDP 데이터그램은 잘립니다. 애플리케이션이 예상하는 가장 큰 데이터그램에 맞춰 값을 고르세요.
recvfrom() 은 (data, src) 를 반환합니다. 받은 바이트와 송신자의 주소입니다. 송신자의 주소는 응답할 대상이므로, 작은 요청/응답 프로토콜을 작성하기 쉽습니다:
while True:
request, src = s.recvfrom(1024)
if request == b"ping":
s.sendto(b"pong", src)
기본적으로 recvfrom() 은 데이터그램이 도착할 때까지 블록 됩니다. 블록되지 않도록 만드는 패턴들(타임아웃, 논블로킹 소켓, asyncio)은 asyncio를 이용한 소켓 에 있습니다.
9.12.3. 요청과 응답¶
두 개의 짧은 스크립트: 하나는 요청을 보내고, 하나는 받아서 응답합니다.
수신자:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("0.0.0.0", 9000))
while True:
req, src = s.recvfrom(64)
print("got", req, "from", src)
s.sendto(b"ack: " + req, src)
송신자:
import socket
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(2.0) # 2 s reply window
s.sendto(b"ping", ("192.168.1.20", 9000))
try:
reply, _ = s.recvfrom(64)
print("reply:", reply)
except OSError:
print("no reply in 2 s -- packet lost?")
송신자에서 주목할 만한 몇 가지:
settimeout()은 수신 호출에 마감 시한을 둡니다. 2초 안에 응답이 도착하지 않으면, 호출은 영원히 블록되는 대신OSError를 발생시킵니다. 손실된 패킷을 감지하는 자연스러운 방법입니다.with블록은 소켓을 자동으로 닫습니다.
9.12.4. 데이터그램 크기 한계¶
UDP 데이터그램은 이론적으로 최대 약 64 KB까지 가능하지만, 실제 한계는 훨씬 작습니다. 송신자와 수신자 사이 경로의 모든 링크에는 Maximum Transmission Unit (MTU), 즉 해당 링크가 하나의 프레임에 담아 전달할 수 있는 가장 큰 단일 바이트 블록이 있습니다. 이더넷과 Wi-Fi는 모두 이 값을 약 1500바이트로 제한하며, 거의 모든 인터넷 경로는 어딘가에서 이 한계로 귀결됩니다.
데이터그램이 통과해야 하는 링크의 MTU를 초과하면, 네트워크 계층이 이를 더 작은 프래그먼트 로 분할하고 목적지에서 재조립합니다. UDP 자체는 분할을 결코 보지 못하지만, 프래그먼트에는 몇 가지 불편한 특성이 있습니다:
어느 하나의 프래그먼트라도 손실되면, 전체 데이터그램이 수신자에서 폐기됩니다. 프래그먼트별 재전송은 없습니다. 손실 확률은 프래그먼트 수와 함께 커집니다.
일부 네트워크와 방화벽은 단편화된 패킷을 의심스러운 것으로 취급하여 완전히 폐기합니다.
재조립은 수신자에서 메모리를 소비하는데, 마이크로컨트롤러에서는 메모리가 부족합니다.
카메라에서의 실용적인 규칙: UDP 메시지를 1500바이트보다 충분히 작게 유지하세요. 약 1400바이트로 하면 IP 및 UDP 헤더, 경로가 추가하는 터널링 오버헤드, 이더넷·Wi-Fi·VPN 링크 간 MTU의 작은 변동을 위한 여지가 남습니다. 그보다 많이 보내야 하는 애플리케이션은 애플리케이션 계층에서 데이터를 청크로 나누거나, 분할과 재조립을 자동으로 처리하는 TCP로 전환해야 합니다.
9.12.5. 흔한 함정¶
UDP가 패킷을 잃을 수 있다는 것을 잊는 것. 조용한 로컬 네트워크에서 완벽하게 작동하는 코드가 더 바쁘거나 더 넓은 네트워크에서 미묘한 방식으로 실패하기도 합니다. 메시지가 도착하지 않았을 가능성을 항상 염두에 두고 설계하세요.
송신자가 보내기 전에 수신자가 바인딩되지 않은 것. 아무도 리스닝하지 않는 포트로 보낸 데이터그램은 조용히 폐기됩니다. 수신자를 먼저 시작하세요.
경로의 MTU보다 큰 데이터그램을 보내는 것. 앞 절을 참고하세요. 메시지를 약 1400바이트 미만으로 유지하세요.
위의 패턴들은 카메라가 사용하는 거의 모든 UDP 용도를 다룹니다. 다음 페이지는 TCP에 대해 동등한 내용을 다룹니다.