사랑한다1234 2013. 6. 20. 15:47

 

_________리눅스 & 네트워크______________________________________________________________

 

 

 vi 팁

 :set ff=dos     

 

 

 파일형식이 리눅스(유닉스)형태가 아니라 윈도우에서 메모장으로 열어봐도 잘보임.

                          ff (fileformat)

 스왑파일 생성안되게하기

 cd ~                                  <= 홈디렉토리로 이동 

 vi .vimrc                             <= vi 옵션파일 열어서 

 set noswapfile                    <= 마지막 라인에 추가

 

 

 

 

 

 select()를 이용한 멀티플렉싱

 

select()를 이용한 여러 클라이언트와의 통신

 

 ★ select()가 에러값 반환시 

     - 서버는 각각의 클라이언트에게 종료 명령을 보내고 자신도 종료

     -  클라이언트는 서버에게 종료명령 보내고 자신도 종료

 

 smart.h

#ifndef _SMART_H_
#define _SMART_H_

 

#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  10 

#define RCVBUFSIZE  500  // 버퍼사이즈
#define ENDMSG  "@!*$q" // 종료 메시지 값
#define LOGOUT  "quit"     // 로그아웃 값

 

#endif //_SMART_H

 server.c (서버)

#include "smart.h"

 

int main()

{

  unsigned char ucBuff[500];
  int servSock; // 랑데뷰 소켓번호를 받기위한 변수
  int clntSock[MAXUSER]; // 커뮤니케이션 소켓번호를 받기위한 배열

  int iMaxSock; // 고려할 최대 식별자수(제일 큰 소켓번호+1) 저장을 위한 변수

  unsigned int uiUser ; // 접속자 수를 카운트하기 위한 변수

  int iTempSock; // 소켓번호를 임시로 담기 위한 변수

  int iCnt;

  int iCnt2;

  int iRet;

  struct sockaddr_in echoServAddr; //랑데뷰 소켓  정보를 넣기 위한 구조체 변수
  struct sockaddr_in echoClntAddr; // 커뮤니케이션 소켓 정보를 넣기 위한 구조체 변수
  unsigned short echoServPort; // Port 번호를 넣기 위한 변수
  unsigned int clntLen;
  fd_set fsStatus; // 식별자 값들을 저장할 변수 
  
  echoServPort = 9999// Port 번호 

  servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    // 소켓 생성하여 소켓번호를 변수에 저장 (랑데뷰 소켓), 랑데뷰 : 만남 
    // AF_INET(Address family) = PF_INET(Protocol family) = 2  (인터넷 Protocol family)               
    // SOCK_STREAM : 신뢰성있는 바이트 스크림 전송을 위한 소켓, 속도느림, TCP
    // SOCK_DGRAM : 최선형의 데이터그램 소켓, 속도빠름, 파일깨질수 있음(신뢰성낮음), UDP



  if( servSock < 0 ) // socket이 생성되지 못하면 -1 반환하므로 에러메시지 출력하고 종료
  {
          printf("socket() failed\n");
          return 0;  
  }

  memset( &echoServAddr, 0sizeof(echoServAddr)); // echoServAddr를 0으로 초기화  
  // memset(메모리 셋팅), 특정 메모리를 1바이트 단위로 원하는 데이터로 채워 넣을때 
  
  echoServAddr.sin_family = AF_INET; // 2, Internet protocol
  
  echoServAddr.sin_addr.s_addr = htonl (INADDR_ANY); // BIG Endian으로 바꿔서 넣어줌  
  // 0 또는 INADDR_ANY를 넣으면 자동으로 찾아서 IP 넣어줌, 아님 직접 자신의 IP를 넣어주어야 함.
  //htonl : host to network long(자료형크기) , 네트워크 표준은 Big Endian 임
  // INADDR_ANY = 0
  
  echoServAddr.sin_port = htons (echoServPort); // Port 번호 Big Endian 으로 바꿔서 넣어줌 
  // htons : host to network short(자료형크기)

  
  iRet = bind(servSock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));
  
