소켓 인터페이스는 네트워크 어플리케이션을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합이다.
통신을 위한 끝점(End point of a connection). 각 소켓은 인터넷 주소와 16비트 정수 포트
로 이루어진 소켓 주소를 가지며, address: port
로 나타낸다. 소켓은 모든 운영체제에서 지원한다.
(데이터를 주고 받기 위해서 소켓 디스크립터(socket descriptor)라는 파일 시스템을 이용)
클라이언트
의 소켓 주소 내의 포트는 클라이언트가 연결 요청을 할 때 커널이 자동으로 할당한다.서버
의 소켓 주소에 있는 포트는 대개 영구적으로 이 서비스에 연결되는 Well-known port
이다.예로
웹 서버는 대개 80
을 사용하고이메일 서버 포트는 25
를 사용한다.
인터넷 연결의 구조
리눅스 커널의 관점에서 보면 소켓은 통신을 위한 끝점이며, Unix 프로그램의 관점에서 보면 소켓은 해당 식별자를 가지는 열린 파일이다.
connect
,bind
,accept
함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터를 필요로 한다.
/* IP socket address structure */
struct sockaddr_in {
uint16_t sin_family; /* Protocol family (always AF_INET) 2*/
uint16_t sin_port; /* Port number in network byte order 2*/
struct in_addr sin_addr; /* IP address in network byte order 4*/
unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */
};
/* Generic socket address structure (for connect, bind, and accept) */
struct sockaddr {
uint16_t sa_family; /* Protocol family */
char sa_data[14]; /* Address data */
};
sockaddr_in 구조체를 sockaddr 구조체로 캐스팅할 필요가 있을 때는 아래와 같이 사용하면 된다.
// 소켓의 주소를 담는 기본 구조체 틀의 역할(socket.h에 정의되어 있음)
typedef struct sockaddr SA;
소켓 인터페이스 기반 네트워크 응용 프로그램의 개요는 아래의 그림과 같다.
socket(): 소켓(file descriptor) 생성하는 함수
bind(): 소켓과 서버의 정보 연결하는 함수(외부 호스트에서 서버 연결 요청 시, 소켓 디스크립터 번호 알아야 함)
listen(): 어떤 컴퓨터로부터 요청이 와도 수락할 수 있게 대기상태에 들어가도록 하는 함수
accept(): 서버 소켓과 클라이언트를 연결하는 함수
connect(): 서버에 연결 요청하는 함수. 서버가 바쁘면 대기열에 넣었다가 때가 되면 accept
getaddrinfo함수는 도메인 주소값을 IP주소값으로 변환할 때 사용하는 함수다. host, service, hint값을 입력하여 result로부터 정보를 추출하는 구조이다.
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, // (ex) "www.example.com" or IP
const char *service, // (ex) "http" or port number
const struct addrinfo *hints,
struct addrinfo **result); // DNS서버로부터 받은 네트워크 주소 정보(IP)를 돌려주는 output 매개변수
//Returns: 0 if OK, nonzero error code on error
// 사용 후 메모리 해제(메모리 누수 방지)
void freeaddrinfo(struct addrinfo *result);
const char *gai_strerror(int errcode);
host에는 도메인 이름이나 정수형인 IP주소를 넣을 수 있다. 호스트 이름을 주소로 변환하고 싶지 않은 경우 이 자리에 NULL을 넣으면 된다.
service에는 서비스 이름이나 포트 번호를 넣으면 된다. 호스트와 마찬가지로 NULL값으로 설정할 수 있지만, host, service 둘 중 한 개는 명시되어야 한다.
struct addrinfo {
int ai_flags; // 기본 동작을 더 수정하는 비트마스크
int ai_family; // AF_INET -> IPv4, AF_INET6 -> IPv6
int ai_socktype; // TCP 경우 SOCK_STREAM
int ai_protocol;
char *ai_canonname;
size_t ai_addrlen; // 소켓 주소 구조체의 크기
struct sockaddr *ai_addr; // 소켓 주소 구조체 가리킴
struct addrinfo *ai_next; // 다음 addrinfo구조체 가리킴
};
getaddrinfo의 가장 큰 장점 중 하나는 addrinfo의 필드들이 추가적인 조작 없이 소켓 인터페이스에서 함수들로 직접 전달될 수 있다는 것이다.
이전 포스트에서 설명했던 connet와 bind 함수에 addrinfo의 ai_family, ai_socktype, ai_protocol 인자는 직접 전달될 수 있다.
getnameinfo 함수는 getaddrinfo 함수의 역이다. 이 함수는 소켓 주소 구조체를 대응되는 호스트와 서비스 이름 스트링으로 변환한다.
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
성공시 0 반환, 실패시 0이 아닌 에러코드 반환
sa인자는 salen 바이트 크기의 소켓 주소 구조체가 들어간다. host는 hostlen 크기의 버퍼, service는 servlen 크기의 버퍼가 들어간다.
함수의 실행이 성공하면 소켓 주소 구조체 sa를 대응되는 호스트와 서비스 이름 스트링으로 변환하고, 이들을 host와 service 버퍼로 복사한다.
호스트 이름을 원하지 않거나 서비스 이름을 원치 않는다면 둘 중 하나는 NULL로 설정이 가능하다.
flags 인자는 비트 마스크로 기본 동작을 수정한다.
flags = NI_NUMERICHOST를 설정한다면 기본값으로 host를 도메인 이름으로 반환하는 것 대신 ip주소 스트링을 대신해서 반환한다.
NI_NUMERICSERV를 설정한다면 서비스 이름 대신 포트 번호를 반환한다.