4월22일 network(select문)/atmega(IOCP모델링 직렬통신)

2010. 4. 22. 10:572010년/4월


-network-

select문

서버에서 클라이언트가 접속시에 소켓을 생성하는데  클라이언트  접속이 많아지면 그만큼  서버에  소켓이 많이 생성된다 그래서 서버에서 이소켓을  관리하기위해 여러모델이 있는데 오늘은 select문에  대해서 배워본다.
select문은 기본적으로 client와 server의 연결상태에서 소켓이  생성된후  client문에서  server로 부터 정보등의 요청이 있을시 server가  대기중에 select를 통해서 무슨 일인지  정하는  경우이다.
Select 모델의 특징
1. 멀티스레드를 사용하지 않고도 여러 개의 소켓을 처리 할 수 있다.
2. 모든 소켓은 넌블로킹 소켓임에도 불구하고 CPU 사용률이 그다지 높지 않다.
3. 각 소켓에 필요한 정보를 관리는 애플리케이션이 구현해야 한다.
select() 함수는 조건을 만족하는 소켓의 개수를 리턴하지만, 구체적으로 어떤 소켓인지 가르쳐주지는 않으므로, 관리하고 있는 모든 소켓에 대해 소켓 셋에 들어있는지 여부를 확인해야 한다. 
int select(
   int nfds,
 fd_set* readfds,
 fd_set* writefds,
 fd_set* exceptfds,
 const struct timeval* timeout
);    성공 : 조건을 만족하는 소켓의 개수 또는 0(타임아웃),실패 : SOCKET_ERROR
nfds : 유닉스와 호환성을 위해 존재하며 윈도우에서는 사용하지 않는다
readfds, writefds, exceptfds : 읽기, 쓰기, 예외 셋을 나타냄
timeout : 초(seconds) 와 마이크로초(microseconds) 단위로 타임아웃을 나타낸다
struct timeval {
    long tv_sec;    // seconds
    long tv_usec;  // microseconds
};
NULL 이면 하나라도 만족할때까지 대기(무한대기..).  0, 0 이면 대기 X,  양수 값이면 그시간까지 대기한다.

--------------------------------------------------------------------------------------------
// 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; // 구조체를 통해서 client접속시 동적할당을 관리하기위해서 생성  

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;
  //윈속 초기화
  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); //SOMAXCONN은 listen()사용시 backlog할  윈도우에서의  최대치를나타냄 
  if(retval==SOCKET_ERROR)
    printf("listen error\n");
  //backlog 인자는 아직 미결인 연결(listen)들에 대한 큐의 늘어날 수 있는 최대 길이를 정의한다.
  //큐에 도착한 연결 요청들이 꽉 찬다면 클라이언트는 ECONNREFUSED 를 가리키는 에러를 받거나, 
  //만일 하위 프로토콜이 재전송을 지원한다면, 요청은 재시도가 성공되도록 하기 위해 무시된다.  
  
  SOCKET client_sock;
  SOCKADDR_IN clientaddr;
  int addrlen;
  // HANDLE hThread;
  // DWORD THreadID;
  
  //accept()
  addrlen = sizeof(clientaddr);
  //클라이언트에서 접근을 하면 연결을 허락해준다.
  //(소켓 서버, 클라이언트의 주소(sockaddr 타입), 클라이언트 주소의사이즈)
  
  
  //server 쪽의 데이타 송신 Thread 생서
  
  
  
  //-----------------------select 모델 ------------------
  
  FD_SET rset;
  FD_SET wset;
  
