Computer Science/Computer Systems

[Lecture 20] TCP/IP 소켓 프로그래밍

Developer07 2022. 11. 20. 10:35

#개요

이번 포스트에서는 다음과 같은 네트워크 개념들을 알아보자

  • IPv4/IPv6를 비롯한 TCP/IP
  • 전송 계층 프로토콜: TCP와 UDP
    • 포트 사용
    • TCP/UDP에서의 디멀티플렉싱 / Demultiplexing (역다중화)
    • IPv4 addressing(주소 지정) & 라우팅
      • subnet / 서브넷(부분망) 및 CIDR 포함
    • 프로토콜 독립성
    • BSD 소켓 API
      • DNS 네임 확인을 위한 유틸리티 함수 포함
    • TCP/IP
      • https://terms.naver.com/entry.naver?docId=1168072&ref=y&cid=40942&categoryId=32851

#Transport and Network Layer

  • *Transport Layer (전송 계층) 프로토콜: 
    • TCP (Transmission Control Protocol):  IP 프로토콜 위에서 연결형 서비스를 지원하는 전송계층 프로토콜로, 인터넷 환경에서 기본으로 사용한다. 즉, 신뢰할 수 있는 데이터 전송법을 제공한다
    • UDP (User Datagram Protocol): 인터넷에서 정보를 주고받을 때, 서로 주고받는 형식이 아닌 한쪽에서 일방적으로 보내는 방식의 통신 프로토콜이다. 인터넷상에서 서로 정보를 주고받을 때 정보를 보낸다는 신호나 받는다는 신호 절차를 거치지 않고, 보내는 쪽에서 일방적으로 데이터를 전달하는 통신 프로토콜이다. 보내는 쪽에서는 받는 쪽이 데이터를 받았는지 받지 않았는지 확인할 수 없고, 또 확인할 필요도 없도록 만들어진 프로토콜을 말한다. UDP는 인터넷에서 사용하는 프로토콜 중 구조가 가장 간단하다. 그렇기 때문에 신뢰적이지 않은 데이터 전송법을 제공한다.
    • 포트 번호는 응용 프로그램을 처리하는 데 사용된다
  • Network Layer (네트워크 계층) 프로토콜: IPv4 & IPv6
    • IP 주소는 호스트(네트워크 인터페이스) 주소 지정에 사용
    • 두 프로토콜 모두 IP와 함께 작동하도록 설계되었으므로 TCP/IP 및 UDP/IP라는 용어가 사용된다

*Transport Layer (전송 계층): 개방형 시스템 간 접속(OSI : open system interconnection)의 참조 모델 가운데 제4층. 하위층을 구성하는 각종 통신 회로망의 품질 차이를 보충하고, 노드(node)간 데이터의 투과적인 전송 기능을 제공하는 역할을 한다. 

Internet Protocol Stack

#UDP / User Datagram Protocol

UDP의 특징은 다음과 같다:

  • 간단하다
  • 데이터그램 지향적이다
  • connectionless: 연결 설정 필요 없음
  • 신뢰하기 어려운 데이터 전송법
  • 패킷 손실을 보상하려는 시도를 하지 않음
  • 멀티캐스트 지원

# TCP / Transmission Control Protocol

TCP의 특징은 다음과 같다:

  • point-to-point: 발신자 하나, 수신자 하나
  • 신뢰적인, in-order byte stream 사용: message boundaries 없음
  • pipelined: 부분적으로 unack’ed된 데이터라도 전송이 진행된다.
  • send & receive buffers: 데이터를 보유
  • full duplex data (전이중 데이터): 동일한 연결에서 양방향 데이터 흐름
  • connection-oriented: 데이터 교환 전 핸드셰이크(제어 메시지 교환)
  • flow controlled: 송신기가 수신기를 압도하지 않음
  • congestion controlled: 네트워크 보호

TCP Segment Header

#TCP/IP & UDP/IP Addressing/Demultiplexing

