CSAPP 11단원 공부

CSAPP11 네트워크 프로그래밍(1)

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이 아님.)

  1. CLIENT가 서비스를 필요할 때, CLIENT는 한 개의 요청(request)을 서버에 보내는 것으로 transaction을 개시.
  2. SERVER는 request를 받고, 해석하고, 자신의 자원들을 적절하게 조작.
  3. SERVER는 응답(response)을 CLIENT로 보내고, 다음 요청 기다림.
  4. 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의 소문자 대문자 차이)

cs11_1_1

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에서 도달 가능한지 학습. 이후 필요시 선택적으로 하나의 포트에서 다른 포트로 프레임 복사.

cs11_1_3

예.

  1. Host A가 같은 segment 내 Host B로 한 프레임 전송 : 브릿지 X는 프레임이 입력 포트에 도달했을 때 프레임 버린다.(다른 세그먼트에서의 대역폭 절역)

  2. Host A가 다른 segmnet 내 Host C로 한 프레임 전송 : 이 프레임을 브릿지 Y에 연결된 포트로만 복사, 호스트 C의 세그먼트에 열견된 포트로만 이 프레임 복사.

Next Level : internets

rounters(라우터)

  • 계층구조의 상부에서 다수의 비호환성 LAN들을 연결하는 특별한 컴퓨터. 네트워크 간 연결을 구성(상호연결 네트워크)
  • 이 연결된 네트워크를 internet이라고 부름

  • 각 라우터는 이들이 연결되는 각 네트워크에 대해 어댑터(포트)를 가짐
  • 고속의 point-to-point 전화 연결 가능, WAN(Wide-Area-Network)의 사례( LAN보다 지리적으로 넓게 운용됨)
  • 임의의 LAN과 WAN들로부터 internet을 만들기 위해서 사용될 수 있음

cs11_1_4

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 주소 최소한 한 개 할당
  • 전달기법(delivery mechanism,두번째 기능) : 서로 다른 네트워킹 기숧은 서로 다른 비호환성을 갖는 비트 인코딩 방법과 프레임 내에 이들을 패키징하는 방법 사용
    • internet protocol은 데이터 비트를 패킷이라는 비연속적인 단위로 묶는 통일된 방법을 정의해서 이 차이 줄임
      • 패킷Header(패킷 크기와 소스 및 목적지 호스트 주소 포함) + data(소스 호스트가 보낸 데이터 비트를 포함)

cs11_1_6

ex.

호스트와 라우터가 호환성이 없는 LAN을 통해 데이터를 전송하기 위해 internet protocol을 사용하는 방법

internet : 하나의 라우터에 연결된 두 개의 LAN

호스트 A에서 돌아가는 LAN1에 연결된 클라이언트 : LAN2에 연결된 호스트 B에서 돌고 있는 서버로 일련의 데이터 바이트 전송(8단계)

  1. 호스트 A의 클라이언트는 클라이언트의 가상 주소 공간에서 커널 버퍼로 데이터를 복사하는 시스템 콜 호출
  2. 호스트 A의 프로토콜 소프트웨어가 internet 헤더와 LAN1 프레임 헤더를 데이터에 추가해서 LAN1 프레임을 생성
  • internet 헤더는 internet 호스트 B로 주소 지정
  • 호스트 A는 이 프레임을 어댑터로 전달
  • LAN1 프레임의 데이터가 internet 패킷이고, 그 데이터는 실제 사용자 데이터
  • 위와 같은 것을 encapsulation(캡슐화)라고 함
  1. LAN1 어댑터는 이 프레임을 네트워크로 복사
  2. 프레임이 라우터에 도달하면, 라우터의 LAN1 어댑터는 전선에서 이것을 읽어서 프로토콜 소프트웨어로 전달
  3. 라우터는 internet 패킷 헤더에서 목적지 internet 주소를 가져와서 패킷을 전달할 곳을 결정(이 경우엔 LAN2 라우팅 테이블에서의 인덱스로 이것을 사용). 라우터는 이전의 LAN1 프레임 헤더를 벗겨내고, 호스트 B의 주소를 갖는 새로운 LAN2 ㅡㅍ레임 헤더를 앞에 붙여서 이것을 어댑터로 전달
  4. 라우터의 LAN2 어댑터는 이 프레임을 네트워크로 복사
  5. 이 프레임이 호스트 B에 도착하면 어댑터는 이 프레임을 전선에서 읽어들이고, 프로토콜 소프트웨어로 넘김
  6. 호스트 B의 프로토콜 소프트웨어는 패킷 헤더, 프레임 헤더 벗겨냄. 프로토콜 소프트웨어는 최종적으로 이 데이터를 서버가 이 데이터를 읽는 시스템 콜을 호출할 때 서버의 가상 주소공간으로 복사.

