클라이언트 프로그램 : nc유틸리티 (Ubuntu)
서버 프로그램 : 파이썬


import socket
# 서버가 바인딩된 호스트 이름 또는 IP 주소를 확인합니다.
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
print(f"Server is running on IP: {local_ip}")
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.bind(("0.0.0.0",9090))
while True:
data, (ip,port) = udp.recvfrom(1024)
print ("From {}:{}: {}".format(ip,port,str(data, 'utf-8')))
udp.sendto(b"Hello back\n", (ip,port))
#이 서버는 "172.19.26.102" IP 주소에서 UDP 패킷을 대기 중
이 부분은 서버가 실행 중인 컴퓨터의 실제 IP 주소를 얻는 코드입니다.
socket.gethostname()은 서버 컴퓨터의 호스트 이름을 반환하고, socket.gethostbyname(hostname)은 그 호스트 이름에 해당하는 로컬 IP 주소를 반환합니다.
예를 들어, 만약 서버가 172.19.27.160 IP를 가지고 있다면, local_ip 변수에는 "172.19.27.160"이 저장됩니다.
이 IP는 서버가 물리적으로 가지고 있는 네트워크 인터페이스의 IP 주소입니다. 이를 출력하는 목적은 사용자가 서버의 IP 주소를 확인할 수 있게 하는 것입니다.
이 부분은 서버의 UDP 소켓이 특정 IP 주소와 포트에 바인딩된다는 것을 의미합니다.
여기서 잠깐! 바인딩 된다란?
서버 프로그램이 특정 IP 주소와 포트 번호에 연결되어 해당 주소와 포트에서 요청을 수신할 준비가 되어 있는 상태를 의미
"0.0.0.0": 이 IP 주소는 모든 네트워크 인터페이스를 의미합니다. 즉, 서버의 UDP 소켓이 서버가 가진 모든 네트워크 인터페이스에서 들어오는 UDP 패킷을 수신할 준비가 되어 있음을 나타냅니다. 이때, 서버 프로그램이 0.0.0.0아 아닌 특정 하나의 IP 주소에만 바인딩하면, 그 특정 IP로만 패킷을 수신할 수 있습니다.
참고
서버(또는 컴퓨터)는 여러 네트워크 인터페이스를 가질 수 있으며, 각 인터페이스에는 서로 다른 IP 주소가 할당될 수 있습니다.
예를 들어:
[이더넷] - 에 하나의 IP 주소가 있을 수 있습니다 (예: 192.168.1.10).
[Wi-Fi] - 에 또 다른 IP 주소가 있을 수 있습니다 (예: 192.168.1.20).
[로컬 루프백 인터페이스] - (localhost 또는 127.0.0.1)도 IP 주소로 간주됩니다.
9090: 이 소켓이 수신할 포트 번호입니다. 클라이언트는 서버의 IP 주소와 포트 9090을 사용해서 UDP 패킷을 전송해야 합니다.
서버는 무한 루프를 돌면서 클라이언트로부터 오는 데이터를 계속해서 수신하고 처리합니다.
udp.recvfrom(1024): UDP 소켓으로부터 최대 1024바이트 크기의 데이터를 수신합니다.
data: 수신한 데이터(바이트열)를 저장합니다. (data라는 이름의 변수에 저장)
(ip, port): 데이터를 전송한 클라이언트의 IP 주소와 포트 번호를 저장합니다.
수신한 데이터를 클라이언트의 IP 주소와 포트 번호와 함께 출력합니다.
str(data, 'utf-8'): 바이트열 데이터를 문자열로 변환하여 출력합니다.
수신한 클라이언트의 IP 주소와 포트 번호로 송신이 잘되었다는 응답의 의미로 "Hello back\n"이라는 메시지를 전송합니다.
이 명령어는 Netcat을 이용해 서버의 IP 주소와 포트 9090으로 UDP 패킷을 전송합니다.
이 상태에서 메시지를 입력하고 Enter 키를 누르면 해당 메시지가 서버로 전송됩니다.
이 메시지는 클라이언트로부터 수신한 데이터를 보여줍니다.
이 부분을 각각 살펴보면:
From 172.19.26.102:60287:
이 부분은 클라이언트의 IP 주소와 포트 번호를 나타냅니다.
172.19.26.102는 클라이언트가 서버에 연결할 때 사용한 클라이언트 측의 IP 주소입니다.
60287은 클라이언트가 서버와 통신할 때 사용한 클라이언트 측의 포트 번호입니다.
클라이언트는 서버와의 통신을 위해 임시 포트를 할당받고, 그 포트 번호를 통해 통신을 합니다.
클라이언트는 (서버의)어느 포트로 패킷 요청을 보낼지 미리 알고 있어야 하기 때문에 서버는 고정된 포트 번호를 사용해야 합니다. 따라서 우리가 서버 프로그램 코드를 작성할 때 포트 번호를 직접 등록(바인딩) 했습니다.
예를 들어, 웹 서버는 일반적으로 80번 포트(HTTP) 또는 443번 포트(HTTPS)를 사용합니다. 클라이언트는 이러한 고정된 포트로 요청을 보내기 때문에, 서버는 특정 포트 번호에 바인딩된 상태로 대기중인 것 입니다.
클라이언트는 서버에 요청을 보낼 때, 자신의 포트 번호를 임의로 할당받습니다. 운영체제는 클라이언트 프로그램이 데이터를 송수신할 수 있도록 가용한 포트 번호 중 하나를 자동으로 할당해 줍니다. 따라서 클라이언트는 특정한 포트 번호에 바인딩될 필요 없이, 운영체제가 자동으로 선택한 포트를 사용합니다.
서버의 포트 번호 뿐 아니라 발신지 포트 번호 역시 반드시 필요합니다. 이 포트 번호를 통해 서버가 클라이언트에게 응답을 보낼 수 있기 때문입니다. 다행히도, 개발자가 일일이 클라이언트의 포트 번호를 지정할 필요는 없습니다. 그 이유는 서버가 클라이언트의 포트 번호를 인식하는 방식에 있습니다.
클라이언트가 서버로 요청을 보내면, 패킷에는 클라이언트의 IP 주소와 함께 발신지 포트 번호도 포함됩니다. 따라서 서버가 패킷을 수신하면 자동으로 클라이언트의 포트 번호를 인식하고, 그 포트로 응답을 보내게 됩니다. 즉, 클라이언트의 발신지 포트 번호는 지정하는 것이 아니라 운영체제(OS)가 자동으로 할당하며, 수동으로 지정하는 것보다 운영체제가 이를 자동으로 관리하는 것이 더 효율적입니다.