socket select/poll

Network/Network 2011. 12. 22. 10:11

4.2.2 select/poll 의 이용

select/poll 은 입출력 다중화를 위한 목적으로 주로 사용된다. 그러나 이들 함수의 경우 스스로가 Time out 을 결정하기 위한 방법을 제공함으로 비록 입출력다중화의 목적이 아닌 단순한 Time out 결정을 위해서도 유용하게 사용할수 있다.

#include <unistd.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <sys/socket.h> 
#include <sys/stat.h> 
#include <arpa/inet.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/time.h> 
#include <sys/types.h> 
 
int main(int argc, char **argv) 
{ 
    int server_sockfd, client_sockfd; 
    int client_len, n; 
    int state; 
    char buf[80]; 
    struct sockaddr_in clientaddr, serveraddr; 
 
    // select time out 설저을 위한 timeval 구조체     
    struct timeval tv; 
    fd_set readfds; 
 
    client_len = sizeof(clientaddr); 
    if ((server_sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) 
    { 
        perror("socket error : "); 
        exit(0); 
    } 
    bzero(&serveraddr, sizeof(serveraddr)); 
    serveraddr.sin_family = AF_INET; 
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    serveraddr.sin_port = htons(atoi(argv[1])); 
 
    bind (server_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); 
    listen(server_sockfd, 5); 
 
    while(1) 
    { 
        memset(buf, 0x00, 80); 
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&clientaddr, 
                            &client_len); 
        // client_sockfd 의 입력검사를 위해서  
        // fd_set 에 등록한다.  
        FD_ZERO(&readfds); 
        FD_SET(client_sockfd, &readfds); 
        // 약 5초간 기다린다.  
        tv.tv_sec = 5; 
        tv.tv_usec = 10; 
 
        // 입력이 있는지 기다린다.  
        state = select(client_sockfd+1, &readfds, 
                        (fd_set *)0, (fd_set *)0, &tv); 
        switch(state) 
        { 
            case -1: 
                perror("select error :"); 
                exit(0); 
            // 만약 5초안에 아무런 입력이 없었다면  
            // Time out 발생상황이다.  
            case 0: 
                printf("Time out error\n"); 
                break; 
            // 5초안에 입력이 들어왔을경우 처리한다.  
            default: 
                if ((n = read(client_sockfd, buf, 80)) <= 0) 
                { 
                    perror("read error : "); 
                    usleep(10); 
                    break; 
                } 
                if (write(client_sockfd, buf, 80) <=0) 
                { 
                    perror("write error : "); 
                    break; 
                } 
                break; 
        } 
        close(client_sockfd); 
    } 
} 
 

여기에서는 select 만을 예로 들었는데 poll 로도 비슷하게 구현 가능하다. select 를 이용할경우 alarm() 에 비해서 신뢰성있게 서버를 구성하는게 가능하다. 그러나 입출력다중화 + time out 검사용으로 사용하기에는 적당하지 가 않다. 위에서의 경우에는 단지 하나의 연결에 대해서만 time out 을 검사했는데, 만약 여러개의 연결을 받아들여서 입출력 다중화를 할경우 select 는 모든 입력에 대한 time out 만을 검사함으로, 각각의 개별적인 입력에 대해서는 time out 결과를 알수 없기 때문이다.

4.3 연결 소켓 Time out 처리

클라이언트에서 서버로 연결 할 때의 Time out 처리에 대해서 알아보자. select함수만을 이용해서 처리할 수는 없다.connect 함수에서 봉쇄되기 때문이다. 결국 소켓을 비 봉쇄로 만든다음 select함수로 기다려야 한다.

다음과 같은 방식으로 connect 타임아웃을 구현할 것이다. fcntl(2) 함수를 이용해서 듣기소켓을 비 봉쇄 소켓으로 만든다.이제 connect함수를 호출하면 바로 리턴될 것이다. select(2)를 이용해서 timeout을 체크하도록 한다. timeout 체크가 끝난 뒤에는 소켓을 원래의 blocking 상태로 되돌린다.

#include <sys/stat.h>  
#include <arpa/inet.h>  
#include <stdio.h>  
#include <string.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
  
int ConnectWait(int sockfd, struct sockaddr *saddr, int addrsize, int sec)  
{  
    int newSockStat;  
    int orgSockStat;  
    int res, n;  
    fd_set  rset, wset;  
    struct timeval tval;  
  
    int error = 0;  
    int esize;  
  
    if ( (newSockStat = fcntl(sockfd, F_GETFL, NULL)) < 0 )   
    {  
        perror("F_GETFL error");  
        return -1;  
    }  
  
    orgSockStat = newSockStat;  
    newSockStat |= O_NONBLOCK;  
  
    // Non blocking 상태로 만든다.   
    if(fcntl(sockfd, F_SETFL, newSockStat) < 0)  
    {  
        perror("F_SETLF error");  
        return -1;  
    }  
  
    // 연결을 기다린다.  
    // Non blocking 상태이므로 바로 리턴한다.  
    if((res = connect(sockfd, saddr, addrsize)) < 0)  
    {  
        if (errno != EINPROGRESS)  
            return -1;  
    }  
  
    printf("RES : %d\n", res);  
    // 즉시 연결이 성공했을 경우 소켓을 원래 상태로 되돌리고 리턴한다.   
    if (res == 0)  
    {  
        printf("Connect Success\n");  
        fcntl(sockfd, F_SETFL, orgSockStat);  
        return 1;  
    }  
  
    FD_ZERO(&rset);  
    FD_SET(sockfd, &rset);  
    wset = rset;  
  
    tval.tv_sec        = sec;      
    tval.tv_usec    = 0;  
  
    if ( (n = select(sockfd+1, &rset, &wset, NULL, NULL)) == 0)  
    {  
        // timeout  
        errno = ETIMEDOUT;      
        return -1;  
    }  
  
    // 읽거나 쓴 데이터가 있는지 검사한다.   
    if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset) )  
    {  
        printf("Read data\n");  
        esize = sizeof(int);  
        if ((n = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&esize)) < 0)  
            return -1;  
    }  
    else  
    {  
        perror("Socket Not Set");  
        return -1;  
    }  
  
  
    fcntl(sockfd, F_SETFL, orgSockStat);  
    if(error)  
    {  
        errno = error;  
        perror("Socket");  
        return -1;  
    }  
  
    return 1;  
}  
  
int main(int argc, char **argv)  
{  
    struct sockaddr_in serveraddr;  
    int sockfd;  
    int len;  
  
    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )  
    {  
        perror("error");  
        return 1;  
    }  
    serveraddr.sin_family = AF_INET;  
    serveraddr.sin_family = AF_INET;  
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
    serveraddr.sin_port = htons(atoi(argv[1]));  
  
    len = sizeof(serveraddr);  
  
    if (ConnectWait(sockfd, (struct sockaddr *)&serveraddr, len, 5) < 0)  
    {  
        perror("error");  
    }  
    else  
    {  
        printf("Connect Success\n");  
    }  
    while(1)  
    {  
        sleep(1);  
    }  
    close(sockfd);  
}  
 

'Network > Network' 카테고리의 다른 글

Socket 함수 정리  (0) 2011.12.22
block & non-block  (0) 2011.12.22
IP Subnet  (0) 2011.12.02
멀티캐스트 원리  (0) 2011.12.02
gethostbyname() 도메인 이름으로 hostent 정보를 구함  (0) 2011.12.01
: