
소켓은 애플리케이션과 네트워크 사이의 인터페이스이고, 어떤 transport를 쓰느냐에 따라 TCP 소켓과 UDP 소켓으로 나뉩니다.
TCP 소켓은 연결을 먼저 세워야 하므로 서버와 클라이언트가 호출하는 함수가 다릅니다. 서버는 create, bind, listen, accept 순서로 자리를 잡고 기다리며, 클라이언트는 socket을 만든 뒤 connect로 찾아갑니다. 연결이 되면 양쪽 모두 read와 write로 데이터를 주고받습니다.

주목할 점은 accept()가 새 소켓을 돌려준다는 것입니다. listen하던 소켓은 계속 문지기로 남고, 실제 대화는 클라이언트마다 새로 만들어진 소켓으로 합니다. 이 구조가 뒤의 TCP demultiplexing을 이해하는 열쇠입니다.
메모대로 코드로 구현할 수 있습니다. 파이썬 표준 라이브러리만으로 충분합니다.
# TCP 에코 서버
import socket
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # create
srv.bind(("0.0.0.0", 9000)) # bind
srv.listen() # listen
while True:
conn, addr = srv.accept() # 클라이언트마다 새 소켓
data = conn.recv(1024) # read
conn.sendall(data) # write
conn.close()
# TCP 클라이언트
import socket
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.connect(("127.0.0.1", 9000))
cli.sendall(b"hello")
print(cli.recv(1024))
UDP는 연결이 없으므로 listen, accept, connect가 필요 없습니다. SOCK_DGRAM으로 만들고 bind한 뒤, 보낼 때마다 목적지를 지정하는 sendto()와 받을 때 보낸 쪽 주소를 함께 받는 recvfrom()을 씁니다. 연결이라는 개념이 없으니 매 데이터그램에 주소를 적어야 하는 것입니다.
이제 문 너머로 내려갑니다. network 계층은 IP address를 보고 데이터를 호스트까지만 배달합니다. 그 호스트 안에서 어느 프로세스가 받아야 하는지는 모릅니다. transport 계층이 가장 먼저 책임지는 것이 바로 이 마지막 구간, 호스트에서 프로세스까지의 전달입니다.
이를 위해 transport 계층은 애플리케이션의 데이터에 source port와 destination port를 담은 header를 붙입니다. 이렇게 만들어진 덩어리를 segment(세그먼트)라고 부릅니다.

UDP는 multiplexing과 demultiplexing 외에 거의 아무것도 하지 않는 transport입니다. 그래서 헤더도 단출합니다. UDP 헤더는 항상 8바이트이며, 네 필드가 각각 2바이트씩입니다.
| 필드 | 크기 | 역할 |
|---|---|---|
| source port | 16bit | 답장을 보낼 주소. 수신 측이 응답할 때 destination port가 됨 |
| destination port | 16bit | demultiplexing의 기준. 어느 소켓으로 보낼지 결정 |
| length | 16bit | 헤더와 데이터를 합친 전체 길이 |
| checksum | 16bit | 전송 중 비트 오류 검출 |
checksum 계산에는 UDP 헤더와 데이터뿐 아니라 IP 헤더에서 가져온 출발지·목적지 주소 등의 pseudo header도 포함됩니다. 또 IPv4에서는 checksum이 선택 사항이고, IPv6에서는 필수입니다. 오류를 검출만 할 뿐 고치거나 재전송하지는 않습니다.
TCP 헤더와 IP 헤더도 이후 동작을 이해하는 데 매우 중요합니다. 각각 TCP 편과 IP 편에서 필드별로 정리합니다.
같은 demultiplexing이지만 소켓을 찾는 기준이 다릅니다.
| UDP | TCP | |
|---|---|---|
| 식별 키 | (dest IP, dest port) 2-tuple | (src IP, src port, dest IP, dest port) 4-tuple |
| 서버 소켓 수 | 포트당 하나. 모든 클라이언트 공유 | listen 소켓 하나 + 클라이언트마다 하나 |
| 출발지가 달라도 | 같은 소켓으로 들어감 | 다른 소켓으로 들어감 |
| 이유 | 연결이 없음 | 연결마다 독립된 상태를 유지 |
UDP 소켓은 목적지 IP와 목적지 포트만으로 완전히 식별되므로, 출발지가 다른 두 세그먼트도 목적지가 같으면 같은 소켓에 도착합니다. 반면 TCP 소켓은 네 값 전부로 식별되어, 출발지 IP나 포트가 다르면 다른 소켓으로 향합니다. 1절에서 accept()가 새 소켓을 돌려준 이유가 여기서 이어집니다. 연결마다 별도 소켓이 있으니, 도착한 세그먼트가 어느 연결 소속인지 가리려면 출발지 정보까지 봐야 합니다.