본문 바로가기
코스웨어/10년 시스템제어

[시스템제어]4월22일 보고서 임창모

by 알 수 없는 사용자 2010. 4. 22.
728x90
반응형

네트워크
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

하나의 스레드로 여러 클라이언트들을 처리하는 것이 => 반복 서버입니다.스레드를 여러 개 가지고 동시에 클라이언트들의 요구를 처리하는 것이 => 병행 서버입니다.
socket() 함수로 생성한 소켓 :  블로킹 소켓 + 동기 입출력
ioctlsocket() 함수 사용 : 넌블로킹 소켓 + 동기 입출력
WSASocket() 함수로 생성한 소켓 : 넌블로킹 소켓 + 비동기 입출력 

Select 모델: 넌블로킹 소켓 + 동기 입출력 + 비동기 통지
WSAASyncSelect 모델
: 넌블로킹 소켓 + 동기 입출력 + 동기 통지WSAEventSelect 모델: 넌블로킹 소켓 + 동기 입출력 + 비동기 통지Overlapped(1) 모델: 넌블로킹 소켓 + 비동기 입출력 + 비동기 통지Overlapped(2) 모델
: 넌블로킹 소켓 + 비동기 입출력 + 비동기 통지

Overlapped(1)과 (2)의 차이점
(1) : 입출력 완료를 이벤트(Event)를 통해 알아냅니다.
(2) : 입출력 완료를 완료 루틴(콜백 함수)을 사용해서 알아냅니다.

IOCP 모델
: 넌블로킹 소켓 + 비동기 입출력 + 비동기 통지
: 효율적인 스레드 사용으로 가장 좋은 성능을 내는 모델이라고 합니다.

. Select 모델이란- 윈도우와 x눅스유닉스 환경 모두 사용가능
-
소켓 모드에 관계없이 여러개의 소켓을 하나의 스레드로 처리 가능
- select 함수는 하나 또는 그 이상의 소켓상태(I/O의 발생유무 상태)를
  결정하는데 사용 

Select 모델 동작 원리

소켓 함수를 호출 해야 할 시점을 알려줌으로써 함수 호출 시 항상 성공하도록 하는 것이다. 
블로킹 소켓 : 소켓 함수 호출 시 조건이 만족되지 않아 블로킹되는 상황을 막을 수 있다.
넌블로킹 소켓 : 소켓 함수 호출 시 조건이 만족되지 않아 다시 호출해야 하는 상황을 막을 수있다.
1. 소켓 셋을 준비해서 select함수에 전달하면 select함수는 소켓 셋에 포함된 소켓에 대해 해당 작업을 할 수 있을 때 까지 대기한다.
2. 적어도 하나의 소켓이 준비가 되면 리턴한다.
3. 소켓 셋에는 준비가 된 소켓만 남고 나머지는 모두 제거된다.
세 개의 소켓에 대해 소켓 함수를 안전하게 호출할 수 있으며, (거의) 항상 함수 호출이
성공하게 된다.
select 모델은 소켓을 넌 블로킹으로 전환하여야 한다.

Select 모델의 특징

1. 멀티스레드를 사용하지 않고도 여러 개의 소켓을 처리 할 수 있다.
2. 모든 소켓은 넌블로킹 소켓임에도 불구하고 CPU 사용률이 그다지 높지 않다.
3. 각 소켓에 필요한 정보를 관리는 애플리케이션이 구현해야 한다.
- select() 함수는 조건을 만족하는 소켓의 개수를 리턴하지만, 구체적으로 어떤 소켓인지 가르쳐주지는 않으므로, 관리하고 있는 모든 소켓에 대해 소켓 셋에 들어있는지 여부를 확인해야 한다. 

Select() 함수를 이용한 소켓 입출력 절차

1. 소켓 셋을 비운다( 초기화 )
    ex) FD_ZERO( &rset );
2. 소켓 셋에 소켓을 넣는다. 최대 개수는 FD_SETSIZE(64)
    ex) FD_SET( listen_sock, &rset );
3. select() 함수를 호출한다. 블로킹 함수로 동작한다.
    ex) select( 0, &rset, &wset, NULL, NULL );
4. select() 함수가 리턴 한 후 소켓 셋에 남아있는 모든 소켓에 대해 적절한 소켓 함수를 호출하여 처리한다.
    ex) FD_ISSET( listen_sock, &rset );
5. 1~4를 반복한다.



- select() 함수 원형

 int select( int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout);