/* 클라이언트가 서버에 접속하기 위해서 서버의 소켓은 지정된 내부 주소(서버 자신의 IP주소) 및
      포트 를 가지고 있어야 하는데 이일을 하는 함수가 bind() 임.*/

  // 구조체변수에 넣어 놓은 소켓 값들(포트번호)을 생성해 놓은 소켓과 결합

  
  if(iRet < 0
  {
          close(servSock);//bind 실패하면 열어 놓은 소켓 닫음     
          printf("bind() failed\n");
          return 0;  
  }


iRet = listen(servSock, MAXPENDING); // 대기할소켓, 대기자수(MAXPENDING)
  //  클라이언트들로 부터의 연결 요구를 기다리도록
  // TCP 연결 요구들이 프로그램에 의해 처리되고 받아들여 질수 있도록 대기자 수만큼 저장함
   
  
  if(iRet < 0)
  {
          close(servSock);    
          printf("listen() failed\n");
         return 0;  
  }

  clntLen = sizeof(echoClntAddr); // 구조체 변수의 크기저장


  iMaxSock = servSock+1;// 가장 큰 소켓식별번호 + 1 = 고려해야할 최대 식별자수

 uiUser = 0;

     

 while(1)

 {


  FD_ZERO(&fsStatus); // 전체 0으로 clear
  FD_SET(0, &fsStatus); // 해당 식별자(키보드) set(1) 함.
  FD_SET(servSock, &fsStatus); // 해당 식별자(소켓) set(1) 함.
  
  for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2)
  {

   
   FD_SET(clntSock[iCnt2-1], &fsStatus); // 해당 식별자(소켓) set(1) 함.
  

     if(iMaxSock<=clntSock[iCnt2-1])
   {
           iMaxSock = (clntSock[iCnt2-1])+1;

           // 소켓 번호가 기존 식별자번호 보다 크면 고려해야할 식별자수 변경시킴 

   }

   
  }
  
  iRet = select(iMaxSock, &fsStatus, 0, 0, 0); 

//식별자 갯수(제일큰소켓번호+1), read 이벤트 발생?, write이벤트 발생?, 에러이벤트 발생?, 대기시간(time out)
//select() 는 식별자 리스트들을 수정하여 준비된(이벤트가 발생한) 식별자들에 해당하는 위치만을 지정함(set)
//NULL 을 넘기면 그 형의 입출력을 무시하도록 만듬, timeout : NULL(이벤트가 발생할때까지 무한대기)
//select()는 성공시 : 입출력 준비가 된 전체 식별자 들의 개수를 반환, 에러시:-1, 타임아웃시 0 반환
/* 대기시간은 struct timeval tv; 구조체 변수를 만들어서 tv.tc_sec = 5;
   이런 식으로 값을 넣은 뒤 구조체변수의 주소를 인자(&tv)로하여 줄수있음.*/

 

  if(0 > iRet) //select() 에러 발생시 
  {  
         for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2)
        {    
             write(clntSock[iCnt2-1], "q", 2); // q(종료명령)를 클라이언트에게 보냄 
        }
        break;
  } 


  if( 1 == FD_ISSET(servSock, &fsStatus)) // 랑데뷰소켓 식별자  read 가능일때  
  {


   iTempSock = accept(servSock, (struct sockaddr *) &echoClntAddr, &clntLen );
   // echoClntAddr은 클라이언트의 주소가 저장됨
   // 소켓이 만들어져 iTempSock 변수에 소켓번호 저장됨 (커뮤니케이션 소켓)


   if(iTempSock < 0)
   {      
          printf("accept() failed\n");
          continue; 
   }
   
   printf("Handling client IP %s\n", inet_ntoa(echoClntAddr.sin_addr));
   // 클라이언트의 IP를 출력
   //inet_ntoa 는 network to ascii로 인자(변수)값을 아스키 코드 값으로 바꿔서
   //IP주소값 형태의 문자열로 만들어 문자열 주소값 반환
   // char *inet_ntoa(struct in_addr in);

   

   printf("Handling client Port %d\n", ntohs(echoClntAddr.sin_port) );
   // 클라이언트의 Port 출력 , ntohs로 다시 little Endian 으로 바꿔줌
  
   if( MAXUSER <= uiUser ) // 접속자수 제한 
   {
          close(iTempSock);
          continue;
   }
   
   clntSock[uiUser] = iTempSock; // 클라이언트 소켓번호 배열에 넣음
   
   ++uiUser; // 현재 접속자수 1 증가
   printf("현재 접속자수 : %d 명\n",uiUser);
 

  
  }
  else if( 1 == FD_ISSET(0, &fsStatus)) // 0번 식별자(키보드)로 읽기 가능할때  
  {
          iRet = read(0, ucBuff, 500); // 키보드로 받은 값을 버퍼(ucBuff)에 저장 
  
          for(iCnt = uiUser; 0 < iCnt ; --iCnt)
          {    
                write(clntSock[iCnt-1], ucBuff, iRet); // 버퍼의(ucBuff) 값을 클라이언트에게 보냄 
                                                                         // 서버와 연결된 클라이언트 모두에게 전송됨                          

          } 
 
  } 
  else
  {
          for(iCnt = uiUser; 0 < iCnt ; --iCnt)
          { 
                    if( 1 == FD_ISSET(clntSock[iCnt-1], &fsStatus)) //해당 클라이언트소켓 식별자로 읽기 가능할때  
                    {
                             iRet = read(clntSock[iCnt-1], ucBuff, 500);
                            //해당 커뮤니케이션 소켓에서 값을 읽어와 버퍼에 넣음
                               
                            printf("[client%d: ",iCnt-1);
   
                            fflush(stdout);// 버퍼 비움    
  
                            write(1, ucBuff, iRet-1); // 버퍼의(ucBuff) 값을 모니터에 출력
   
                            printf("]\n");
   
                           for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2)
                           {    
                                 write(clntSock[iCnt2-1], ucBuff, iRet); // 버퍼의(ucBuff) 값을 클라이언트에게 보냄 
                                 // 서버와 연결된 다른 클라이언트에게 전달됨 

                           }
                           if('q' == ucBuff[0])
                           {
                                    break;
                           }
                   }
           }
           if(ucBuff[0] == 'q') // 버퍼(ucBuff)의 첫번째 값이 'q' 이면 탈출 
           {
                 break;
           }
  }


 }
   
 close(servSock);
 for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2)
 {  
        close(clntSock[iCnt2-1]); //소켓모두 닫고 프로그램 종료  
 }
 
  
 return 0;


}

 

 client.c (클라이언트)

