소켓 인터페이스를 위한 도움함수들

Run·2021년 9월 21일
0

getaddrinfo 함수와 소켓 인터페이스들은 처음 배울 때 바로바로 활용하기 어렵다.
그래서 clientfd와 listenfd를 만드는 과정들을 하나의 함수에 때려박아보자.

open_clientfd

  1. 접속을 원하는 호스트와 포트를 파라미터로 입력한다.
  2. 접속을 원하는 서버와 포트의 주소를 getaddrinfo 함수를 통해 가져온다.
  3. getaddrinfo를 통해 생성된 addrinfo 연결리스트 내 주소들connect를 시도한다.
  4. 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

  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;
}
profile
정글에서 살아남기

0개의 댓글