소켓 인터페이스와 getaddrinfo 함수를 처음 배웠을 때 바로 사용하기란 쉽지 않을 것이다.
그래서 이번에는 한번에 클라이언트의 clientfd, 서버의 listenfd를 생성해주는 함수를 만들어 사용해보자.
두 함수는 이전 두 포스팅에서 다뤘던 소켓 인터페이스의 함수들과 getaddrinfo 함수를 이용한다.
open_clientfd 함수의 내용은 다음과 같다.
1. 접속을 원하는 서버와 포트의 주소를 getaddrinfo 함수를 통해 가져온다.
2. getaddrinfo를 통해 생성된 addrinfo 연결리스트 내 주소들과 connect를 시도한다.
3. connect에 성공한 clientfd를 반환한다.
int open_clientfd(char *hostname, char *port) {
int clientfd, rc;
struct addrinfo hints, *listp, *p;
//hints의 모든 필드 비우고 hints만들기
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM; //TCP소켓만 반환
hints.ai_flags = AI_NUMERICSERV; //포트 넘버로 반환
hints.ai_flags |= AI_ADDRCONFIG; //로컬 호스트가 와 같은 프로토콜을 사용하는 주소 반환
//getaddrinfo 함수 실행 (실패 시 -2 반환)
if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
return -2;
}
//getaddrinfo를 통해 얻은 addrinfo 연결리스트를 돌며 connet 시도
for (p = listp; p; p = p->ai_next) {
//clientfd 생성
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; //소켓 생성 실패하면 연결리스트의 다음 원소로
//연결에 성공했으면 for문을 나가고 연결에 성공한 clientfd 반환
if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
break;
//연결에 실패했으면 clientfd 소켓을 닫고 연결리스트의 다음 원소로
if (close(clientfd) < 0) {
fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
return -1;
}
}
//연결리스트 메모리 해제
freeaddrinfo(listp);
if (!p) //모든 connect가 실패했을 때
return -1;
else
return clientfd;
}
open_listenfd 함수도 open_clientfd 함수와 내용이 비슷하다.
1. getaddrinfo를 실행해 addrinfo 구조체의 연결리스트를 생성한다.
이때 host를 NULL로 설정하면 호스트 주소는 localhost가 할당이 된다.
2. getaddrinfo를 통해 생성된 addrinfo 연결리스트 내 주소들과 bind를 시도한다.
3. bind에 성공하면 bind에 성공한 소켓을 listen함수에 인자로 넣어 듣기 소켓으로 만들고 이를 반환한다.
4. bind에 실패했다면 연결리스트의 남은 주소들과 bind를 시도한다.
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, rc, optval=1;
//hints의 모든 필드 비우고 hints만들기
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; //AI_PASSIVE를 설정하면 듣기 소켓으로 이용할 수 있는 소켓 주소를 리턴함. 이때 host인자는 NULL이어야함.
hints.ai_flags |= AI_NUMERICSERV;
if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
return -2;
}
//getaddrinfo를 통해 얻은 addrinfo 연결리스트를 돌며 bind 시도
for (p = listp; p; p = p->ai_next) {
if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue;
//기존의 bind 되었던 소켓을 재사용하기 위해 SO_REUSEADDR 인자를 넣어 소켓의 옵셔을 변경해준다. (Eliminates "Address already in use" error from bind)
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break;
if (close(listenfd) < 0) {
fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
return -1;
}
}
freeaddrinfo(listp);
if (!p)
return -1;
//bind에 성공한 소켓이 있나면 listen함수에 인자로 넣어줘 연결 요청을 받을 수 있는 듣기 소켓으로 변환
if (listen(listenfd, LISTENQ) < 0) {
close(listenfd);
return -1;
}
return listenfd;
}