호스트 H1의 프로세스 A는 호스트 H2의 프로세스 B와 어떻게 통신할까?

  • 각 스트림은 As, Ps, Ad, Pd로 특징지어진다
    • As 와 Ad는 32비트 IPv4 주소 또는 128비트 IPv6 주소(예: 172.217.9.196 또는 2607:f8b0:4004:807:2004)를 지정하는 source와 destination이다 (s = source, d = destination)
    • Ps, Pd는 16비트 포트 번호이다. 주소 + 프로토콜 조합당 하나의 네임스페이스가 있다 (예: 80/tcp, 80/tcp6, 53/udp, 53/udp6).
    • 네임스페이스: 데이터들이 어떤 층위에 속해 있는지를 지정해 놓는 공간이다. 이름이 같은 데이터라도 층위에 따라 다른 의미를 가질 수 있기 때문에 이를 구분한다
  • 관점에 따라 로컬 주소와 원격/피어 주소가 각각 쌍(As, Ps) 또는 (Ad, Pd)이다
  • 디멀티플렉싱(수신 패킷 전송 위치 결정)을 수행하려면 TCP의 As, Ps, Ad, Pd 4개 모두 필요하지만 UDP의 경우에는 (Ad, Pd)만 필요하다
    • 디멀티플렉싱 (역다중화): 다수의 다중화된 것에서 분리하는 일. 해당 계층의 통신 접속을 위하여 바로 하위 계층의 단일 접속에서 수신된 서비스 데이터로부터 이 계층의 프로토콜 데이터에 일치하는 프로그램으로 수행된다

#IPv4 Addressing

  • IP 주소는 호스트가 아니라 인터페이스를 나타낸다
  • 연결된 인터페이스는 주소가 common prefix를 공유해야 하는 서브넷을 형성한다
  • 서브넷은 라우팅 destination이다
  • 서브넷 내 라우팅 없음 - destination에 직접 도달할 수 있다
  • CIDR은 최대 31개의 prefix bit를 허용한다
    • Classless Inter-Domain Routing / CIDR은 IP 주소를 할당하고 패킷을 라우팅하는 방법 중 하나이다. 기존 8비트 단위로 통신망부와 호스트부를 구획하지 않는 방법이다.
  • 223.1.1.0/24는 223.1.1.0 – 223.1.1.255(netmask 255.255.255.0)를 포함한다
  • 223.1.7.0/30는 223.1.7.0 – 223.1.7.3 (netmask 255.255.255.252)를 포함한다

Subnetting in IPv4

#IPv4 Address Space Subdivision (IPv4 주소 공간 분할 문제)

당신이 작은 회사에 의해 네트워크 관리자로 고용되었다고 가정해보자. 191.23.25.0/24에 256개의 작은 블록 주소가 제공되고, PPP (Point-to-Point Protocol)를 통해 2개의 개별 사이트에 60/120 시스템이 있는 2개의 LAN을 ISP의 에지 라우터에 연결해야 한다. 각 서브넷에 어떤 IP 주소를 할당해야 할까?

답:

#Socket

소켓(socket)은 UC 버클리 (University of California at Berkeley)에서 만들어져 1982년 BSD(Berkeley Software Distribution) UNIX 4.1에서 처음 소개 되었으며 현재 널리 사용 되는 것은 1986년 BSD UNIX 4.3에서 개정 된 것이다, 그래서 이 소켓을 BSD소켓 또는 버클리 소켓이라고 불리며 개발자는 이를 사용하여 네트워크 개발을 효율적으로 할 수 있다

 

소켓의 예를 들어보자. 친구에게 택배를 보낸다고 했을 때, 상자에 보낼 물건을 넣고 나의 이름, 주소 등의 인적사항과 친구의 집 주소를 적어야 한다. 컴퓨터에서는 이 역할을 소켓이 해준다고 보면 된다. 윈도우에서는 95버전부터 API에 정식으로 포함하여 제공한다.

  • 윈도우 소켓은 DLL을 통해 기능이 제공되므로 DLL 초기화와 종료 작업을 위한 함수가 필요하다.
  • 효율적인 개발을 하기 위해 멀티스레드를 주로 사용한다.
  • 윈도우 XP SP 2부터 RAW 소켓 지원이 중지되었다. 역으로 그 이하 운영체제에서는 사용이 가능하다.
  • 유닉스 소켓과 호환성이 높아 이식하여 활용하기가 편리하다

