CHAPTER 11. 네트워크 프로그래밍
웹 검색, 이메일 메시지, 온라인 게임 등 모두가 네트워크 응용을 사용
11.1 클라이언트-서버 프로그래밍 모델
모든 네트워크 응용 프로그램은 Client-server 모델에 기초 : 1개의 server process + 1개 이상의 client process로 구성
SERVER : 일부 리소스를 관리, 조작해서 Client를 위한 일부 서비스를 제공
예시
- 웹 서버 : 디스크 파일들을 관리, CLIENT를 대신해서 이들을 가져오고 실행
- FTP 서버 : CLIENT를 위해 저장하고 읽어오는 디스크 파일들 관리
- 이메일 서버 : CLIENT를 위해서 읽고 갱신하는 스풀 파일 관리
CLIENT-SERVER 모델의 근본적인 연산은 트랜잭션(transaction),
4단계(CLIENT-SERVER transaction은 database transaction이 아님.)
- CLIENT가 서비스를 필요할 때, CLIENT는 한 개의 요청(request)을 서버에 보내는 것으로 transaction을 개시.
- SERVER는 request를 받고, 해석하고, 자신의 자원들을 적절하게 조작.
- SERVER는 응답(response)을 CLIENT로 보내고, 다음 요청 기다림.
- CLIENT는 response를 받고 처리.
CLIENT, SERVER : PROCESS, NOT machin, NOT host
한 개의 호스트 : 서로 다른 많은 클라리언트와 서버를 동시에 실행할 수 있음.
CLIENT와 SERVER transaction : 동일하거나 다른 호스트에 존재할 수 있음.
CLIENT-SERVER 모델 : CLIENT와 SERVER의 호스트로의 ampping에 관계없이 동일
11.2 네트워크
클라이언트와 서버 : 별도의 호스트에서 돌아가기도 함, 컴퓨터 네트워크의 하드웨어 및 소프트웨어 자원을 사용해서 통신
네트워크 : 호스트 입장에서 단지 또 다른 I/O 디바이스, 데이터를 위한 소스와 싱크로 서비스
I/O 버스의 확장 슬롯에 꽂혀있는 어댑터 : 네트워크에 물리적인 인터페이스 제공
테트워크에서 수신한 데이터 ->어댑터 -> I/O 버스와 메모리 버스 -> 메모리 (DMA 전송으로 복사)
물리적인 네트워크 : 기하학적 위치로 구성된 계층구조 시스템. 하위 수준은 LAN(Local Area Network) 대중적인 LAN 기술은 Eternet
SAN(System Area Network) : cluster, machin room
LAN(Local Area Network) : building, campus
WAN(Wide Area Network) : country, world
internet = internetwork : Global IP Internet 은 internet의 가장 유명한 예시 중 하나(i의 소문자 대문자 차이)
Lowest Level : Eternet Segment
Eternet Segment : Host로 향하는 몇개의 전선 + Hub라고 하는 작은 상자. 방이나 빌딩의 층과 같은 작은 지역에 설치
각 전선 : 동일한 최대 비트 대역폭(100Mb/s, 1 Gb/s). 한쪽 끝(호스트의 어댑터) ~ 다른쪽 끝(Hub의 Port)
Hub는 각 Port에서 수신한 모든 비트를 종속적으로 다른 모든 Port로 복사. 그래서 모든 Host는 모든 비트로 볼 수 있음.
각 Eternet adapter : 어댑터의 비휘발성 메모리에 저장된 전체적으로 고유한 48비트 주소를 가짐.
호스트는 Frame이라는 비트들을 segment의 다른 호스트에 보낼 수 있음.
프레임(Frame) : 프레임의 소스와 목적지, 프레임의 길이를 식별할 수 있는 고정된 Header 비트 가짐. 그 뒤에 data bit
모든 호스트 어댑터는 Frame 볼 수 있다. => 목적지 host만 실제로 읽어들인다.
Next Level : Bridged Ethernet Segment
Bridged Ethernet Segment : 전선 + bridge(작은 상자)를 사용해서 다수의 ethernet segment가 연결된 더 큰 LAN
선 : bridge - bridge, bridge - Hub 두 종류(대역폭 다름)
Bridge : Hub 보다 더 높은 전선의 대역폭. 알고리즘으로 자동으로 어떤 Host가 어떤 Port에서 도달 가능한지 학습. 이후 필요시 선택적으로 하나의 포트에서 다른 포트로 프레임 복사.
예.
-
Host A가 같은 segment 내 Host B로 한 프레임 전송 : 브릿지 X는 프레임이 입력 포트에 도달했을 때 프레임 버린다.(다른 세그먼트에서의 대역폭 절역)
-
Host A가 다른 segmnet 내 Host C로 한 프레임 전송 : 이 프레임을 브릿지 Y에 연결된 포트로만 복사, 호스트 C의 세그먼트에 열견된 포트로만 이 프레임 복사.
Next Level : internets
rounters(라우터)
- 계층구조의 상부에서 다수의 비호환성 LAN들을 연결하는 특별한 컴퓨터. 네트워크 간 연결을 구성(상호연결 네트워크)
-
이 연결된 네트워크를 internet이라고 부름
- 각 라우터는 이들이 연결되는 각 네트워크에 대해 어댑터(포트)를 가짐
- 고속의 point-to-point 전화 연결 가능, WAN(Wide-Area-Network)의 사례( LAN보다 지리적으로 넓게 운용됨)
- 임의의 LAN과 WAN들로부터 internet을 만들기 위해서 사용될 수 있음
internet의 특성 : 매우 다르고 비호환적인 기술을 갖는 여러 가지 LAN과 WAN들로 이루어져 있음
Logical Structure of an internet
Ad hoc interconnection of networks
- ad hoc 는 임시라는 뜻, 즉 독립 단말끼리 외부 도움없이 자기들만의 자율적 임시 망 구성을 뜻함
- 즉, 고정된 유선망을 가지지 않고 이동호스트(Mobile Host)로만 이루어져 통신되는
- 특별한 topology 없음
- topology : 컴퓨터 네트워크의 요소들(링크, 노드 등)을 물리적으로 연결해 놓은 것, 또는 그 방
- 방대하게 다양한 router 와 link capacities
패켓(packet)을 souce에서 목적지로 네트워크를 통해 이동 (이때마다 홉이 발생?)
- 라우터가 네트워크간 bridge 형성
- 다른 패킷은 다른 경로를 취할 수도 있음
각 호스트는 물리적으로 다른 모든 호스트에 연결되어 있다
-> 어떻게 어떤 소스 호스트가 모든 비호환적인 네트워크들을 지나서 데이터 비트를 다른 목적지 호스트로 전송할까?
protocol software
- 여러 가지 네트워크 간의 차이를 줄여 주는 각 호스트와 라우터에서 돈다
- 어떻게 호스트들과 라우터들이 데이터를 전송하기 위해서 협력하는지를 결정하는 프로토콜 구현한 것
- 명명법(naming scheme,첫번째 기능) : 서로 다른 LAN 기술은 주소를 호스트에 할당하는 서로 다른 비호환성을 갖는 방법 사용.
-
- internet 프로토콜은 호스트 주소를 위한 통일된 포맷을 정의하여 이 차이 줄임
- 각 호스트는 자신을 유일하게 식별하는 internet 주소 최소한 한 개 할당
- internet 프로토콜은 호스트 주소를 위한 통일된 포맷을 정의하여 이 차이 줄임
- 전달기법(delivery mechanism,두번째 기능) : 서로 다른 네트워킹 기숧은 서로 다른 비호환성을 갖는 비트 인코딩 방법과 프레임 내에 이들을 패키징하는 방법 사용
-
- internet protocol은 데이터 비트를 패킷이라는 비연속적인 단위로 묶는 통일된 방법을 정의해서 이 차이 줄임
- 패킷은 Header(패킷 크기와 소스 및 목적지 호스트 주소 포함) + data(소스 호스트가 보낸 데이터 비트를 포함)
- internet protocol은 데이터 비트를 패킷이라는 비연속적인 단위로 묶는 통일된 방법을 정의해서 이 차이 줄임
ex.
호스트와 라우터가 호환성이 없는 LAN을 통해 데이터를 전송하기 위해 internet protocol을 사용하는 방법
internet : 하나의 라우터에 연결된 두 개의 LAN
호스트 A에서 돌아가는 LAN1에 연결된 클라이언트 : LAN2에 연결된 호스트 B에서 돌고 있는 서버로 일련의 데이터 바이트 전송(8단계)
- 호스트 A의 클라이언트는 클라이언트의 가상 주소 공간에서 커널 버퍼로 데이터를 복사하는 시스템 콜 호출
- 호스트 A의 프로토콜 소프트웨어가 internet 헤더와 LAN1 프레임 헤더를 데이터에 추가해서 LAN1 프레임을 생성
- internet 헤더는 internet 호스트 B로 주소 지정
- 호스트 A는 이 프레임을 어댑터로 전달
- LAN1 프레임의 데이터가 internet 패킷이고, 그 데이터는 실제 사용자 데이터
- 위와 같은 것을 encapsulation(캡슐화)라고 함
- LAN1 어댑터는 이 프레임을 네트워크로 복사
- 프레임이 라우터에 도달하면, 라우터의 LAN1 어댑터는 전선에서 이것을 읽어서 프로토콜 소프트웨어로 전달
- 라우터는 internet 패킷 헤더에서 목적지 internet 주소를 가져와서 패킷을 전달할 곳을 결정(이 경우엔 LAN2 라우팅 테이블에서의 인덱스로 이것을 사용). 라우터는 이전의 LAN1 프레임 헤더를 벗겨내고, 호스트 B의 주소를 갖는 새로운 LAN2 ㅡㅍ레임 헤더를 앞에 붙여서 이것을 어댑터로 전달
- 라우터의 LAN2 어댑터는 이 프레임을 네트워크로 복사
- 이 프레임이 호스트 B에 도착하면 어댑터는 이 프레임을 전선에서 읽어들이고, 프로토콜 소프트웨어로 넘김
- 호스트 B의 프로토콜 소프트웨어는 패킷 헤더, 프레임 헤더 벗겨냄. 프로토콜 소프트웨어는 최종적으로 이 데이터를 서버가 이 데이터를 읽는 시스템 콜을 호출할 때 서버의 가상 주소공간으로 복사.
더 배워야할 것
- 서로 다른 네트워크들이 서로 다른 최대 프레임 크기를 갖는다면?
- 라투덜은 어디로 프레임을 전달해야 할지 어떻게 알까?
- 라우터들은 언제 네트워크 구조가 변경되는지 어떻게 알까?
- 패킷이 손실된다면 어떻게 될까?
11.3 글로벌 IP 인터넷
TCP/IP 프로토콜(Transmission Control Protocol/Internet Protocol)을 구현한 소프트웨어를 각 인터넷 호스트가 실행
인터넷 클라이언트와 서버 : 소켓 인터페이스와 Unix I/O 함수들의 혼합을 사용해서 통신
소켓 함수 : 일반적으로 시스템 콜들로 구현, 시스템 콜은 커널에서 트랩 발생시키고 TCP/IP에서 다양한 커널 모드 함수들 호출
TCP/IP는 프로토콜의 집합, 각각 서로 다른 기능 제공
IP
- 기본 명명법, 데이터그램이라고 하는 패킷을 한 인터넷 호스트에서 다른 호스트로 보낼 수 있는 배달 매커니즘 제공
- 만일 데이트거램을 잃어버리거나 네트워크 내에서 중보되는 경우 복구하려 노력하지 않음 => 안정성 X
- UDP(Unreliable Datagram Protocol) 는 프로세스에서 프로세스로 unreliable하게 전송 될 수 있음( IP는 다소 확정해서 데이터그램이 호스트에서 호스트로 감)
TCP
- IP위에 구현한 복잡한 프로토콜
- 프로세스들 간에 안전한 완전 양방향 연결 제공
- Uses IP to provide reliable byte streams from precess-to-process over connections
- 여기서는 TCP/IP를 하나의 독립 프로토콜로 보고 내부 동작은 언급 X
프로그래머 관점의 인터넷
- 호스트의 집합은 32비트 IP 주소 집합에 mapping 된다.
- IP 주소의 집합은 인터넷 도메인 네임(Internet Domain Name)이라고 부르는 식별자의 집합에 mapping
- 하나의 인터넷 호스트의 프로세스는 연결을 통해서 다른 인터넷 호스트의 프로세스와 통신할 수 있다.
11.3.1 IP 주소
IP 주소 : unsigned 32bit int(비부호형 32비트 정수)
네트워크 프로그램은 IP 주소를 IP 주소 구조체에 저장
1
2
3
4
/* IP addres structure */
struct in_addr {
unit32_t s_addr; /* Address in network byte order (big-endian) */
};
인터넷 호스트들이 서로 다른 호스트 바이트 순서를 가질 수 있기 때문에 TCP/IP는 네트워크 패킷 헤더에 포함되는 IP 주소 같은 모든 int형 데이터 아이템에 대해서 통일된 네트워크 바이트 순서(big endian 바이트 순서)를 정의
IP 주소 구조체의 주소는 호스트 바이트 순서가 little endian인 경우에도 항상 네트워크 바이트 순서(big endian)로 저장
Unix는 네트워크와 호스트 바이트 순서 간의 변화를 위한 함수 제공
1
2
3
4
5
6
7
8
9
#include <arpa/inet.h>
uint32_t htonl(unit32_t hostlong);
unit16_t htons(uint16_t hostshort);
//returns : value in network byte order
unit32_t ntohl(unit32_t netlong);
unit16_t ntohs(unit16_t netshort);
//returns : value in host byte order
htonl
함수 : unsigned 32bit int를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환.
ntohl
함수 : unsigned 32bit int를 네트워크 바이트 순서에서 호스트 바이트 순서로 변환.
htons
함수, ntohs
함수 : unsigned 16bit int에 대해 대응하는 변환을 수행
64비트는 존재X
우리가 아는 IP주소 : dotted-decimal 표기
ex. 128.2.194.242 0x8002c2f2
리눅스에선 hostname 으로 결정
linux> hostname -i
128.2.210.175
applications는 IP 주소와 dotted-decimal string 사이를 inet_pton과 inet_ntop으로 상호 변환
1
2
3
4
5
6
7
8
#include <arpa/inet.h>
int inet_pton(AF_INET, const char *src, void *dst);
Returns: 1 if OK, 0 if src is invalid dotted decimal, -1 on error
const char *inet_ntop(AF_INET, const void *src, char *dst,
socklen_t size);
Returns: pointer to a dotted-demical string if OK, NULL on error
함수 이름에서 “n” = network, “p” = presentation을 의미
32비트 IPv4 주소(AF_INET), 128비트 IPv6 주소(AF_INET6)도 처리 가능
inet_pton
함수 : dotted-decimal 스트링(src)을 네트워크 바이트 순서를 갖는 이진 IP 주소(dst)로 변환한다.
src가 유효한 dotted-decimal string 가리키지 않으면 0 반환
다른 모든 에러 : -1 반환, errno 설정
inet_ntop
함수 : 네트워크 바이트 순서를 갖는 이진 IP주소를 이에 대응하는 dotted-decimal src로 변환하고 NULL로 끝나는 결과 스트링의 최대 size바이트를 dst로 반환
11.3.2 인터넷 도메인 이름(Internet Domain Name)
인터넷 client 와 server 서로 통신할 때 : IP 주소 사용
크기가 큰 정수는 기억 힘듬 => 도메인 이름들의 집합을 IP 주소 집합으로 mapping하는 매커니즘. 단어들의 배열, 점 으로 구분
도메인 이름들의 집합 : 계층구조 형성
계충구조
- 이름이 없는 루트노드 : 첫번째 단계
- ICANN(Internet Corporation for Assigned Names and Numbers) : 비영리조직
- 2단계 도메인 이름 포함. 수 많은 ICANN이 인정하는 대행사가 요청하는 순서에 의해 이름 할당.
인터넷 : 도메인 이름의 집합과 IP 주소 집합 사이에 mapping 정의
mapping : 1개의 HOSTS.TXT -> DNS(Domain Name System)라는 전 세계에 분산된 데이터베이스에 의해 관리
DNS : 수백만 개의 호스트 엔트리로 구성, 각 엔트리는 도메인 이름과 IP 주소들의 equivalence(동일성) 클래스로 생각 가능
각 인터넷 호스트 : 지역적으로 정의된 도메인 이름 localhost를 가지고 있음
루프백 주소 : 127.0.0.1에 mapping
linux> nslookup localhost
Address: 127.0.0.1
localhost 이름 : 같은 머신에서 돌고 있는 client와 server들을 참조하는 편리하고 portable한 방법 제공. debugging 시에 유용
localhost의 도메인 이름 : hostname
linux> hostname
whaleshark.ics.cs.cmu.edu
- Simple case : 도메인 이름과 IP 주소의 1:1 mapping
linux> nslookup whaleshark.ics.cs.cmu.edu
Address: 128.2.210.175
- Some case : 다수의 도메인 이름과 IP 주소의 n:1 mapping
linux> nslookup cs.mit.edu
Address: 18.62.1.6
linux> nslookup eecs.mit.edu
Address : 18.62.1.6
- General case : 다수의 도메인 이름과 다수의 IP 주소의 n : m mapping
linux> nslookup www.twitter.com
Address : 199.16.156.6
Address : 199.16.156.70
Address : 199.16.156.102
Address : 199.16.156.230
linux> nslookup twitter.com
Address : 199.16.156.102
Address : 199.16.156.70
Address : 199.16.156.6
Address : 199.16.156.70
- Some case : IP 주소 mapping이 없는 도메인 이름
linux> nslookup edu
*** Can't find edu: No answer
linux> nslookup ics.cs.cmu.edu
*** Can't find ics.cs.cmu.edu : No answer
11.3.3 인터넷 연결
connection(연결) : 인터넷 client와 server는 연결을 통해서 바이트 스트림을 주고받는 방식으로 통신
point-to-point 연결 : 두개의 프로세스를 연결한다.
full-duplex(완전양방향) : 데이터가 동시에 영방향으로 흐를 수 있다.
안정적 : 소스 프로세스가 보낸 바이트 스트림이 결국 보낸 것과 동일한 순서로 목적지 프로세스에서 수신
Socket(소켓)
- 연결의 종단점
- 각 socket은 인터넷 주소와 16비트 정수 포트(라우터의 그 port 아님)로 이루어진 소켓 주소 가지며 이것을 adress : port로 나타낸다.
- client의 socket 주소 내의 포트 : ephemeral port(client가 연결 요청을 할 때 커널이 자동으로 할당)
- server의 socket 주소 내의 포트 : 대개 영구적으로 이 서비스에 연결되는 Well-known port(잘 알려진 포트)
- ex.
- echo server : 7/echo
- ssh servers: 22/ssh
- email server : 25/smtp
- Web servers: 80/http
연결 : 두 개의 종단점의 소켓 주소에 의해 유일하게 식별 => socket pair(소켓 쌍), tuple로 나타낸다.
(cliaddr:cliport, servaddr:servport)
cliaddr : client의 IP 주소
cliport : client의 포트
servaddr : server의 IP 주소
servport : server의 포트
11.4 소켓 인터페이스
Network application을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합
11.4.1 소켓 주소 구조체
in linux kernel의 관점 -> socket = 통신을 위한 끝점
in linux program의 관점 -> socket = 해당 식별자를 가지는 열린 파일
네트워크를 포함하는 모든 Unix I/O device들은 file로 모델링 되었음.
regular file I/O 와 socket I/O는 어떻게 application 이 socket descriptor(식별자)를 “여는가”로 구별
인터넷 소켓 주소 : sockaddr_in
타입의 16byte struct에 저장
인터넷 application에 대해
sin_family
필드는 AF_INETsin_port
필드는 16bit 포트 번호sin_addr
필드는 32bit IP 주소
IP 주소와 포트 번호 : 네트워크 바이트 순서(big endian)로 저장
connect, bind, and accept
함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터 필요
현재 : void * 포인터 사용
당시 : typedef struct sockaddr SA;
sockaddr_in
구조체를 포괄적인 sockaddr 구조체로 캐스팅할 필요가 있을 때마다 이 타입 사용
(in 접미사는 input X, internet)
Generic socket address
1
2
3
4
struct sockaddr {
unit16_t sa_family; /* Protocol family */
char sa_data[14]; /* Address data. */
};
Internet-specific socket address : Must cast struct sockaddr_in *
to struct sockaddr *
for funtions that take socket address arguments.
1
2
3
4
5
6
struct sockaddr_in {
uint16_t sin_family; /* Protocol family (always AF_INET) */
unit16_t sint_port; /* Port num in network byte order */
struct in_addr sin_addr; /* Ip addr in network byte order */
unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
}
11.4.2 socket함수
client와 server : socket 식별자를 생성하기 위해서 socket
함수를 사용
1
2
3
4
5
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
//Returns : nonnegative descriptor if OK, -1 on error
소켓을 끝점으로 만든다 : 다음과 같이 하드코드된 인자로 socket
함수를 호출
clientfd = Socket(AF_INET, SOCK_STREAM, 0);
AF_INET : 우리가 32bit IP 주소를 사용하고 있음을 나타냄
SOCK_STREAM : 소켓이 인터넷 연결의 끝점이 될 것임을 나타냄
이 매개변수들을 자동으로 생성해서 코드가 프로토콜에 무관하게 되도록 getaddrinfo
함수 이용하는게 좋다.
반환된 clientfd 식별자는 부분적으로 열렸다 : server인지 client인지에 따라 socket의 오픈 과정 완료가 달라진다.
11.4.3 connect
함수
client : connect
함수 호출해서 서버와의 연결 수립
1
2
3
4
5
6
#include <sys/socket.h>
int connect(int clientfd, const struct sockaddr *addr,
socklen_t addrlen);
//Returns: 0 if OK, -1 on error
connect
함수 : 소켓 주소 addr의 서버와 인터넷 연결 시도, addrlen -> sizeof(sockaddr_in)
연결이 성공할 때까지 블록되어 있거나 에러가 발생
성공 => clientfd 식별자는 읽거나 쓸 준비 완료, 연결은 소켓 쌍으로 규정
(x:y, addr.sin_addr:addr.sin_port)
x = client의 IP 주소
y = client 호스트의 client 프로세스를 유일하게 식별하는 단기 포트
(상술했듯, 가장 좋은 방법은 getaddrinfo
를 이용해 connect의 인자들을 제공하는 것)
11.4.4 bind
함수
소켓 함수 bind, listen, accept
는 server가 client와 연결을 수립하기 위해 사용
1
2
3
4
5
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
//Returns:0 if OK, -1 on error
bind
함수는 커널에게 addr에 있는 서버의 소켓 주소를 소켓 식별자 sockfd와 연결하라고 요청.
addrlen 인자는 sizeof(sockaddr_int)
임
socket
, connect
처럼 가장 좋은 방법은 getaddrinfo
를 이용해서 bind할 인자들을 제공하는 것
11.4.5 listen
함수
client : 연결 요청을 개시하는 능동적 개체.
server : client로부터의 연결 요청을 기다리는 수동적 개체
커널에서 socket
함수가 만든 식별자는 한 연결의 client 쪽 끝에서 존재하는 능동 소켓에 대응됨
server : listen
함수를 호출해서 이 식별자를 client 대신에 server가 사용하게 될 것이라고 알려줌
1
2
3
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//Returns:0 if OK, -1 on error
listen함수 : sockfd를 능동 소켓에서 듣기 소켓으로 변환(듣기 소켓은 client로부터의 연결 요청 승락할 수 있음)
backlog인자 : 커널이 요청들을 거절하기 전에 큐에 저장해야 하는 연결의 수에 대한 정보 제공
정확한 의미는 너무 깊은 공부 필요, 보통 1024와 같은 큰 값으로 설정
11.4.6 accept
함수
server : accept
함수를 호출해서 client로부터의 연결 요청을 기다림
1
2
3
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *addr, int *addrlen);
//Returns: nonnegative connected descriptor if OK, -1 on error
accpet
함수
- client로부터의 연결 요청이 듣기 실별자
listenfd
에 도달하기 기다림 - 그 후 addr 내의 client의 소켓 주소를 채움
- Unix I/O 함수들을 사용하여 client와 통신하기 위해 사용될 수 있는 연결 식별자를 반환
듣기 식별자와 연결 식별자 사이의 구분
듣기와 연결 식별자 사이에 구분하는 이유
구분을 통해 많은 클라이언트 연결을 동시에 처리할 수 있도록 하는 동시성 서버를 만들 수 있음. 예를 들어, 연결 요청이 듣기 식별자에게 도달할 때마다 연결 식별자에 대해 클라이언트와 통신하는 새로운 프로세스를 fork 할 수 있음.
듣기 식별자
- client 연결 요청에 대해 끝점으로서의 역할
- 보통 한 번 생성, server가 살아있는 동안 계속 존재
연결 식별자
- client와 server 사이에 성립된 연결의 끝점
- server가 연결 요청을 수락할 떄마다 생성
- server가 client에 서비스 하는 동안에만 존재
11.4.7 호스트와 서비스 변환
getaddrinfo
, getnameinfo
: 이진 소켓 주소 구조체들과 호스트이름, 호스트주소, 서비스이름, 포트번호들 사이에 앞 뒤로 변환
소켓 인터페이스와 함께 이용하면 특정 IP 프로토콜의 버전에 의존하지 않는 네트워크 프로그램 작성 가능
장점 : 재진싱입성, 우리가 portable protoco-independent code를 쓰게 해줌.
단점 : 다소 잡잡
getaddrinfo
함수
- 호스트이름, 호스트주소, 서비스이름, 포트번호의 스트링 표시를 소켓 주소 구조체로 변환
- 재진입 가능, 모든 프로토콜에 대해 동작
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, /* Hostname or address */
const char *service, /* Port or service name */
const struct addrinfo *hints, /* Input parameters */
struct addrinfo **result); /* Output linked list */
//Returns : 0 if OK, nonzero error code on error
void freeaddrinfo(struct addrinfo *result); /* Free linked list */
//Returns: nothing
const char *gai_strerror(int errcode); /* return error message */
//Returns: error message
-
host와 service(소켓 주소의 두 개의 구성요소)가 주어지면,
getaddrinfo
는 각각이 host와 service에 대응되는 소켓 주소 구조체를 가리키는 addrinfo 구조체의 연결리스트를 가리키는 result를 리턴 -
client 가
getaddrinfo
를 호출한 후 이 리스트를 방문하며, 각 소켓 주소를 socket과 connect로의 호출이 성공하고, 연결이 성립할 때까지 차례로 시험 -
Helper functions 두 개
-
메모리 누수 피하기 위해, application은
freeaddrinfo
를 호출해서 이 리스트 반환해야함 -
getaddrinfo
가 errorcode를 반환하면, application은gai_strerror
를 호출해서 이 코드를 메시지 스트링으로 변환할 수 있음
-
clients : 이 리스트를 따라, 소켓을 호출하고 연결이 성공할때까지 각 소켓 주소마다 시도해본다.
servers : 이 리스트를 소켓을 호출하고 bind가 성공할때까지 따라간다.
addrinfo
구조체
1
2
3
4
5
6
7
8
9
10
11
/* getaddrinfo가 사용하는 addrinfo 구조체 */
struct addrinfo {
int ai_flags; /* Hints argument flas */
int ai_family; /* First arg to socket function */
int ai_socktype; /* Second arg to socket function */
int ai_protocol; /* Third arg to socket function */
char *ai_canonname; /* Canonical hostname */
size_t ai_addrlen; /* Size of ai_addr struct */
struct sockaddr *ai_addr; /* Ptr to socket address structure */
struct addrinfo *ai_next; /* Ptr to next item in linked list */
};
각 getaddrinfo
에 의해 반환되는 addrinfo 구조체는 socket
함수에 직접 passed 될 수 있는 aregument를 포함한다.
또한 connect 와 bind 함수에 직접적으로 passed 될 수 있는 socket address 구조체를 가리키는 points 도 포함한다.
Host and Service Conversion
getnameinfo
함수
getnameinfo
는 socket address를 상응하는 host와 service로 변환한다. 즉, getaddrinfo
의 반대이다.
- 쓸모 없어진
gethostbyaddr
,getservbyport
함수 대체 - 재진입가능, 프로토콜에 독립적
1
2
3
4
5
6
7
8
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen, /* In : socket addr */
char *host, size_t hostlen, /* Out : host */
char *service, size_t servlen, /*Out : service */
int flags); /* optional flags */
//Returns:0 if OK, nonzero error code on error
ex.
HOSTINFO 라는 프로그램.
getaddrinfo
와 getnameinfo
를 이용해서 도메인 이름과 연관된 IP주소로의 mapping을 출력
Conversion Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "csapp.h"
int main(int argc, char **argv)
{
struct addrinfo *p, *listp, hints;
char buf[MAXLINE];
int rc, flags;
if (argc != 2) {
fprintf(stderr, "usage: %s <domain name>\n", argv[0]);
exit(0);
}
/* Get a list of addrinfo records */
memset(&hints, 0 , sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* IPv4 only */
hints.ai_socktype = SoCK_STREAM; /* connections only */
if ((rc = getaddrinfo(argv[1], NULL, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gat_strerror(rc));
exit(1);
}
/* Walk the list and display each IP address */
flags = NI_NUMERICHOST; /* Display address string instead of domain name */
for (p= listp; p; p = p -> ai_next) {
Getnameinfo(p->ai_addr, p->ai_addrlen, buf, MAXLINE, NULL, 0 , flags);
printf("%s\n", buf);
}
/* Clean up */
Freeaddrinfo(listp);
exit(0);
}
HOSTINFO 실행 결과
linux> ./hostinfo twitter.com
199.16.156.102
199.16.156.230
199.16.156.6
199.16.156.70
11.4.8 소켓 인터페이스를 위한 도움함수들
Sockets Helper
-
open_clientfd
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
int open_clientfd(char *hostname, char *port) { int clientfd; struct addrinfo hints, *listp, *p; /* Get a list of potential server addresses */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; /* Open a connection */ hints.ai_flags = AI_NUMERICSERV; /* ... using a numeric port arg. */ hints.ai_flags |= AI_ADDRCONFIG; /* Recommended for connections */ Getaddrinfo(hostname, port, &hints, &listp); /* Walk the list for one that we can successfully connect to */ for (p - listp; p; p = p-> ai_next) { /* Create a socket descriptor */ if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) <0) continue; /* Socket failed, try the next */ /* Connect to the server */ if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) break; /* Success */ Close(clientfd); /* Connect failed, try another */ } /* Clean up */ Freeaddrinfo(listp); if (!p) /* All connects failed */ return -1; else /* The last connect succeeded */ return clientfd; }
open_listenfd
함수 : client로부터 연결을 승낙하는데 사용될 수 있는 듣기 식별자를 만든다.
재진입 가능, 프로토콜-독립적
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, optval=1;
/* Get a list of potential server addresses */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype= SOCK_STREAM; /* Accept connections */
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
hints.ai_flags |= AI_NUMERICSERV; /* ... using port number */
Getaddrinfo(NULL, port, &hints, &listp);
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Creat a socket descriptor */
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* Socket failed, try the next */
/* Eliminates "Address already in use" error from bind */
Setsockopt(listnfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
Close(listenfd); /* Bind failed, try the next */
}
/* Clean up */
Freeaddrinfo(listp);
if (!p) /* No address worked */
return -1;
/* Make it a listening socket ready to accept connection requests */
if (listen)listenfd, LISTENQ) < 0) {
Close(listenfd);
return -1;
}
return listenfd;
}
open_clientfd
and open_listenfd
는 둘다 IP의 특정 버전으로부터 독립적이다.
Echo 클라이언트 : Main routine
echoclient.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "csapp.h"
int main(int argc, char **argv)
{
int clientfd;
char *host, *port, buf[MAXLINE];
rio_t rio;
if (arg != 3) {
fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
exit(0);
}
host = argv[1];
port = argv[2];
clientfd = Open_clientfd(host, port);
Rio_readinitb(&rio, clientfd);
while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clintfd);
exit(0);
}
Iterative Echo Server : Main Routine (반복적 echo 서버)
echoserveri.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "csapp.h"
void echo (int connfd);
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr; /* Enough room for any addr */
char client_hostname[MAXLINE], client_port[MAXLINE];
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage) ; /* Important! */
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
Getnameinfo((SA *) &clientaddr, clientlen,
client_hostname, MAXLINE, client_port, MAXLINE, 0);
printf("Connected to (%s, %s)\n", client_hostname, client_port);
echo(connfd);
Close(connfd);
}
exit(0);
}
echo
function
서버는 EOF(end-of-file) 조건이 만족되기 전까지 text를 읽고 echo 하기 위해 RIO를 사용한다.
EOF 조건은 client가 close(clientfd)
를 호출하여 생긴다.
echo.c
1
2
3
4
5
6
7
8
9
10
11
12
void echo (int connfd)
{
size_t n;
char buf[MAXLINE];
rio_t rio;
Rio_readinitb(&rio, connfd);
while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
printf("Server received %d bytes\n", (int)n);
Rio_writen(connfd, buf, n);
}
}
연결 시에 EOF가 갖는 의미
EOF라고 하는 문자는 존재하지 않음. 대신, 커널이 감지하는 조건임.
application은 read함수에서 0을 return code로 받을 때 EOF 조건을 알게 됨.
EOF는 디스크 파일에 대해서 현재 파일 위치가 파일 길이를 넘어가면서 발생.
- 인터넷 연결에서는 어떤 프로세스가 자신의 연결의 끝점을 닫을 때 EOF 발생.
- 연결의 다른 끝에 있는 프로세스는 스트림의 마지막 바이트를 지나서 읽으려고 할 때 EOF 감지
telnet
을 사용하는 Testing server
telnet
프로그램은 Internet 연결을 통해 아스키 문자열을 전송하는 서버 테스트를 하는데 매우 유용하다.- 사용 :
linux> telnet <host> <portnumber>
-
에서 작동하고 port에서 listen 하는 server와 연결을 생성한다.
Testing the Echo Server with telnet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
whaleshark> ./echoserveri 15213
Connected to (MAKOSHARK. ICS. CS. CMu.EDU, 50280)
server received 11 bytes
server received 8 bytes
makoshark> telnet whaleshark.ics.cs.cmy.edu 15213
Trying 128.2.210.175...
Connected to whaleshark.ics.cs.cmu.edu (128.2.210.175).
Escape character is '^]'.
Hi there!
Hi there!
Howdy!
Howdy!
^]
telnet> quit
Connection closed.
makoshark>