- 반환값
- FD_SET 구조체에 포함되어 지정된 I/O에 준비하고 있는 소켓 핸들의 총 개수를 반환지정된 타임아웃 시간을 초과하면, 0을 반환에러가 발생한 경우 SOCKET_ERROR를 반환, WSAGetLastError 함수로 에러코드
- FD_SET 읽기, 쓰기, 예외상황의 I/O변화 발생을 감지할 대상이 되는 소켓들을 지정하는 배열형 구조체
readfds : 입력을 받을 수 있는 상태(수신할 데이터가 있는지), 연결이 끊어졌는지, writefds : 출력 할 수 있는 상태, 연결 성공 유무
exceptfds : oob 감지, 예외상황
- FD_SET 매크로
FD_SETSIZE : 소켓 기술자의 최대개수(기본 : 64)
FD_CLR : 소켓을 제거
FD_ISSET : 소켓이 들어 있는지 확인
FD_SET : 소켓을 셋팅(초기화)FD_ZERO : 초기화

select모델 서버 구현화 예시

// Winsoc03_Select.cpp : Defines the entry point for the console application.
//


#include "stdafx.h"

#include <winsock2.h> //windows.h보다 먼저 선언해야함
#include <windows.h>
#include <process.h>
#include <stdlib.h>
#include <iostream>



#define BUFSIZE 512

unsigned int WINAPI ServerProc(LPVOID lpParam);


int nTotalsocket;


typedef struct STsock{
  SOCKET sock;
  char buf[BUFSIZE+1];
  int recvbyte;
  int sendbyte;
}STsock;

STsock* sock_area[FD_SETSIZE];

int main(int argc, CHAR* argv[])

  DWORD dwID;
  
  HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ServerProc, NULL, 0, (unsigned *)&dwID); //스레드 생성
  
  WaitForSingleObject(h,INFINITE);  //스레드 종료를 기다림 
  
  printf("Main Thread 종료\n");
  
  CloseHandle(h);           //핸들을 반환
  
  return 0;
}

// 링커->입력->추가종속성에 ws2_32.lib를 추가 해주어야 한다.

