336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
소켓(Socket)의 이해
2.1.1 소켓의 정의
▶ 소켓(socket)은 1982년 BSD(Berkeley Software Distribution) UNIX 4.1에서 처음 소개되으며 현재 널리 사용되는 것은 1986년의 BSD UNIX 4.3에서 개정된 것이다.
▶ 소켓은 소프트웨어로 작성된 통신 접속점이라고 할 수 있는데 네트웍 응용 프로그램은 소켓을 통하여 통신망으로 데이터를 송수신하게 된다.
▶ 그림 2-1에 세 개의 응용 프로그램이 각각 소켓을 통하여 TCP/IP를 공유하고 있는 것을 나타냈다.
▶ 소켓은 응용 프로그램에서 TCP/IP를 이용하는 창구 역할을 하며 응용 프로그램과 소켓 사이의 인터페이스를 소켓 인터페이스라고 한다.
▶ 한 컴퓨터내에는 보통 한 세트의 TCP/IP가 수행되고 있으며, 네트웍 드라이버는 LAN 카드와 같은 네트웍 접속 장치(NIU: Network Interface Unit)를 구동하는 소프트웨어를 말한다.
2.1.2 소켓번호
▶UNIX에서 파일을 새로 열면(open) int 타입의 정수를 리턴하는데 이와같이 open문이 리턴한 정수를 파일기술자(file descriptor)라고 하며 프로그램에서 open된 파일을 액세스할 때 이 파일기술자를 사용하게 된다.
▶ 파일기술자는 기술자 테이블(descriptor table)의 index 번호인데(그림 2-2 참조), 기술자 테이블이란 현재 open되어 있는 파일의 각종 정보를 포함하고 있는 구조체를 가리키는 포인터들로 구성된 테이블이다.
▶ 예를들어 한 응용 프로그램내에서 2개의 파일을 open하면 파일기술자는 3과 4가 배정된다
▶ 프로그램에서 소켓을 개설하면 파일기술자와 똑같은 기능을 하는 소켓기술자(socket descriptor)가 리턴된다.
▶ 응용 프로그램에서 이 소켓을 통하여 목적지 호스트와 연결을 요구하거나 패킷을 송수신할 때 해당 소켓기술자를 사용하게 된다(이 책에서는 편의상 소켓기술자를 소켓번호라고 부르겠다).
▶ UNIX에서는 파일기술자와 소켓기술자가 같은 기술자 테이블의 index가 된다. 즉, 파일과 소켓이 기술자 테이블을 공유한다.
▶ 한 프로세스에서 파일 open시 리턴되는 파일기술자와 소켓 개설시 리턴되는 소켓기술자의 값은 서로 중복된 것이 없게 된다.
▶ 그림 2-2에는 두 개의 파일과 한 개의 소켓을 개설하였을 때의 기술자 테이블과 기술자 테이블이 가리키는 파일 또는 소켓 데이터 구조체를 나타내고 있다.
▶ 그림 2-3에 응용 프로그램과 소켓 그리고 TCP/IP의 관계를 구체적으로 나타냈다.
▶ 네 개의 응용 프로그램이 소켓번호로 각각 4, 3, 3, 3을 사용하고 있는 것을 나타냈다(응용 프로그램 1은 파일을 하나 먼저 열고 있으므로 소켓번호가 4가 된 것이다).
▶ 한편 소켓번호는 응용 프로그램내에서 순서대로 배정되며 그 프로그램내에서만 유일하게 구분되면 되는 것이므로 서로 다른 응용 프로그램에서 같은 소켓번호를 사용하는 것은 문제가 되지 않는다.
▶ 포트번호는 TCP/IP가 지원하는 상위 계층의 프로세스를 구분하기 위한 번호이므로 하나의 컴퓨터내에 있는 응용 프로세스들은 반드시 서로 다른 포트번호를 사용하여야 한다.
▶ 그림 2-3에서는 네 개의 응용 프로그램이 3000번부터 3003번의 포트번호를 사용하는 것을 가정하였다.
▶ 그림 2-3에서 연결형 서비스는 TCP가 그리고 비연결형 서비스는 UDP가 각각 처리하는 것을 보였다.
2.1.3 소켓의 이용
▶ 소켓을 이용한 네트웍 응용 프로그램에서 상대방과 IP 패킷을 주고받기 위하여는 다음의 다섯 가지 정보가 정해져야 한다.
① 통신에 사용할 프로토콜(TCP 또는 UDP)
② 자신의 IP 주소
③ 자신의 포트번호
④ 상대방의 IP 주소
⑤ 상대방의 포트번호
▶ 통신에 사용할 프로토콜은 연결형 또는 비연결형을 말하는데 인터넷 프로그램에서는 연결형 서비스를 TCP 또는 스트림(stream) 서비스라고도 부르고, 비연결형 서비스를 UDP 또는 데이터그램 서비스라고도 부른다.
▶ 자신의 IP 주소는 응용 프로그램이 수행되는 컴퓨터의 IP 주소를 말하며, 자신의 포트번호는 이 컴퓨터에서 수행되고 있는 응용 프로그램들 중 본 응용 프로그램을 구분하는 고유번호이다.
▶ 상대방의 IP 주소는 통신하고자 하는 상대방(목적지) 컴퓨터의 IP 주소를 말하며, 상대방의 포트번호는 목적지 컴퓨터내에서 수행중인 여러 응용 프로그램 중 나와 통신할 프로그램을 지정하는 번호이다.
▶ 잘 알려진(well-known) 표준 인터넷 서비스(ftp, mail, http 등)를 처리하는 서버 프로그램은 미리 지정된 포트번호를 사용하고 있다.
▶ 소켓 프로그래밍에서 첫번째로 해야 할 일은 통신 창구 역할을 하는 소켓을 만드는 것으로 이것은 서버와 클라이언트에서 모두 필요한데 이를 위하여 socket() 시스템 콜을 호출한다.
▶socket()이 성공적으로 수행되면 새로 만들어진 소켓번호(int 타입)를 리턴하고 에러가 발생하면 -1이 리턴된다.
▶ socket()의 사용 문법은 다음과 같다.
int socket (
domain, /* 프로토콜 체계 */
type, /* 서비스 타입 */
protocol; /* 소켓에 사용될 프로토콜 */
▶ 소켓은 본래 TCP/IP, 즉 인터넷만을 위하여 정의된 것이 아니며 UNIX 네트웍, XEROX 네트웍 등에서도 사용할 수 있도록 일반적으로 정의된 것이다.
▶ TCP/IP 프로토콜을 사용하려면 소켓을 개설할 때 프로토콜 체계를 인터넷으로 지정하여야 한다.
▶ 이를 위하여 domain을 PF_INET으로 선택하여야 한다.
▶ 한편 domain으로 가질 수 있는 값은 다음과 같다(PF는 Protocol Family를 나타냄).
domain : PF_INET (인터넷 프로토콜 체계 사용)
PF_UNIX (UNIX 방식의 푸로토콜 체계 사용)
PF_NS (XEROX 네트웍 시스템의 프로토콜 체계 사용)
▶ type은 서비스 타입(type of service)을 말하는데, 연결형(스트림) 서비스를 제공하는 소켓을 만들려면 SOCK_STREAM을, 비연결형(데이터그램) 서비스를 선택하려면 SOCK_ DGRAM을 선택하여야 한다.
type : SOCK_STREAM (스트림 방식의 소켓 생성)
SOCK_DGRAM (데이터그램 방식의 소켓 생성)
▶ protocol은 소켓을 지원하는 프로토콜을 지정하는데 일반적으로 0을 쓰면 시스템이 자동으로 설정해 준다.
▶ 다음은 socket() 시스템 콜을 호출하고 생성된 소켓번호를 출력하는 예제 open_socket.c이다.
▶ 이 프로그램에서는 먼저 /etc/passwd 파일을 열고 리턴된 파일기술자(file descriptor)를 출력한 후, 소켓을 두 개 열어서 소켓번호가 어떤 값인지 확인한다.
/* 스트림형 소켓 열기 */
sd1 = socket(PF_INET, SOCK_STREAM, 0) ;
printf("stream socket descriptor = %d\n", sd1) ;
/* 데이터그램형 소켓 열기 */
sd2 = socket(PF_INET, SOCK_DGRAM, 0) ;
printf("datagram socket descriptor = %d\n", sd2) ;
/* 또다른 파일 열기 */
close(sd2) ;
close(sd1) ;
▶ 위의 예제 open_socket.c의 실행 결과는 다음과 같다.
stream socket descriptor = 3
datagram socket descriptor = 4
▶ socket() 시스템 콜은 트랜스포트 프로토콜(TCP 또는 UDP)을 선택하여 하나의 소켓을 만드는 함수이다.
▶ 리턴된 소켓번호는 응용 프로그램내에서 생성된 파일과 소켓을 구분하는 유일한 번호인 것을 알 수 있다.
2.1.4 소켓주소 구조체
▶ 소켓을 이용할 통신 객체(클라이언트 또는 서버)의 구체적인 주소를 표현하기 위해서는 주소 체계(address family), IP 주소, 포트번호 세 가지가 지정되어야 하며 이 세 가지 주소 정보를 소켓주소(socket address)라고 부른다.
▶ 소켓 프로그래밍에서는 소켓주소를 담을 구조체 sockaddr를 아래와 같이 정의하였으며 이것은 2바이트의 address family와 14바이트의 주소(IP 주소 + 포트번호)로 구성되어 있다.
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* 주소 */
};
▶ 그런데 위에 정의된 sockaddr 소켓주소 구조체에 IP 주소, 포트번호 등을 직접 쓰거나 읽기가 불편하므로 인터넷 프로그래밍에서는 sockaddr 구조체를 사용하는 대신 4바이트의 IP 주소와 2바이트의 포트번호를 구분하여 지정할 수 있는 인터넷 전용 소켓주소 구조체인 sockaddr_in을 주로 사용한다.
▶ sockaddr_in에서는 다시 32비트의 IP 주소를 저장하는 구조체 in_addr를 사용하고 있으며 sockaddr_in과 sockaddr의 호환성을 위하여 두 구조체의 전체 크기는 16바이트로 같도록 하였다.
struct in_addr {
u_long s_addr; /* 32비트의 IP 주소를 저장할 구조체 */
};
struct sockaddr_in {
short sin_family; /* 주소 체계 */
u_short sin_port; /* 16비트 포트번호 */
struct in_addr sin_addr; /* 32비트 IP 주소 */
char sin_zero[8]; /* 16바이트 크기를 맞추기 위한 dummy */
};
▶ 위에서 주소 체계 sin_family로 선택할 수 있는 것은 다음과 같으며 인터넷에서는 항상 인터넷 주소 체계(AF_INET)를 선택하여야 한다.
sin_familly : AF_INET (인터넷 주소 체계)
AF_UNIX (유닉스 파일 주소 체계)
AF_NS (XEROX 주소 체계)
▶ 소켓주소의 주요 내용은 IP 주소와 포트번호인데, 소켓주소는 응용 프로그램이 자신의 소켓주소(local address)를 표현하는 데에도 사용되며 상대방 프로세스의 소켓주소(remote address)를 표현할 때도 사용된다(즉, 2.1.3절의 ②+③ 또는 ④+⑤를 각각 나타내기 위해 소켓주소 구조체가 사용된다).
2.1.5 소켓 프로그래밍 절차
▶ 소켓 프로그래밍도 대표적인 네트웍 프로그래밍으로서 1.3절에서 설명한 클라이언트-서버 통신 모델로 구현된다.
▶ 소켓을 이용한 클라이언트와 서버의 프로그래밍 절차를 간략히 설명하고, 2.3절과 2.4절에서 클라이언트와 서버 프로그램의 작성 과정을 상세히 설명하겠다.
▶ 그림 2-4에 클라이언트와 서버가 TCP(스트림형 또는 연결형) 소켓을 만들고 서로 연결한 다음 데이터를 송수신하고 소켓을 종료하는 절차를 나타냈다.
▶ 클라이언트-서버 통신 모델에서는 항상 서버 프로그램이 먼저 수행되고 있어야 한다.
▶ 서버는 socket()을 호출하여 통신에 사용할 소켓을 하나 개설하고 이때 리턴된 소켓번호와 자신의 소켓주소 (2.1.3절의 ②+③)를 bind()를 호출하여 서로 연관시켜 둔다.
▶ 서버에서 bind()가 필요한 이유는 소켓번호는 응용 프로그램이 알고 있는 통신 창구 번호이고, 소켓주소(②+③)는 네트웍 시스템(즉, TCP/IP)이 알고 있는 주소이므로 이들의 관계를 묶어 두어야(bind) 응용 프로세스와 네트웍 시스템간의 패킷 전달이 가능하기 때문이다(bind()의 자세한 사용법은 2.4.1절에서 설명함).
▶ 다음에 서버는 listen()을 호출하여 클라이언트로부터의 연결요청을 기다리는 수동 대기모드로 들어간다.
▶ 클라이언트로부터의 연결요청이 왔을 때 이를 처리하기 위하여 accept()를 호출한다.
▶ 서버는 accept() 시스템 콜에서 기다리고 있다가 클라이언트가 connect()를 호출하여 접속요구를 해오면 이를 처리한다.
▶ 연결이 성공적으로 이루어지면 서버는 클라이언트와 데이터를 송수신할 수 있게 된다.
▶ 한편 클라이언트는 socket()을 호출하여 소켓을 만든 후 bind()를 부를 필요 없이, 서버에게 연결요청을 보내기 위하여 connect()를 호출한다.
▶이때 클라이언트는 접속할 상대방 서버의 소켓주소(④+⑤) 구조체를 만들어 connect()의 함수 인자로 주어야 한다.
▶ 서버와 연결이 이루어지면 (즉, connect()문이 성공적으로 리턴되면) 서버와 데이터를 송수신할 수 있다.
▶ 클라이언트에서 bind()를 호출할 필요가 없는 이유는, 클라이언트 프로그램은 서버 프로그램과 달리 자신이 사용하는 IP 주소나 포트번호를 다른 클라이언트 또는 서버가 미리 알고 있을 필요가 없기 때문이다.
▶ 서버의 응용 프로그램은 자신이 사용하고 있는 포트번호를 통하여 클라이언트들의 서비스를 처리해야 하므로, 응용 프로그램이 소켓번호와 소켓주소를 bind()하는 것이 필수적이다.
▶ 클라이언트는 포트번호를 임의로 사용해도 되기 때문에 포트번호를 특정한 값으로 bind()시켜 두는 것이 필요 없다.
▶ 클라이언트는 오히려 bind()를 사용하는 것이 클라이언트 프로그램의 범용성을 떨어뜨리게 된다. 왜냐하면 같은 포트번호를 사용하는 클라이언트 프로그램들이 하나의 컴퓨터에서 두 개 이상 실행되면 에러가 발생하기 때문이다.