#include "smart.h"

int main()
{


  int sock;
  struct sockaddr_in echoServAddr;
  unsigned short echoServPort;
  char * servIP;
  char * echoString;
  char Buffer[RCVBUFSIZE];
  unsigned int echoStringLen;
  int bytesRcvd;
  int totalBytesRcvd;
  int iRet;  
  fd_set fsStatus;// 식별자 값들을 저장할 변수 (128바이트)

  
  servIP = "192.168.10.48"// 연결시도 할 IP 어드레스(서버주소)

  sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 소켓 생성 
  
  if(0 > sock) // socket 생성 실패시 종료 
  {
        printf("socket() ERROR\n");
    
        return 0;
  }  
  
  memset(&echoServAddr, 0sizeof(echoServAddr));
  // 구조체 변수 0으로 초기화 

  echoServAddr.sin_family = AF_INET; // Internet Protocol
  
  echoServAddr.sin_addr.s_addr = inet_addr(servIP); 
  // 문자열(IP주소)을 4바이트로 정수로 바꿔서 대입 
  //unsigned long int inet_addr(const char *cp);
  //unsigned long int inet_network(const char *cp);
       
  echoServAddr.sin_port = htons(9999); // Port 번호 Big Endian 으로 바꿔서 넣어줌  

  iRet = connect(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr));
  // 두번째 인자의 IP주소(서버)로 연결시도 
  
  if(0 > iRet) // connect 실패시 sock 닫고 종료 
  {

    printf("connect() ERROR\n");
    close(sock);
    
   return 0;

  }
    
    
  while(1)
  {

    FD_ZERO(&fsStatus); // 전체 0으로 clear
    FD_SET(0&fsStatus); // 해당 식별자(키보드) set(1) 함.
    FD_SET(sock, &fsStatus); // 해당 식별자(소켓) set(1) 함.

    iRet = select(sock+1&fsStatus,0,0,0);
//식별자 갯수(제일큰소켓번호+1), read 이벤트 발생?, write이벤트 발생?, 에러이벤트 발생?, 대기시간(time out)
//select() 는 식별자 리스트들을 수정하여 준비된(이벤트가 발생한) 식별자들에 해당하는 위치만을 지정함(set)
// NULL 을 넘기면 그 형의 입출력을 무시하도록 만듬, timeout : NULL(이벤트가 발생할때까지 무한대기)
//select()는 성공시 : 입출력 준비가 된 전체 식별자 들의 개수를 반환, 에러시:-1, 타임아웃시 0 반환
/* 대기시간은 struct timeval tv; 구조체 변수를 만들어서 tv.tc_sec = 5;
   이런 식으로 값을 넣은 뒤 구조체변수의 주소를 인자(&tv)로하여 줄수있음.*/
    

   

    if(0 > iRet) // select() 에러나면...
   {   
         printf("select() ERROR\n");
         write(sock,"q",2);
         break; 
   }


    if(1 == FD_ISSET(0&fsStatus)) // 0번 식별자(키보드)로 읽기 가능할때
    {


      iRet = read(0, Buffer, 500);
      // 키보드 입력될때까지 대기 
     

      write(sock, Buffer, iRet); // 소켓(서버)으로 버퍼값 보냄
    

   }     
  if(1 == FD_ISSET(sock, &fsStatus)) //소켓으로 부터 읽기 가능할때 
  {
            iRet = read(sock, Buffer, 500); // 소켓으로부터 값 읽어봐서 버퍼에 넣음 
      
            printf("[server: ");
            fflush(stdout);
      
            write(1, Buffer, iRet-1); // 버퍼의 값들을 모니터에 출력   
            printf("]\n");


  }

  if(Buffer[0== 'q'
  {
          break;
  }


  }
  close(sock);
  return 0;


}

 

 

 

____________________________________________________________________________________

 

 

 

 

 

__________실습파일 & 수업자료____________________

 

client

 

client.c

 

DieWithError.c

 

HandleTCPClient.c

 

server

 

server.c

 

smart.h

 

TCPEchoClient

 

TCPEchoClient.c

 

TCPEchoServer.c

___________________________________________________