TCPIP

2013.06.19_select()활용_Client소켓늘려보기_

성엽이 2013. 6. 20. 00:18

 ---소스파일----

server.c

 

tcp_client.c

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

 기본이론정리

 .반이중 통신
양쪽 방향으로 신호의 전송이 가능하기는 하지만 경우에 따라 반드시 한쪽 방향으로만 전송이 이루어지게 한 방식을 말합니다.

주컴퓨터와 단말기가 반이중 방식으로 통신할 경우, 주컴퓨터가 단말기에 데이터를 보내는 동안은 단말기에서 데이터를 입력할 수 없으며, 반대로 단말기에서 데이터가 입력되고 있는 동안은 주컴퓨터가 단말기로 데이터를 보낼 수 없습니다

.전이중 통신
송신을 하면서 동시에 수신도 할 수 있는 방식을 말한다.

.server의 랑데뷰 소켓
accept를 하게되면 서버에 랑데뷰 소켓을 1차적으로 사용하게 됩니다

랑데뷰 소켓이란 ?
 클라이언트에서 보내져서 큐상에서 대기하고 있는 정보(서버와 통신을 원하는 클라이언트의 정보)를 두번째 인자에 연결한 다음 클라이언트소켓과 연결된 새로운 소켓을 반환합니다
앞으로 그 클라이언트와의 통신은 반환된 소켓 디스크립터를 사용하게됩니다
반환값-- 성공시 communication 소켓 디스크립터, 실패시 -1을 반환
이 communicatoin 소켓을 이용하여 통신을 합니다

이러한 랑데뷰소켓은 4번부터 지정이되고

 I err  랑데뷰   


서버로 접속하는 클라이언트는 5번부터 접속이됩니다

 I err  랑데뷰   클라이언트



.멀티플렉싱을 활용한 서버
:멀티플렉싱이란 값을 하나하나 대입하여 찾는 풀링방식과 특정 값을 호출하기까지 대기하는 인터럽트방식을 결합한 방식을 뜻합니다 간단하게 구조를 그린다면

 while(무한반복)
 검사 및 세팅
 select
 accept
 read & write


의 구조로 되어 있습니다 여기서  select가 인터럽트 방식에 해당되고 read write accept처리에는 풀링방식이 사용됩니다

 

 

 

Sever.c 소스

 

 #include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define  MAXPENDING 5 // 서버에서 받을 허용 인원.
#define  MAXUSER 2  // 소켓으로 받을 사람수.

