iBetter Books
수정

소켓이라는 추상

포트까지 이해했다면 "그럼 프로세스는 실제로 어떻게 네트워크를 사용하나요"라는 질문이 자연스럽게 따라옵니다. 프로세스가 직접 IP 패킷을 조립하고 헤더를 채워 넣을까요. 아닙니다. 운영체제는 그 복잡한 일을 감춰주는 추상 인터페이스를 제공합니다. 그것이 소켓(socket)입니다.

파일처럼 쓰는 네트워크

유닉스 계열 운영체제에서 소켓은 파일처럼 다룹니다. 소켓을 만들면 파일 디스크립터라는 번호를 돌려줍니다. 이후 데이터를 보내는 것은 파일에 쓰는 것과 같은 인터페이스이고, 데이터를 받는 것은 파일에서 읽는 것과 같습니다. 프로세스는 IP가 어떻게 동작하는지, TCP 헤더가 어떻게 생겼는지 전혀 알 필요가 없습니다.

이 설계 원칙을 "모든 것은 파일이다"라고 표현합니다. 하드디스크, 키보드, 프린터, 네트워크 연결이 모두 파일처럼 읽고 쓸 수 있습니다. 덕분에 네트워크 코드와 파일 입출력 코드가 비슷한 모양이 됩니다.

소켓의 생성과 연결 과정

서버 쪽과 클라이언트 쪽이 하는 일이 다릅니다.

서버는 먼저 소켓을 만들고(socket), 특정 포트 번호에 결합(bind)한 다음, 연결 요청을 기다리겠다고 선언(listen)합니다. 그 뒤 실제 연결이 들어오면 수락(accept)하여 대화용 소켓 하나를 새로 받습니다. 웹 서버가 80번 포트에 앉아서 여러 클라이언트를 동시에 처리할 수 있는 것은 이 accept가 매번 새로운 소켓을 만들어 주기 때문입니다.

클라이언트는 더 단순합니다. 소켓을 만들고(socket) 서버 IP와 포트로 연결을 시도(connect)합니다. 연결이 되면 그 순간부터 읽고 쓸 수 있습니다.

스트림 소켓과 데이터그램 소켓

소켓에는 종류가 있습니다. TCP를 쓰는 소켓을 스트림 소켓(SOCK_STREAM)이라 하고, UDP를 쓰는 소켓을 데이터그램 소켓(SOCK_DGRAM)이라 합니다.

스트림 소켓은 마치 수도관처럼 한번 연결되면 데이터가 순서대로 끊임없이 흐릅니다. 경계가 없습니다. 100바이트를 두 번 보내면 받는 쪽에서 50바이트씩 네 번 받을 수도 있습니다. 받는 쪽이 적당히 잘라서 쓰는 것은 애플리케이션의 몫입니다.

데이터그램 소켓은 편지처럼 독립된 덩어리로 주고받습니다. 한 번에 보낸 데이터가 한 덩어리로 도착합니다. 연결 없이도 보낼 수 있습니다. 목적지 주소를 매번 지정합니다.

소켓이 추상화하는 것

소켓이라는 개념이 중요한 이유는 애플리케이션 개발자가 TCP인지 UDP인지만 선택하면 되고, 세부 동작은 운영체제가 책임진다는 점입니다. Python의 socket 모듈이든 Java의 java.net.Socket이든 Go의 net.Dial이든, 내부는 결국 운영체제의 소켓 시스템 콜을 감싼 것입니다.

다음 절에서는 netstat 명령으로 지금 이 컴퓨터에 열려 있는 소켓들을 직접 들여다봅니다.