unsigned int WINAPI ServerProc(LPVOID lpParam)
{
  WSADATA wsa;
  int retval;
  int i;
  //윈속 초기화
  if(WSAStartup(MAKEWORD(2,2),&wsa) !=0)
    return -1;
  
  SOCKET listen_sock = socket(AF_INET,SOCK_STREAM,0);
  
  if(listen_sock == INVALID_SOCKET) 
    printf("소켓 초기화 실패\n");
  
  //---------------넌 블록킹 소켓으로 전환--------
  u_long on =1;
  retval = ioctlsocket(listen_sock,FIONBIO,&on);
  if(retval==SOCKET_ERROR)
  {
    printf("소켓 속성 변경 실패\n");
    return 0;
  }
  //----------------------------------------------
  
  SOCKADDR_IN serveraddr;
  
  ZeroMemory(&serveraddr,sizeof(serveraddr));
  serveraddr.sin_family= AF_INET;
  serveraddr.sin_port=htons(9000);
  serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
  
  //bind 서버의 지역 IP주소와 지역 포트번호를 결정
  //(클라이언트의 접속을 수용할 소켓,이변수늬 주소와 지역포트 번호로 초기화 시킴,소켓주소 구조체변수의 길이)
  retval=bind(listen_sock,(SOCKADDR*)&serveraddr,sizeof(serveraddr));
  if(retval==SOCKET_ERROR)
    printf("bind error\n");
  
  //listen
  //client의 접속을 받아들일수 있는 상태로 포트 상태를 변경한다.
  retval=listen(listen_sock,SOMAXCONN);
  if(retval==SOCKET_ERROR)
    printf("listen error\n");
  
  
  SOCKET client_sock;
  SOCKADDR_IN clientaddr;
  int addrlen;
  HANDLE hThread;
  DWORD THreadID;
  
  //accept()
  addrlen = sizeof(clientaddr);
  //클라이언트에서 접근을 하면 연결을 허락해준다.
  //(소켓 서버, 클라이언트의 주소(sockaddr 타입), 클라이언트 주소의사이즈)
  
  
  //server 쪽의 데이타 송신 Thread 생서
  
  
  
  //-----------------------select 모델 ------------------
  
  FD_SET rset;//이변수를 통해서 rev send 확인 변수 
  FD_SET wset;
  printf("[TCP 서버]첫번째 클라이언트 접속 대기\n");
  
  
do
  { 
    FD_ZERO(&rset); //소켓 셋 초기화
    FD_ZERO(&wset);
    FD_SET(listen_sock,&rset);//listen_sock에 rec할일이 생겻다.
    
    //소켓 셋 검사 : 클라이언트 수용 --------- select 1
    for(i=0; i<nTotalsocket; i++)
    {
      if(sock_area[i]->recvbyte >sock_area[i]->sendbyte)
        FD_SET(sock_area[i]->sock,&wset);
      
else
        FD_SET(sock_area[i]->sock,&rset);
    }
    
    //select
      
    retval=select(0,&rset,&wset,NULL,NULL);
    if(retval==SOCKET_ERROR)
      printf("select 에러\n");
    
    
    //소켓 셋 검사 
    if(FD_ISSET(listen_sock,&rset)){
      addrlen = sizeof(clientaddr);
      //printf("소켓 정보를 추가 할 수 없습니다.\n");
      client_sock = accept(listen_sock,(SOCKADDR *)&clientaddr,&addrlen);
      if(client_sock ==INVALID_SOCKET)
      { //WSAEWOULDBLOCK이란 읽을려고 하는데 첫번째 데이터가 없을때 발생이 된다.
        //즉 NonBlocking 모드에서는 자주 발생할수 밖에 없는 에러이다.
        if(WSAGetLastError()!=WSAEWOULDBLOCK)
          printf("accept error\n");
      }
      
else
      {
        printf("[TCP 서버]클라이언트 접속: IP 주소=%s, 포트번호 %d\n",\
          inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
        //소켓 정보 추가
        if(nTotalsocket>=FD_SETSIZE-1)
        {
          
          return 0;
        }
        
        STsock *ptr = new STsock;
        if(ptr == NULL)
        {
          printf("동적 할당 실패 [메모리가 부족한것 같습니다.\n");
          return 0;
        }
        
        ptr->sock = client_sock;
        ptr->recvbyte =0;
        ptr->sendbyte =0;
        sock_area[nTotalsocket] =ptr;
        nTotalsocket++;
      }
    }
    
    
    
    
    //소켓 셋 검사 : 데이터 통신 --------- select 2
    for(i=0; i<nTotalsocket; i++)
    {
      STsock *ptr = sock_area[i];
      
      if(FD_ISSET(ptr->sock,&rset))//데이타가 들어왔는지 검사
      {
        ptr->recvbyte = recv(ptr->sock,ptr->buf,BUFSIZE,0);
        retval=ptr->recvbyte;
        
        if(retval == SOCKET_ERROR){
          if(WSAGetLastError() != WSAEWOULDBLOCK)
          {
            printf("데이타 수신 에러\n");
            return 0;
          }
        }
        else if(retval==0)//클라이언트에서 접속을 종료할경우
        {
          STsock *ptr = sock_area[i];
          SOCKADDR_IN clientaddr;
          int addrlen = sizeof(clientaddr);
          getpeername(ptr->sock,(SOCKADDR *)&clientaddr,&addrlen);
          
          printf("[TCP 서버] 클라이언트 종료: IP 주소 = %s, 포트번호= %d\n",\
            inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
          
          closesocket(ptr->sock);
          delete ptr;
          
          for(int num=i; num<nTotalsocket; i++)//종료한 자리를 메꾸어서 배치를 시켜줌
          {
            sock_area[num]=sock_area[num+1];
          }
          nTotalsocket--;
        }
        else //받은 데이타 출력
        {
          //데이터 출력
          ptr->buf[ptr->recvbyte]='\0';
          //클라이언트의 정보를 가져옴
          getpeername(ptr->sock,(SOCKADDR*)&clientaddr,&addrlen);
          printf("[TCP/%s:%d] %s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),ptr->buf);
        } 
      }
    }
    
    for(int i=0; i<nTotalsocket; i++)
    {
      STsock *ptr = sock_area[i];
      if(FD_ISSET(ptr->sock,&wset))
      { 
        //데이터 보내기
        retval = send(ptr->sock,ptr->buf,ptr->recvbyte, 0);
        ptr->sendbyte=retval;
        if(retval==SOCKET_ERROR)
        { 
          if(WSAGetLastError() != WSAEWOULDBLOCK)
          {
            printf("send error!\n");
            break;
          }
        }
        
        if(ptr->sendbyte==ptr->recvbyte)
        {
          ptr->sendbyte=0;
          ptr->recvbyte=0;
        }
      }
    }
    
 }while(1);
 
 
 closesocket(listen_sock);
 WSACleanup();          //윈속 종료
 printf("server thread 종료\n");
 return 0;
}

728x90