int main()
{
 int servSock; // 서버소켓
 int clntSock[MAXUSER]; // 클라이언트소켓 2개를 받음.
 unsigned int ui_User; // 접속자 수
 int iMaxSock; // select함수의 첫번째 인자.
 int tempSock; // 임시 소켓.
 int iRet;  // 저장할 변수
 unsigned char uc_buff[500];  // 채팅시에 문자열을 받는 버퍼.
 
 struct sockaddr_in echoServAddr; // 신구조체의 서버IP를 가지는 변수.
 struct sockaddr_in echoClntAddr; // 신구조체의 클라이언트IP를 가지는 변수

 unsigned short echoServPort;  // Port 번호를 가지는 변수. 2byte 크기.
 unsigned int clntLen;    // 클라이언트 변수
 unsigned int iCnt;       // for 문 돌리기위함.
 unsigned int iCnt2;     // for 문 돌리기위함.
 fd_set fsStatus;         // fd_set 구조체, select 함수를 쓰기위해서
  
 echoServPort = 9999; // Port 번호 0~65535 범위를 가짐.
 
 // Socket 생성
 //프로토콜패밀리결정, 소켓의형태(데이타보내는방식,SOCK_STREAM or SOCK_DGRAM)설정,
 //STREAM인지DGRAM인지에 따라서 IPPROTO_TCP , IPPROTO_UDP 로 정해줌.
 //신뢰성 : STREAM (데이터의 송수신값을 확인하며 보냄),ex)일반파일
 //비신뢰성 : DGRAM(데이터을 확인없이 한번에 보냄),ex)MP3..
 servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
 if( 0 > servSock ) // Error 코드
 {
  printf("socket() failed\n");
  return 0;
 }
   
 // 메모리 크기를 특정값으로 초기화 여기서는 0 으로 만들어줌.
 memset(&echoServAddr, 0, sizeof(echoServAddr));
 // #define AF_INET PF_INET 2 로 define 되어있음.  패밀리선택, PF_INET 을 쓴다.
 echoServAddr.sin_family = AF_INET; 
 // Host to Network long 으로 host의 크기를 Long형(4byte)만큼
 // 지정하고 little-Endian -> Big-Endian방식으로 보내줌.
 // 0 을 넣어주면 Host에서 주소를 랜덤으로 줌, 고정적으로 사용하고 싶을때는 직접 주소값을 넣어줌.
 echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
 // Host to Network short 으로 host의 크기를 short형(2byte)만큼
 // 지정하고 little-Endian -> Big-Endian방식으로 보내줌.
 // 0 을 넣어주면 Host에서 주소를 랜덤으로 줌, 고정적으로 사용하고 싶을때는 직접 주소값을 넣어줌.
 echoServAddr.sin_port = htons(echoServPort); // Port는 정해져있으므로 정해줌. 

 // 구조체 안에 설정한것을  소켓에다가 넣어줌. bind()
 iRet = bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));

 // if조건문을 위해서 iRet 변수를 지정.
 if( 0 > iRet )
 {
  printf("bind() failed\n");
  close(servSock); // 서버소켓 닫기.
  return 0;
 }
 
 // listen(서버소켓, 받을 클라이언트 수)
 // if조건문을 위해서 iRet 변수를 지정.
 iRet = listen(servSock,MAXPENDING); // Client 연결할때마다 하나씩 새로운 소켓을 얻는데 사용.
 if( 0 > iRet)
 {
  printf("listen() failed\n");
  close(servSock);
 
  return 0;
 }
 
 // 서버로 들어오는 클라이언트연결에대해서 accept()를 호출하면서 소켓을 만든다.
 clntLen = sizeof(echoClntAddr); // clntLen로 Client 크기를 받고, clntLen의 주소를 인자로 넘김.
 // 대기중에 있다가 서버에 접속허용을 받으면 Client 소켓을 만듬. 강제로 대기(블로킹함수)

 iMaxSock = servSock+1; // Sock의 제일 큰번호를 의미 , 제일큰번호 + 1
 ui_User = 0;   // 접속자 수.
 
 while(1)
 { 
  FD_ZERO(&fsStatus);  // 파일디스크립트 모두 초기화.
  FD_SET(0,&fsStatus); // 키보드(stdin:0번)입력 비트를 상태확인 위해 SET으로 바꿈.
  FD_SET(servSock,&fsStatus); // 서버소켓상태 확인을 위해 SET으로 바꿈.
  
  for(iCnt2=ui_User; iCnt2 > 0; --iCnt2)  // ui_User 접속상태일때 동작.
  {
   FD_SET(clntSock[iCnt2-1],&fsStatus); // 클라이언트소켓상태 확인을 위해 SET으로 바꿈.
   if(iMaxSock <= clntSock[iCnt2-1])
   {
    iMaxSock = clntSock[iCnt2-1] + 1;    
   }
  }
  
  select(iMaxSock,&fsStatus,0,0,0); // 소켓과키보드,읽어오는상태확인,
  if( 1 == FD_ISSET(servSock,&fsStatus)) // 랑데뷰소켓감지.
  {
   tempSock = accept(servSock, (struct sockaddr *)&echoClntAddr,&clntLen);
   // 소켓은 2가지가 있는데 랑데뷰소켓, 커뮤니케이션소켓이 있다.
   // accept 함수에 의해서 랑데뷰소켓이 생성됨.(랑데뷰소켓은 Clinet 의 주소를 들고있음)
   // 랑데뷰소켓이 Client의 정보를 들고 있다가 
   if( 0 > tempSock ) // 클라이언트소켓 생성 확인 구문
   {
    printf("accept() failed\n");
    continue;
   }
  
   // Network to ASCII, 클라이언트IP를 문자열[(ex)192.211.10.20]로 바꿔서 출력해줌.
   printf("Handling client IP %s\n", inet_ntoa(echoClntAddr.sin_addr)); 
   // Port 번호를 Network to Host short형 크기로 출력해줌.
   printf("Handling client PORT  %d\n", ntohs(echoClntAddr.sin_port)); 
   
   if(ui_User >= MAXUSER) // 접속자수 제한. 접속자가 5명이 넘어가면 끊고 새로받음.
   {
    close(tempSock);
    continue;
   }
   clntSock[ui_User] = tempSock;
   ++ui_User;
   printf("현재 접속자수는 [%d]명 입니다.\n", ui_User);
  }
  else if( 1 == FD_ISSET(0,&fsStatus) ) // 키보드 입력을 받으면 if 문 실행.
  {
   iRet = read(0, &uc_buff, 500); // 키보드로 버퍼에 저장.
   for(iCnt=ui_User;iCnt > 0; --iCnt)
   {           // 반복문을 통해서
    write(clntSock[iCnt-1],&uc_buff, iRet); // 접속한 사람한테 모두 다 날림.
   } 
  }
  else
  {
   for(iCnt=ui_User;iCnt > 0; --iCnt) // 접속자 모두에게 순서대로 메시지를 보냄.
   { // 여러명중에 특정 Client가 보낸 메시지를 각각 화면에 출력함.
    if( 1 == FD_ISSET(clntSock[iCnt-1],&fsStatus) ) // 커뮤니케이션소켓상태 변화가 생기면 if 문 실행.
    {// read함수로 접속한 client로 부터 글자를 받음.
     iRet = read(clntSock[iCnt-1],uc_buff,sizeof(uc_buff)-1); 
     printf("[Client%d: ", iCnt-1);//보낸이 확인.
     fflush(stdout);  // '\n' 비워줌.
    // uc_buff[iRet] = 0; // '\n'문자를 '\0' 으로 변환
     write(1,uc_buff,iRet-1); // 화면에 Client로 부터 온 글자를 출력.
     printf("]\n");// '\n' 출력,
     // 여기까지는 모두 서버에 출력됨.

     for(iCnt2=ui_User; iCnt2 > 0; --iCnt2)
     {
      write(clntSock[iCnt2-1],&uc_buff, iRet); // 접속한 사람한테 모두 다 날림.
     }
     
    // write(clntSock,uc_buff,iRet); // 클라이언트소켓에 uc_buff에 있는 글자를 출력.
    
    }
   }
   
   if( 'q' == uc_buff[0] ) // q 가 uc_buff에 차면 종료.
   {
    break; // 'q' 를 눌러서 종료.
   }
   
  }
 }

 for(iCnt2=ui_User; iCnt2 > 0; --iCnt2)
 {
  close(clntSock[iCnt-2]); // 클라이언트소켓을 닫기.
 }
 close(servSock); // 서버소켓을 닫기.
 return 0;
}


 

 

 

 Client.c

 #include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define RCVBUFSIZE 500

