IT_Programming/Network Programming

[I/O Multiplexing] select를 이용한 멀티플렉싱 구현 (Ver. Win / Linux)

JJun ™ 2009. 7. 7. 10:57

==================================================================================================

 

우선 기본적으로 select()함수를 이용한 멀티플렉싱 서버의 예제이다.

(앞으로 공부할 멀티플렉싱 관련 함수는 poll()과 리눅스의 epoll(), kqueue 등이 있다..)

일단 가장 기본적인 select()함수를 사용해봤다....

참고 : TCP/IP 소켓 프로그래밍

 

==================================================================================================

 

select()함수를 사용하게 되면, 한곳에 모아놓은 여러 개의 파일 디스크립터를 동시에 관찰할 수 있다.

수신할 데이터를 지니고 있는 파일 디스크립터가 어떤 것들인지, 데이터를 전송할 경우 로킹되지 않고,

바로 전달 가능한 파일 디스크립터는 어떤 것들인지, 그리고 예외가 발생한 파일 디스크립터는 어떤 것들인지

정도가 관찰 내용이 된다.

 

※ 전달되는 이벤트 정보가 한정적이기 때문에 여러 정보를 알아내기 위해서는 하드코딩이 필요하다.

    이럴 경우 select()보다는 poll()을 사용한다.

 

--------------------------------------------------------------------------------------------------

 

int select (int nfds, fd_set  FAR  *readfds, fd_set  FAR  *writefds, fd_set  FAR  *exceptfds,

                 const struct timeval  FAR  *timeout);

 

1. 필요한 헤더파일 : <winsock2.h>

 

2. 인자 : nfds - 사실 아무 의미 없는 인자 (호환성 때문일지도...) → 윈도우즈 기반은 소켓 핸들 수를

                                                                                              적을 필요가 없다.          

              readfds - "입력 스트림에 변화가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다.     

              writefds - "출력 스트림에 변화가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다.    

              exceptfds - "예외가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다.

              timeout - 함수 호출 후 무한 대기 상태에 빠지지 않도록 타임-아웃을 설정한다.        

 

3. 반환값 : 성공 = 0 이상 (0은 타임-아웃시 리턴될 경우, 0 이상 : 변화된 파일 디스크립터의 수) 

                 실패 = -1

 

--------------------------------------------------------------------------------------------------

 

[ 파일 디스크립터 설정 ]

FD_ZERO(*set) : set 포인터가 가리키는 배열을 NULL로 초기화.

FD_SET(s, *set) : 핸들 s를 set 포인터가 가리키는 배열의 멤버에 포함시킨다.

FD_CLD(s, *set) : set 포인터가 가리키는 배열에서 핸들 s를 삭제

FD_ISSET(s, *set) : 핸들 s가 set 포인터가 가리키는 배열의 멤버라면 0이 아닌 값을,

                                 멤버가 아니라면 0을 리턴한다. 

 

--------------------------------------------------------------------------------------------------

  

[소스코드 : Windows]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winsock2.h>

 

#pragma comment(lib, "WS2_32.lib")

 

#define BUF_SIZE 100

 

void dll_load(WSADATA *wsaData); // winsock 2.2 dll load
void conn_init(SOCKET *serv_sock, SOCKADDR_IN *serv_addr, int port); // 네트워크 주소 초기화
void init_fdset(fd_set *reads, SOCKET serv_sock); // fd_set 초기화
void error_handling(char *message); // 에러 메시지 처리 함수

 