소켓을 이용한 TCP/IP 프로그래밍을 소켓 프로그래밍이라고 한다. 일반적으로 소켓 프로그래밍 하면 유닉스/리눅스 환경을 가리키고, BSD 운영 체제에서 처음 소개되었으므로 BSD Socket이라고 표현한다. 윈도우 환경에서는 WinSock(Windows Socket)이라는 명칭을 따로 사용한다. 그리고 IPC 기법 중 하나다. 

 

출처: 나무위키/소켓

#Socket API

  • BSD 4.1 유닉스(1981)에 처음 도입되었으며, 현재 모든 플랫폼에서 사실상 표준이 되었다
  • 일반적인 프로세스 간 통신(IPC) 기능
    • 애플리케이션 프로세스가 다른 애플리케이션 프로세스와 메시지를 주고받을 수 있는 a host-local, application-created, OS-controlled 인터페이스("door")
  • 네트워크 통신에 사용되는 경우
    • 애플리케이션 프로세스와 엔드 투 엔드 전송 프로토콜(UDP/TCP) 사이의 "도어"
    • 유닉스에서 소켓은 파일 설명자이므로 read(2), write(2), close(2)와 다른 system call도 사용 가능하다.
      • 파일 설명자 (File Descripter): 프로그램에서 보조 기억 장치에 있는 파일에 접근하는 데 이용되는 여러 가지 파일 정보를 담고 있는 자료 구조.
    • 바인딩은 많은 고급 언어에 존재한다 (예: java.net.Socket, Python socket)

#UDP Socket API

일반적인 UDP 통신 시나리오에서 사용되는 소켓 API 호출

#socket(2)

int socket(int domain, int type, int protocol);
  • domain: PF INET, PF UNIX, PF INET6, ...
  • type: SOCK DGRAM (UDP를 위해), SOCK STREAM (TCP를 위해), ...
  • 프로토콜: 지정되지 않은 경우 0(또는 IPPROTO UDP 또는 IPPROTO TCP)
  • 정수 파일 설명자를 반환
  • 완전히 프로세스와 OS 간의 소통 (네트워크 작업이 전혀 관여되지 않음)
  • 더 많은 call들을 보기 위해선 manpage 참조: ip(7), udp(7), tcp(7), socket(2), socket (7), unix(7) type “man 2 socket”, “man 7 socket” 등등

#bind(2)

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
  • sockfd: socket()에 의해 반환됨
  • my_addr: "socket address" - 로컬 주소이다(receive를 위해 destination, send를 위해 source)
  • addrlen: address 길이
  • 주소는 가변 크기(variable-sized)의 데이터 구조이다
  • 프로토콜의 네임스페이스에 지정된 (로컬) 주소로 소켓을 "바인딩"한다
  • 네트워크를 통해 정보가 전송되지 않음
  • 하나의 소켓이 하나의 프로토콜/포트에 바인딩될 수 있지만, 예외는 다음과 같다
    • 멀티캐스트 / multicast
    • 이중 바인딩: 동일한 소켓은 IPv4 및 IPv6에 바인딩할 수 있음

#Address Family Polymorphism

struct sockaddr { /* GENERIC TYPE, should be "abstract" */
    sa_family_t sa_family; /* address family */
    char sa_data[14]; /* address data */
};

/* This is the concrete "subtype" for IPv4 */
struct sockaddr_in {
    sa_family_t sin_family; /* address family: AF_INET */
    u_int16_t sin_port; /* port in network byte order */
    struct in_addr sin_addr; /* internet address */
};

struct sockaddr_storage { /* large enough to store addresses */
    sa_family_t sa_family; /* address family */
    char sa_data[?]; /* address data */
};

#IPv4 vs IPv6 addresses

/* Internet IPv4 address. */
struct in_addr {
    u_int32_t s_addr; /* address in network byte order */
};

/* IPv6 address */
struct in6_addr {
    union
    {
    uint8_t u6_addr8[16];
    uint16_t u6_addr16[8];
    uint32_t u6_addr32[4];
    } in6_u;
};

