이전 글
에코 서버(1) - 호스트와 서비스 변환
에코 서버는 클라이언트가 표준 입력으로 받은 데이터를 서버가 받아서 그대로 돌려주면 클라이언트가 받은 데이터를 표준 출력하는 간단한 프로그램이다. CSAPP 책에서는 CGI 프로그램을 이해하
nkdev.tistory.com
이전 포스팅에서 배웠던 getaddrinfo()함수와 소켓 인터페이스들은 open_clientfd(), open_listenfd()로 감싸서 사용한다.
getaddrinfo()를 설명하면서 잠깐 소개된 함수들인데 이번 포스팅에서 자세히 다뤄볼 예정이다.
소켓 인터페이스를 위한 도움함수들
1. open_clientfd()
클라이언트 측에서 서버와 연결할 때 사용하는 함수
서버의 hostname, port를 전달하면 연결 가능한 서버 소켓 후보들 중 하나와 연결을 맺는다.
내부적으로 socket()->connect()를 수행한 후 clientfd를 반환한다.
세부적인 절차는 다음과 같다.
- 인자로 연결 맺기를 원하는 hostname, port를 전달한다.
- getaddrinfo()로 host, port를 이용해 서버 port 중 연결 가능한 후보들을 addrinfo 구조체의 리스트 listp로 반환받는다.
- hints로 연결 맺기를 원하는 소켓이 어떤 종류인지 커스텀하면 listp는 그 설정에 맞는 구조체만 반환한다.
- for문으로 listp를 하나씩 방문하여 연결 가능한 소켓이 있다면 연결(socket(), connect() 호출)하고, 하나의 연결이 성사되면 break로 반복문을 빠져나온다.
- 연결 완료 후 얻은 식별자 clientfd 즉 UNIX I/O 함수를 이용한 입력, 출력에 대해 준비된 열린 소켓을 리턴한다.
int open_clientfd(char *hostname, char *port) {
int clientfd, rc;
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 */
if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
return -2;
}
/* 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 */
if (close(clientfd) < 0) { /* Connect failed, try another */ //line:netp:openclientfd:closefd
fprintf(stderr, "open_clientfd: close failed: %s\n", strerror(errno));
return -1;
}
}
/* Clean up */
freeaddrinfo(listp);
if (!p) /* All connects failed */
return -1;
else /* The last connect succeeded */
return clientfd;
}
2. open_listenfd()
서버가 listen 소켓을 생성하는 함수
마찬가지로 getaddrinfo()를 통해 반환받은 listp를 순회하면서 bind 가능한 포트를 찾는다.
bind()를 성공하면 listen()으로 클라이언트의 connect 요청을 기다리게 만든 후 listenfd를 반환한다.
정리하면 내부적으로 socket() -> bind() -> listen()을 수행하고 listenfd 반환
int open_listenfd(char *port)
{
struct addrinfo hints, *listp, *p;
int listenfd, rc, 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 */
if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
return -2;
}
/* Walk the list for one that we can bind to */
for (p = listp; p; p = p->ai_next) {
/* Create 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(listenfd, SOL_SOCKET, SO_REUSEADDR, //line:netp:csapp:setsockopt
(const void *)&optval , sizeof(int));
/* Bind the descriptor to the address */
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break; /* Success */
if (close(listenfd) < 0) { /* Bind failed, try the next */
fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
return -1;
}
}
/* 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(), open_listenfd()함수는 csapp.c에서 Open_clientfd(), Openlistenfd()로 또 한번 감싸져서 사용되는데, 이 과정에서 추가적인 에러 처리를 해주고 있다. 그래서 우리는 직접 쓸 때 대문자로 시작하는 함수를 사용해야 한다.
에코 서버에서 쓰이는 함수는 이 정도 깊이로만 알아둬도 될 것 같다.
CSAPP에서 만든 함수들은 다 추상화가 너무 잘 되어있어서 소켓 하나 연결할 때도 open_listenfd() 안에 socket(), bind(), listen()과정이 다 감춰져있고 파일을 하나 읽을 때도 rio_readlineb() 안에 rio_read(), 또 그 안에 read()시스템호출이 숨겨져있다.
그래서 최종적인 서버 코드와 클라이언트 코드를 파악하는 데 시간이 오래 걸리긴 하지만!
이렇게 하나 하나 파다 보면 재밌다. 어떤 함수가 무슨 역할을 하고.. 또 어디서 쓰이는지 알아가다 보면 시간이 너무 빨리 간다.
그저 일주일 안에 많은 내용을 한꺼번에 습득해야 해서 항상 시간이 모자랄 뿐..
그리고 요즘 느낀 건데 정글에서 공부하면서 내 공부 스타일을 어떤 방향으로 바꿔야될지 깨달았다.
개발자로 일하려면 학자 스타일로 파고들기보다는 제공된 인터페이스를 제대로 사용할줄 아는 능력이 필요하다!!
으악 이제 빨리 tiny webserver 해야지...하루도 안 남았다
'정글' 카테고리의 다른 글
| 9주차 퀴즈 : Deadlock 발생 조건 / 해결 방법, 포인터 연산, 메모리 해제 (0) | 2025.05.13 |
|---|---|
| 웹 서버와 CGI (0) | 2025.05.08 |
| 에코 서버 실행하기 (4) | 2025.05.06 |
| gcc 컴파일 하는 법 (0) | 2025.05.06 |
| OSI 7 Layers/TCP/UDP/HTTP (4) | 2025.05.06 |