int main(int argc, char *argv[])
{
     WSADATA wsaData;
     SOCKET serv_sock, clnt_sock;
     SOCKADDR_IN serv_addr, clnt_addr;
     TIMEVAL timeout;
     fd_set reads, temps;
     int arrIndex, clnt_len, str_len;
     char message[BUF_SIZE];

 

     if(argc != 2)
     {
          printf("Usage : %s <PORT>\n", argv[0]);
          exit(0);
     }

    

     dll_load(&wsaData);
     conn_init(&serv_sock, &serv_addr, atoi(argv[1]));

    

     if(bind(serv_sock, (SOCKADDR *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
          error_handling("bind() Error");

    

     if(listen(serv_sock, 5) == SOCKET_ERROR)
          error_handling("listen() Error");
 
     init_fdset(&reads, serv_sock);


     while(1)
     {
          temps = reads; // 원본 보존을 위한 복사

                                // → select()함수 호출이 끝나면 변화가 생긴 파일 디스크립터 위치를 제외한 나머지

                                //     위치의 비트들은 0으로 초기화 되기 때문이다.
          timeout.tv_sec = 5;
          timeout.tv_usec = 0;

 

          if(select(0, &temps, 0, 0, &timeout) == SOCKET_ERROR)
               error_handling("select() Error!");

 

          for(arrIndex=0; arrIndex<reads.fd_count; ++arrIndex) // 전체 검색
          {
               if(FD_ISSET(reads.fd_array[arrIndex], &temps)) // 상태 변화를 찾는다. 

               {                                                                   // (비트[1]가 같은 것이 변화된 것)
                    if(reads.fd_array[arrIndex] == serv_sock) // 연결 요청인 경우
                    {
                         clnt_len = sizeof(clnt_addr);
                         clnt_sock = accept(serv_sock, (SOCKADDR *)&clnt_addr, &clnt_len);
                         FD_SET(clnt_sock, &reads); // 소켓을 추가
                         printf("클라이언트 연결 : 소켓 핸들 %d\n", clnt_sock);
                    }
                    else
                    {
                         str_len = recv(reads.fd_array[arrIndex], message, BUF_SIZE-1, 0);
                         if(str_len == 0) // 연결 종료 요청인 경우
                         {
                              closesocket(temps.fd_array[arrIndex]);
                              printf("클라이언트 종료 : 소켓 핸들 %d\n", reads.fd_array[arrIndex]);
                              FD_CLR(reads.fd_array[arrIndex], &reads);
                         }
                         else // 나머지 메시지 전송
                         {
                              send(reads.fd_array[arrIndex], message, str_len, 0);
                         }
                    }
               }
          }    
     }

     WSACleanup();
    

     return 0;
}

 

void dll_load(WSADATA *wsaData)
{
     if(WSAStartup(MAKEWORD(2, 2), wsaData) != 0)
          error_handling("WSAStartup() Error!");
}

 

void conn_init(SOCKET *serv_sock, SOCKADDR_IN *serv_addr, int port)
{
     *serv_sock = socket(PF_INET, SOCK_STREAM, 0);
     if(serv_sock == NULL)
          error_handling("socket() Error!");

 

     serv_addr->sin_family = AF_INET;
     serv_addr->sin_addr.s_addr = htonl(INADDR_ANY);
     serv_addr->sin_port = htons(port);
}

 

void init_fdset(fd_set *reads, SOCKET serv_sock)
{
     FD_ZERO(reads);
     FD_SET(serv_sock, reads);
}

 

void error_handling(char *message)
{
     fputs(message, stderr);
     fputc('\n', stderr);
     exit(1);
}

 

==================================================================================================

 

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

1. 필요한 헤더파일 : <sys/time.h> : 블로킹 지속 시간 관련 세팅시에 필요 (timeout 세팅)

                               <sys/types.h>, <unistd.h>

 

2. 인자 : n - 검사 대상이 되는 파일 디스크립터(비트 배열)의 수              

              readfds - "입력 스트림에 변화가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다.     

              writefds - "출력 스트림에 변화가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다.    

              exceptfds - "예외가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다.

              timeout - 함수 호출 후 무한 대기 상태에 빠지지 않도록 타임-아웃을 설정한다.        

 

3. 반환값 : 성공 = 0 이상 (0은 타임-아웃시 리턴될 경우, 0 이상 : 변화된 파일 디스크립터의 수)

                 실패 = -1

 

--------------------------------------------------------------------------------------------------

 

[ 파일 디스크립터 설정 ]

 

FD_ZERO(fd_set *fdset) : fdset 포인터가 가리키는 변수들의 모든 비트들을 0으로 초기화.

 

FD_SET(int fd, fd_set *fdset) : fdset 포인터가 가리키는 변수에 fd로 전달되는 파일 디스크립터

                                                  정보를 설정

 

FD_CLD(int fd, fd_set *fdset) : fdset 포인터가 가리키는 변수에서 fd로 전달되는 파일 디스크립터

                                                  정보를 삭제

 

FD_ISSET(int fd, fd_set *fdset) : fdset 포인터가 가리키는 변수가 fd로 전달되는 파일 디스크립터

                                                     정보를 지니고 있는지 확인.

 

--------------------------------------------------------------------------------------------------

 

[소스코드 : Linux]

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>

 

#define BUF_SIZE  100

 

void conn_init(int *serv_sock, struct sockaddr_in *serv_addr, int port);
void init_fdset(int *fd_max, fd_set *reads, int serv_sock);
void error_handling(char *message);

 

int main(int argc, char *argv[])
{
     int serv_sock, clnt_sock, fd_max, fd, str_len, clnt_len;
     struct sockaddr_in serv_addr, clnt_addr;
     struct timeval timeout;
     fd_set reads, temps;
     char message[BUF_SIZE];

    

     if(argc != 2)
     {
          printf("Usage : %s <PORT>\n", argv[0]);
          exit(0);
     }

 

     conn_init(&serv_sock, &serv_addr, atoi(argv[1]));
 
     if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
          error_handling("bind() Error!");

 

     if(listen(serv_sock, 5) == -1)
          error_handling("listen() Error!");

 

     init_fdset(&fd_max, &reads, serv_sock);

    

     while(1)
     {
          temps = reads; // 원본 보존을 위한 복사

                                // → select()함수 호출이 끝나면 변화가 생긴 파일 디스크립터 위치를 제외한 나머지

                                //     위치의 비트들은 0으로 초기화 되기 때문이다.

          timeout.tv_sec = 5;
          timeout.tv_usec = 0;

 

          if(select(fd_max + 1, &temps, 0, 0, &timeout) ==  -1)
               error_handling("select() Error!");

 

          for(fd = 0; fd < fd_max + 1; ++fd)
          {
               if(FD_ISSET(fd, &temps)) // 상태 변화를 찾는다. (비트[1]가 같은 것이 변화된 것)
               {
                    if(fd == serv_sock) // 연결 요청인 경우
                    
                         clnt_len = sizeof(clnt_addr);
                         clnt_sock = accept(serv_sock, (struct sockaddr *)& clnt_addr, &clnt_len);
                         FD_SET(clnt_sock, &reads);

 

                         if(fd_max < clnt_sock)
                              fd_max = clnt_sock;

 

                         printf("클라이언트 연결: 파일 디스크립터 %d\n", clnt_sock);
                   }
                   else
                   {
                         str_len = read(fd, message, BUF_SIZE);
                         if(str_len == 0) // 연결 종료 요청인 경우
                         {
                              FD_CLR(fd, &reads);
                              close(fd);
                              printf("클라이언트 종료: 파일 디스크립터 %d\n", fd);
                         }
                         else // 나머지 메시지 전송
                         {
                              write(fd, message, str_len);
                         }
                    }
               }
          } 
     }
 
     return 0;
}

 

void conn_init(int *serv_sock, struct sockaddr_in *serv_addr, int port)
{
     *serv_sock = socket(PF_INET, SOCK_STREAM, 0);
     if(serv_sock == NULL)
          error_handling("socket() Error!");

 

     serv_addr->sin_family = AF_INET;
     serv_addr->sin_addr.s_addr = htonl(INADDR_ANY);
     serv_addr->sin_port = htons(port);
}

 

void init_fdset(int *fd_max, fd_set *reads, int serv_sock)
{
     FD_ZERO(reads);
     FD_SET(serv_sock, reads);
     *fd_max = serv_sock;
}

 

void error_handling(char *message)
{
     fputs(message, stderr);
     fputc('\n', stderr);
     exit(1);
}

 

==================================================================================================



8 VMWare 네트워크 설정.doc
0.22MB