/*
  typedef  struct fd_set
  {
  u_int fd_count;
  SOCKET fd_array[FD_SETSIZE]
  }FD_SET;
  fd_count:설정하는 소켓번호
  fd_array:설정된 소켓 배열 
  */

  printf("[TCP 서버]첫번째 클라이언트 접속 대기\n");
  
  do
  { 
    FD_ZERO(&rset); //소켓 셋 초기화// 변수 초기화하는 함수(FD_ZERO);
    FD_ZERO(&wset);
    FD_SET(listen_sock,&rset); //listen_sock에  read 할일 생겼다고 세팅(writet설정도 가능) 
    
    //소켓 셋 검사 : 클라이언트 수용 --------- select 1
    for(int i=0; i<nTotalsocket; i++)
    {
      if(sock_area[i]->recvbyte >sock_area[i]->sendbyte) //받은데이터가 보낸데이터보다  큰가?
        FD_SET(sock_area[i]->sock,&wset);  //그렇다면 sock_area[i]소켓에 write할이 생겼다고 보냄 
      else
        FD_SET(sock_area[i]->sock,&rset);  //그게 아니라면 sock_area[i]소켓에 read로  보냄 
    }
    
    //select
    
    retval=select(0,&rset,&wset,NULL,NULL); //send recv 가 발생되는지 보다가 &rest와 &west에 알려줌 
    if(retval==SOCKET_ERROR)
      printf("select 에러\n");
    
    
    //소켓 셋 검사 
    if(FD_ISSET(listen_sock,&rset))//FD_SET통해서 어떤  읽어오는  소켓이 send나 recv  일어나는지  
      //확인하는 매크로같은  작업 (listen_sock에read할일이생겼나?)
    {
      addrlen = sizeof(clientaddr);
      //  printf("소켓 정보를 추가 할 수 없습니다.\n");
      client_sock = accept(listen_sock,(SOCKADDR *)&clientaddr,&addrlen); 
      //지금까지는 연결할일 있는지 없는지 계속 확인했지만 지금은 if(FD_ISSET)인해서 연결할일이  생기면
      //들어와서 accept를 하기때문에 관리가 쉽다 .
      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)   //최대 접속자 FD_SETSIZE(64)이되면 종료 
        {
          
          return 0;
        }
        
        STsock *ptr = new STsock; //C++에서 사용하는 동적할당 new와 malloc와 같다. new는 매크로이다 
        if(ptr == NULL)        //실패 에러메세지  출력후 종료 
        {
          printf("동적 할당 실패 [메모리가 부족한것 같습니다.\n");
          return 0;
        }
        
        ptr->sock = client_sock;  //전역변수설정 client_sock 클라이언트 정보 입력 
        ptr->recvbyte =0;         
        ptr->sendbyte =0;
        sock_area[nTotalsocket] =ptr;
        nTotalsocket++;   //접속자수 증가 
      }
    }
    
    
    
    
    //소켓 셋 검사 : 데이터 통신 --------- select 2
    for(int i1=0; i1<nTotalsocket; i1++)
    {
      STsock *ptr = sock_area[i1];
      
      if(FD_ISSET(ptr->sock,&rset))//데이타가 들어왔는지 검사
      {
        ptr->recvbyte = recv(ptr->sock,ptr->buf,BUFSIZE,0); // 해당소켓으로부터 recv받음 
        retval=ptr->recvbyte;    //buf만큼 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;    //C++에서 동적할당  반환 
          
          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 i2=0; i2<nTotalsocket; i2++)
    {
      STsock *ptr = sock_area[i2];
      if(FD_ISSET(ptr->sock,&wset)) //if(sock_area[i]->recvbyte >sock_area[i]->sendbyte) 
      {                //맞을경우    FD_SET(sock_area[i]->sock,&wset);하는데 
                      //FD_ISSET통해서 있는지 알아보고  recv값그대로  send함 
        //데이터 보내기
        retval = send(ptr->sock,ptr->buf,ptr->recvbyte, 0);//받은만큼 받은크기를 send()함 
        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;
}
--------------------------------------------------------------------------------------------
select는 FD_ISSET통해서 발생될경우  send  recv accept를 다 찾아서 했다. 그래서 나오는것이

MSAselect모델링가 나온다 여기서는 send발생하면 send가  나오고 recv가 나오면 recv로 찾아가고 accept가나오면 accept에서 찾아짐
//문제점이 send안에서 어떤send인지 못찾음

MSAEventselect모델링는 할일이 생기면 send와 recv안의 숫자로 찾아짐
//문제점이  갯수가  천개로 증가할경우 send안에 숫자로 다지정해서 관리해야한다.
//하지만 여기서 전송이 증가한후 send할경우 동시에 10개가 오고  동시에 20개오면
//계속  밀려서 과부화가 일어난다.

MSAOVERLPPED 모델링 윈도창이 겹쳐지게 하는 원리와 같다.  완료루틴생성으로
//동시에 많이  와도  따로 창으로 만들어   완료시킨다. 
//

IOCP모델링
-ATMEGA-
#include<avr/io.h>
#include<avr/signal.h>
#include<avr/interrupt.h>
#include<stdlib.h>


#define CPU_CLOCK     16000000  //CPU clock=16000000Hz
#define TICKS_PER_SEC  1000    //Ticks per sec =1000
#define PRESCALER    128      //클럭의  배수
#define BAUD_RATE 19200    //통신시 이용할 속도
#define BAUD_RATE_L (CPU_CLOCK/(16l*BAUD_RATE))-1 
#define BAUD_RATE_H ((CPU_CLOCK/(16l*BAUD_RATE))-1)>>8

unsigned char USART_Receive(void);   //recv
void Protocall(void);        //직렬포트설정 
unsigned char PINCN(void);
volatile unsigned int  g_elapsed_time;//시간변수 
unsigned char Pnum;              //PINC 번호 
void uart_send_byte(unsigned char byte); // send 부분 
void intiLED(void);            //LED초기화
void setTCCR0(void);          //TCCR0설정
void initTCNT0(void);          //TCNT0초기화
void setTIMSK(void);          //TIMSK 설정
void sleep(unsigned int elapsed_time);  //1초  대기
SIGNAL(SIG_OVERFLOW0);          //timer0의 오버플로우  함수
unsigned char count=0;
unsigned char USART_Receive(void)
{
  while(!(UCSR1A&(1<<RXC)));
  return UDR1;
}
unsigned char PINCN(void)
{
  if((255-PINC)==128)
  {
    Pnum=1;
  }
  else if((255-PINC)==64)
  {
    Pnum=2;
  }
  else if((255-PINC)==32)
  {
    Pnum=3;
  }
  else if((255-PINC)==16)
  {
    Pnum=4;
  }
  else if((255-PINC)==8)
  {
    Pnum=5;
  }
  else if((255-PINC)==4)
  {
    Pnum=6;
  }
  else if((255-PINC)==2)
  {
    Pnum=7;
  }
  else if((255-PINC)==1)
  {
    Pnum=8;
  }
  else
  {
    Pnum=0;
  }
  return Pnum;

}
void Protocall(void)
{

  UBRR1L = (unsigned char)BAUD_RATE_L; //baud rate 설정
  UBRR1H =(unsigned  char)BAUD_RATE_H;
  //parity설정 1stop bit설정,문자사이즈 8bit 설정
  UCSR1C=(0<<UPM1)|(0<<UPM0)|(0<<USBS)|
    (1<<UCSZ1)|(1<<UCSZ0);
  //송신수신 interrupt설정(PORTD27번28번사용), 문자사이즈 8bit설정
  UCSR1B=(1<<TXEN)|(1<<RXEN)|(0<<UCSZ2);

}
void uart_send_byte(unsigned char byte)
{
  while(!(UCSR1A&(1<<UDRE)));
  UDR1=byte ; 
}

void intiLED(void)
{
  DDRF=0xFF;
  PORTF=0xFF;
  DDRC=0x00;

}
void setTCCR0(void)
{
  TCCR0=(TCCR0|(1<<2));
  TCCR0=(TCCR0|(1<<0));
  TCCR0=(TCCR0&(~(1<<1)));
  
}
void initTCNT0(void)
{
  TCNT0 =256-(CPU_CLOCK/TICKS_PER_SEC/PRESCALER);
}          
void setTIMSK(void)  
{
  TIMSK=(TIMSK)|(1<<0);
}        
void sleep(unsigned int elapsed_time)
{
  g_elapsed_time=0;
  while(g_elapsed_time<elapsed_time);
  
  
}
SIGNAL(SIG_OVERFLOW0)
{
  initTCNT0();
  
  g_elapsed_time++;
}

int main()
{  
  unsigned char i;    //게임 20번할 횟수
  // 정답을 맞춘 갯수 
  int random; //입력받을 난수 값
  unsigned char Lnum=0;  //LED번호 
  unsigned char buf[3];
  unsigned char  j;
  unsigned char ch=0;
    //PINC번호  
  Protocall();
  intiLED();
  setTCCR0();
  initTCNT0();
  setTIMSK();
  SREG=0x80;
  
  ch=USART_Receive();
  if(0<ch)
  {
    sleep(1000);
    for(i=0;20>i;++i)
    {
    
      srand(rand());
      random=rand()%8;
      PORTF=~(1<<random);
      Lnum=random+1;
      uart_send_byte(Lnum);
      sleep(800);
      PORTF=0xFF;    
      uart_send_byte(PINCN());
    
      sleep(200);
    }
    
  
  }
  return  0;
}

--------------------------------------------------------------------------------------------
받는쪽
#include<stdio.h>
#include<windows.h>


int main()
{
  char szPort[15];  //포트명을 저장할 변수
  
  wsprintf(szPort,"COM%d",1); //포트 2번으로 통신
  
  HANDLE  m_hComn =NULL;  //HANDLE생성후   초기값 NULL
  m_hComn = CreateFile(szPort,GENERIC_READ|GENERIC_WRITE,0,NULL,
    OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
  
  if(m_hComn==INVALID_HANDLE_VALUE)  //포트접속실패시 에러
  {
    printf("(!) Failed to create a  Comm Device file \n");
    return FALSE;
  }
  
  DCB dcb;        //DCB 구조체생성
  dcb.DCBlength=sizeof(DCB);    //포트의 크기 비트레이 바이트크기 등을
  GetCommState(m_hComn,&dcb);    
  dcb.BaudRate=19200;
  dcb.ByteSize=8;
  dcb.Parity=0;
  dcb.StopBits=0;
  
  SetCommState(m_hComn,&dcb);    
  OVERLAPPED  osWrite,osRead;      //송신용 객체 생성
  osWrite.Offset=0;
  osWrite.OffsetHigh=0;
  osWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
  osRead.Offset=0;
  osRead.OffsetHigh=0;
  osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
  //unsigned char buf[2]; 
  unsigned char buf1;
  unsigned char buf2;
  unsigned char i;
  unsigned char j;
  printf("게임을 시작하시겠습니까?\n");
  scanf("%d",&i);
  while(1)
  {
  
    while(1)
    {
      WriteFile(m_hComn,&i,1,NULL,&osWrite);
      ReadFile(m_hComn,&buf1,1,NULL,&osRead);
      ReadFile(m_hComn,&buf2,1,NULL,&osRead);
      if(buf1!=buf2)
      {
        printf("LED[%d]\t",buf2);
        printf("SWITCH[%d]  실패\n",buf1);
        
      }
      else
      {
        printf("LED[%d]\t",buf2);
        printf("SWITCH[%d]\n",buf1);
      }
      Sleep(1000);   
      
    }
    printf("다시  하시겠습니까? y/n\n");
    scanf("%d",&j);
    if(j=='n')
    {
      break;
    }
  }
  CloseHandle(m_hComn);
  
  return 0;
}