#sendto(2), recvfrom(2), send(2), recv(2), connect(2)

ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
  • read/write 의 경우 에서와 같이 s, buf, len
  • flags: MSG OOB, MSG PEEK – 대부분 0
  • to/from은 remote/peer address이다: 데이터그램이 어디서 왔는지, 어디로 보내야 하는지
  • fromlen은 value-result (에일리어싱 / aliasing이 있는 경우를 제외하고 call-by-reference )이다
  • connect(2)를 사용하여 기본 주소를 설정한 다음 send(2)/recv(2)를 사용할 수 있다

#TCP Socket API Call Sequence

  • 왼쪽: 클라이언트 (“connecting socket”)
  • 오른쪽: 서버 (“listening socket”)
  • 서버가 여러 통화를 통해 여러 클라이언트를 순차적으로 또는 동시에 수락할 수 있음
  • 독립적인 direction: read(2)/write(2)는 임의의 순서로 사용할 수 있다
  • read(2)/write(2) 또는 recv(2)/send(2)가 사용될 수 있다
  • 한 방향을 종료하기 위해 shutdown(2) 사용

#connect(2)

int connect(int sockfd, const struct sockaddr *peeraddr, int addrlen);
  • sockfd: socket()에 의해 반환됨
  • peeraddr: peer address
  • 서버와의 핸드셰이크 시작, SYN 패킷 전송
  • 성공적인 system call은 핸드셰이크가 성공했음을 나타낸다

#listen(2), accept(2)

int listen(int s, int backlog);
int accept(int s, struct sockaddr *addr, int *addrlen);
  • addr: 받은 peer (클라이언트) 주소
  • listen()은 accept()전에 call해야 한다.
    • 네트워크 작업이 없지만 연결 요청 대기열을 시작하도록 OS에 알린다
  • accept()는 클라이언트가 pending될 때까지 보류하고 이 클라이언트에 대한 연결을 나타내는 새 소켓을 반환한다. 전달된 소켓이 후속 호출에서 더 많은 클라이언트를 수락할 준비가 되었다는 것을 나타낸다

#The IPv6 Challenge

  • IPv4는 40억 개의 주소만 제공하므로 주소 공간이 고갈되었다
  • IPv6는 1990년대에 IPv4의 후속으로 설계되었다
  • 그러나 IPv6는 별도의 네트워크이다
    • 호스트가 IPv4를 통해 연결될 수 있음
    • 또는 IPv4와 IPv6를 통해서
    • 또는 IPv6만을 사용해서
  • 두 경우 모두 네트워크 응용 프로그램이 작동해야 한다:
    • 소켓 코드에 주소를 포함하거나 크기/형식에 대해 가정하면 안된다
    • 시스템에서 사용자가 (클라이언트로서) 사용해야 하는 주소또는 (서버로서)지원해야하는 주소를 알려주게 하면된다

#IPv6 Transition Plan

서버는 IPv4와 IPv6을 모두 제공하며, 클라이언트는 둘 다 사용 가능할 때 IPv4보다 IPv6을 더 선호한다. 결국 IPv4 연결은 끊어지게 될 수도 있다.

 

다음 그래프를 보자:

구글 서비스에 접속하는 사용자들 사이에서 IPv6 채택률

#Protocol Independent Programming

int getaddrinfo(const char *node,const char *service, const struct addrinfo *hints,struct addrinfo **res);
  • getaddrinfo()를 사용하여 적절한 주소 패밀리 및 주소에 대한 정보를 얻을 수 있다
    • IPv4, IPv6 또는 둘 다에 바인딩할 서버의 경우: AI_PASSIVE가 설정되어 있고 node == NULL인 경우
    • 연결할 클라이언트의 경우(DNS 이름 또는 지정된 주소 표기법 기준), RFC 3484(현재 RFC 6724) 순서에 따라
  • getnameinfo()를 사용하여 주소를 print 가능한 형식으로 변환
  • http://www.akkadia.org/drepper/userapi-ipv6.html 링크에 가면 IPv6 Programming튜토리얼이 있다
    • 이중 바인딩 / dual-bind 기능을 사용할 수 있다(리눅스 전용)
    • 보통 방법은 두 개의 별도 소켓을 사용하는 것이다

#Demultiplexing in UDP + TCP

UDP Demultiplexing
TCP Demultiplexing