void my_bzero(void *vp, unsigned int ui_size);
void my_memset(void *vp, unsigned char uc_pad, unsigned int ui_size);

int main(int argc, char *argv[])
{
 int sock;
 struct sockaddr_in echoservaddr;
 unsigned short echoservport;
 char *servIP; // IP 주소.
 char *echoString;
 char Buffer[RCVBUFSIZE];
 unsigned int echoStringLen;
 int bytesRcvd;
 int totalBytesRcvd;
 int iRet;
 fd_set fsStatus;
 argc = 1;

 servIP = argv[1]; // IP 주소 변수.

 sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 소켓 생성.

 if( 0 > sock )
 {
  printf("socket() failed\n");
  return 0;
 }
 // 구조체 설정.
    my_memset(&echoservaddr, 0, sizeof(echoservaddr));  // 메모리 초기화.
 echoservaddr.sin_family  = AF_INET;    // 프로토콜 설정.
 echoservaddr.sin_addr.s_addr= inet_addr(servIP); // 주소 지정.
 echoservaddr.sin_port  = htons(9999); //  short형으로 host>network 크기지정.

 // 고객이 은행을 찾아가는 것. 서버의 accept() 함수랑 비슷.
 iRet = connect(sock, (struct sockaddr *)&echoservaddr, sizeof(echoservaddr));

 if( 0 > iRet )
 {
  printf("connect() failed\n");
  close(sock);
  return 0;
 }

 
 while(1)
 {
  FD_ZERO(&fsStatus); // 파일디스크립트 모두를 초기화(메모리 초기화)
  FD_SET(0,&fsStatus); // 키보드출력(stdin) 0번 비트를 지정 1로 변경, 상태를 알기위함.
  FD_SET(sock,&fsStatus); // 소켓을 지정, 저수준입/출력 변화 상태를 알기위함.
        // 여기선 Client에서 보내온 문자.
        
  // 소켓과키보드(변수갯수), 문자읽기상태확인(변수의주소),
  select(sock+1,&fsStatus,0,0,0); // 문자쓰기상태확인(변수의주소),예외처리(변수주소),select가 반환전까지 시간.
  
  if( 1 == FD_ISSET(0,&fsStatus) ) // 키보드의상태가 변화가있으면 1임. 없으면 0.
  {         // 변화가 있다면 if 문 실행.
   iRet = read(0,Buffer,sizeof(Buffer)); // 키보드로 받은 문자를 버퍼에 저장.
   //Buffer[iRet-1] = '\0';     // '\n' 문자를 '\0' 으로 받기위함.
   write(sock,Buffer,iRet); // iRet로 받은갯수만큼, Buffer에 저장후 글자를 출력.
   //putchar('\n');   // 비교를 위해 엔터를 쳐줌. 
   //iRet = read(sock,Buffer,500); // 위의 글자를 다시 버퍼에 저장. 
   //Buffer[iRet-1] = '\0';    // '\n' 문자를 '\0' 으로 받기위함.

   //printf("[%s]\n", Buffer);  // Buffer 에 있는 글자를 화면에 출력.
  }
  else if( 1 == FD_ISSET(sock,&fsStatus) ) // Server에서 보낸 문자가 생기면 sock에 변화가 생김.
  {          // 변화가 있다면 if 문 실행.
   iRet = read(sock,Buffer,500); // Server에서 보낸 문자 버퍼에서 읽어옴.
   printf("[Server : "); // 보낸이 확인.
   fflush(stdout);   // '\n' 비워줌.
   write(1, Buffer, iRet-1);// 서버 문자를 화면에 출력. 개행문자 제거.
   printf("]\n"); // 출력후 '\n'
  }
  if( 'q' == Buffer[0] )  // q 눌르면 종료.
  {
   break;
  }
 }
  
 close(sock); // 소켓 해제.
 return 0;
}

// memset 은 메모리에 원하는 값으로 채워줌.
void my_memset(void *vp, unsigned char uc_pad, unsigned int ui_size)
{
 while( 0 !=  ui_size )  // 메모리에 uc_pad의 값이 다 채워지면 반복문을 나옴.
 {       // ui_size가 0이 되면 나옴.
  *(unsigned char *)vp = uc_pad;
  --ui_size;
  vp = (unsigned char *)vp+1;  // void* 타입을 unsigned char 형으로 타입으로 바꿔서
 }         // 옮길 바이트(unsigned char)만큼을 지정해줬음.
}      

void my_bzero(void *vp, unsigned int ui_size)
{
 my_memset(vp, 0x00,ui_size); 
}