/**
* int open_clientfd(char *hostname, char *port)
* @include : <csapp.h>
* @brief : 파라미터로 받아오는 호스트이름과 포트번호로 되있는 서버에게 커넥션을 오픈
* (이 함수는 재진입을 지원하고 프로토콜로부터 독립적)
* @param[in] p->ai_family : int domain (32-bit IP address사용함을 나타냄)
* @param[in] p->ai_socktype : int type (이 소켓이 end-point임을 나타냄)
* @param[in] p->ai_protocol : int protocol
* @return : 성공시, 읽고 쓸 준비가 되어있는 소켓 디스크립터를 반환.
* 실패시, -1
*/
int open_clientfd(char *hostname, char *port) {
int clientfd, rc;
struct addrinfo hints, *listp, *p;
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 */
/**
* int getaddrinfo(hostname, port, &hints, &listp)
* freeaddrinfo(), gai_strerror()와 한세트세트.
* @include: <sys/types.h>, <sys/socket.h>, <netdb.h>
* @brief : 호스트 이름, 주소, 서비스이름, 포트넘버의 스트링을 socket address 구조로 바꿔줌
* @param[in] hostname->const char *host
* @param[in] port->const char *service
* @param[in] 앤퍼센트hints ->const struct addrinfo *hints
* @param[in] 앤퍼센트listp ->struct addrinfo **result
* 여기서 &listp가 더블포인터(**result)와 같은 이유: listp가 포인터라서.
* @return : 성공시, return 0
* 실패시, nonzero error code
*/
if ((rc = getaddrinfo(hostname, port, &hints, &listp)) != 0) {
fprintf(stderr, "getaddrinfo failed (%s:%s): %s\n", hostname, port, gai_strerror(rc));
return -2;
}
/* 링크드리스트를 하나씩 워크스루하며 성공적으로 연결가능한 것을 찾는다. addrinfo구조체에 링크드리스트의 다음 아이템을 위한 *ai_next 있음 */
for (p = listp; p; p = p->ai_next) {
/* socket descriptor 생성하기 */
/** int socket(p->ai_family, p->ai_socktype, p->ai_protocol)
* @include: <sys/types.h>, <sys/socket.h>
* @brief : socket descriptor를 생성함
* @param[in] p->ai_family : int domain (32-bit IP address사용함을 나타냄)
* @param[in] p->ai_socktype : int type (이 소켓이 end-point임을 나타냄)
* @param[in] p->ai_protocol : int protocol
* @return : 성공시, 양수의 descriptor. 실패시, -1
*/
if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue; /* 실패시 다음 시도. Socket failed, try the next */
/* 서버와 연결하기 */
/** int connect(clientfd, p->ai_addr, p->ai_addrlen)
* @include : <sys/socket.h>
* @brief : a 클라이언트가 서버와 연결을 한다
* @param[in] clientfd : int clientfd
* @param[in] p->ai_addr : const struct sockaddr *addr
* @param[in] p->ai_addrlen : socklen_t addrlen
* @return : 성공시, 0. 실패시, -1
*/
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;
}
/* $end open_clientfd */
/**
* int open_listenfd(char *port)
* @brief : listening descriptor를 오픈하고 리턴함
* (이 함수는 헬퍼함수로 재진입을 지원하고 프로토콜로부터 독립적)
* @param[in] port :
* @return : 성공시, 읽고 쓸 준비가 되어있는 리스닝 디스크립터를 반환.
* 실패시, -2 for getaddrinfo error
* -1 with errno set for other errors
*/
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 */
/* 디스크립터를 주소에 바인드하기 */
/** bind(listenfd, p->ai_addr, p->ai_addrlen)
* @include : <sys/socket.h>
* @brief : 디스크립터를 주소에 바인드. 커널에게 서버의 소켓 주소(addr)을 소켓 디스크립터(sockfd)와 연결하라고 요청함
* @param[in] listenfd : int sockfd
* @param[in] p->ai_addr : const struct sockaddr *addr
* @param[in] p->ai_addrlen : socklen_t addrlen
* @return : 성공시, 0. 실패시, -1
*/
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;
}
/* $end open_listenfd */
/* echoclient p.968 */
#include "csapp.h"
/**
* int main(int argc, char **argv)
* @include : <csapp.h>
* @brief : fgets함수가 EOF를 만날 때 까지, 반복적으로
* 텍스트 라인을 서버에게 보내고, 서버로부터 오는 에코라인을 읽고, 결과를 출력한다.
* 루프 종료시, 디스크립터를 클로즈한다.
* @param[in] argc : 안쓰임. 왜안쓰이지..?
* @param[in] 별별argv : argv[1] - host정보, argv[2] - port정보
* @param[in] p->ai_protocol : int protocol
*/
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); /* Associate a descriptor with a read buffer and reset buffer */
while (Fgets(buf, MAXLINE, stdin) != NULL) {
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd);
exit(0);
}
/* echoclient p.970 */
/**
* int main(int argc, char **argv)
* @include : <csapp.h>
* @brief : 리스닝 디스크립터를 열고 무한루프에 들어가서 반복적으로
* 클라이언트로부터의 연결을 기다리고,
* 연결된 클라이언트의 도메인 이름과 포트를 출력하고,
* 클라이언트의 echo 함수를 호출한다.
* @param[in] argc :
* @param[in] 별별argv : argv[0] - ?, argv[1] - port정보
*/
#include "csapp.h"
void echo(int connfd);
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
/**
* 여기 아래에 clientaddr을 주목할 필요가 있다.
* struct sockaddr_in 아니라 sockaddr_storage 구조체로 한 이유는
* 이 구조체는 어떤 타입의 소켓 주소를 담아도 충분히 크기 때문에 프로토콜에 독립적이기 때문이다.
* 따라서 accept가 리턴되기 전에 클라이언트 소켓의 주소를 clientaddr에 채워넣어진다.
*/
struct sockaddr_storage clientaddr;
char client_hostname[MAXLINE], client_port[MAXLINE];
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
/**
* Accept(listenfd, (SA *)&clientaddr, &clientlen)
* Accept(int s, struct sockaddr *addr, <error-type> *addrlen)
*/
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);
}
/**
* repeatedly reads and writes lines of text until the rio_readlineb function encounters EOF
*/
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) {
Rio_writen(connfd, buf, n);
}
}
인바운드 규칙 편집에서 테스트를 위한 규칙을 아래 그림과 같이 추가해준다.
포트번호는 well known port number 제외하고 아무거나 해주고 ip는 퍼블릭아이디/32로 해준다.(왜 32로 해줘야하는지는 모르겠다)
telnet을 brew install telnet
명령어로 다운하고
telnet 퍼블릭아이피주소 열어둔포트번호
명령어를 치면 연결이 되는 모습을 아래와 같이 확인할 수 있다.