[Lecture 20] TCP/IP 소켓 프로그래밍
#개요
이번 포스트에서는 다음과 같은 네트워크 개념들을 알아보자
- 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)간 데이터의 투과적인 전송 기능을 제공하는 역할을 한다.
#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/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)를 포함한다
#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
#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