cs11_1_5

더 배워야할 것

  • 서로 다른 네트워크들이 서로 다른 최대 프레임 크기를 갖는다면?
  • 라투덜은 어디로 프레임을 전달해야 할지 어떻게 알까?
  • 라우터들은 언제 네트워크 구조가 변경되는지 어떻게 알까?
  • 패킷이 손실된다면 어떻게 될까?

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하는 매커니즘. 단어들의 배열, 점 으로 구분

도메인 이름들의 집합 : 계층구조 형성

계충구조

  1. 이름이 없는 루트노드 : 첫번째 단계
  2. ICANN(Internet Corporation for Assigned Names and Numbers) : 비영리조직
  3. 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

  1. Simple case : 도메인 이름과 IP 주소의 1:1 mapping

linux> nslookup whaleshark.ics.cs.cmu.edu

Address: 128.2.210.175

  1. 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

  1. 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

  1. 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.
  1. echo server : 7/echo
  2. ssh servers: 22/ssh
  3. email server : 25/smtp
  4. Web servers: 80/http

연결 : 두 개의 종단점의 소켓 주소에 의해 유일하게 식별 => socket pair(소켓 쌍), tuple로 나타낸다.

(cliaddr:cliport, servaddr:servport)

cliaddr : client의 IP 주소

cliport : client의 포트

servaddr : server의 IP 주소

servport : server의 포트


cs11_1_7

11.4 소켓 인터페이스

Network application을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합

cs11_1_9

11.4.1 소켓 주소 구조체

cs11_1_11

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_INET
  • sin_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함수

  1. client로부터의 연결 요청이 듣기 실별자 listenfd에 도달하기 기다림
  2. 그 후 addr 내의 client의 소켓 주소를 채움
  3. Unix I/O 함수들을 사용하여 client와 통신하기 위해 사용될 수 있는 연결 식별자를 반환

듣기 식별자와 연결 식별자 사이의 구분

듣기와 연결 식별자 사이에 구분하는 이유

구분을 통해 많은 클라이언트 연결을 동시에 처리할 수 있도록 하는 동시성 서버를 만들 수 있음. 예를 들어, 연결 요청이 듣기 식별자에게 도달할 때마다 연결 식별자에 대해 클라이언트와 통신하는 새로운 프로세스를 fork 할 수 있음.

듣기 식별자

  • client 연결 요청에 대해 끝점으로서의 역할
  • 보통 한 번 생성, server가 살아있는 동안 계속 존재

cs11_1_8

연결 식별자

  • 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

cs11_1_10

  • host와 service(소켓 주소의 두 개의 구성요소)가 주어지면, getaddrinfo는 각각이 host와 service에 대응되는 소켓 주소 구조체를 가리키는 addrinfo 구조체의 연결리스트를 가리키는 result를 리턴

  • client 가 getaddrinfo를 호출한 후 이 리스트를 방문하며, 각 소켓 주소를 socket과 connect로의 호출이 성공하고, 연결이 성립할 때까지 차례로 시험

  • Helper functions 두 개

    1. 메모리 누수 피하기 위해, application은 freeaddrinfo를 호출해서 이 리스트 반환해야함

    2. 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 라는 프로그램.

getaddrinfogetnameinfo를 이용해서 도메인 이름과 연관된 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>