사랑한다1234 2013. 6. 21. 15:44

________리눅스 & 네트워크________________________________________________________________________________________

 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  "-zeq="  // 로그아웃 값

#define CLTEND  0x05       // ctrl + E , 종료 메시지 

#define OUTMSG  "한명이 퇴장하셨습니다."


#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;
 //fd_set : 128바이트 크기의 자료형이고
 //식별자리스트를 저장하기 위한 변수를 선언할때 사용
 
 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, 0, sizeof(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; // 현재 접속자수 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], ENDMSG, sizeof(ENDMSG));
               // ENDMSG(종료 메시지) 값을 접속된 모든 클라이언트에게 보냄 
         }
         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 으로 바꿔줌
  
   printf("Socket Number %d\n", iTempSock); //소켓번호 출력
  
   
   if( MAXUSER <= uiUser )//접속자수 제한
   {
          close(iTempSock);
          continue;
   }
   
   clntSock[uiUser] = iTempSock; // 새로 접속한 소켓(식별자)번호 배열에 저장 
   
   ++uiUser; // 현재 접속자수 1 증가
   printf("현재 접속자수 : %d 명\n",uiUser);
   


  }
  if( 1 == FD_ISSET(0, &fsStatus)) // 0번 식별자(키보드)로 Read 이벤트 발생할때 
  {


   iRet = read(0, ucBuff, 500); // 키보드로 받은 값을 버퍼(ucBuff)에 저장 
   // 키보드의 입력이 발생했을때 그 값들이 운영체제 버퍼에 들어가고
   // 키보드이벤트가 발생하면 read()가 키보드 입력을 받지 않아도
   // 운영체제의 버퍼값을 바로 읽어들인다.
  
   if(CLTEND == ucBuff[0]) // 키보드로 첫번째 값을 ctrl + E 입력했을때  
   {


    for(iCnt = uiUser; 0 < iCnt ; --iCnt)
    {    
            write(clntSock[iCnt-1], ENDMSG, sizeof(ENDMSG)); 
           // 접속된 모든 클라이언트에게 ENDMSG(클라이언트를 종료시키는 값)을 보냄 
    }


                break; // 반복문 탈출하여 소켓 닫고 프로그램 종료


        }
   
        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);
                          // 해당 커뮤니케이션 소켓에서 값을 읽어와 버퍼에 넣음
       
                          if(0 == strcmp(LOGOUT,ucBuff))// 클라이언트로 부터 로그아웃 메시지가 왔을때 
                          {
                                 close(clntSock[iCnt-1]);// 로그아웃 메시지를 보내온 소켓번호 닫음 
                                 --uiUser;// 현재 접속자수 -1
                                 clntSock[iCnt-1] = clntSock[uiUser];
                                 // 제일 뒤의 소켓 번호를 빈(로그아웃된) 배열 자리에 채움  
      
                                 for(iCnt2 = uiUser; 0 < iCnt2 ; --iCnt2)
                                 {    
                                        write(clntSock[iCnt2-1],OUTMSG, sizeof(OUTMSG));
                                         // 아웃메시지를  모든 클라이언트에게 보냄 
      
                                 }      
                                 printf("["OUTMSG"]\n");

                           // 아웃메시지 출력 , "" 하면 개행문자가 붙었더라도 붙어서 출력된다.


                                 continue;
                         }
                
                        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) 값을 모든 클라이언트에게 보냄
     
                       }
                }
         }
  }


 }
   
 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, 0, sizeof(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_ZERO(fd_set *fs_Status):fs_Status Bit Field all initialize to 0
  FD_SET(0, &fsStatus); // 해당 식별자(키보드) set(1) 함.
  //FD_SET(0, fd_set *fs_Status):fs_Status bit field<- 0 add, field value:1
  FD_SET(sock, &fsStatus); // 해당 식별자(소켓) set(1) 함.
  //FD_SET(int sock, fd_set *fs_Status):fs_Status bit field<- sock add, field value: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, LOGOUT, sizeof(LOGOUT)); // 소켓(서버)으로 로그아웃 값 보냄
         break; // 반복문 탈출하여 소켓닫고 종료 
  }
  
  //FD_ISSET(0, fd_set *fs_Status):0 <- change yes/no check, field value 1 True Return
  if(1 == FD_ISSET(0, &fsStatus)) // 0번 식별자(키보드)로 이벤트 발생 할때
  {
          iRet = read(0, Buffer, 500);
          // 키보드의 입력이 발생했을때 그 값들이 운영체제 버퍼에 들어가고
          // 키보드이벤트가 발생하면 read()가 키보드 입력을 받지 않아도
          // 운영체제의 버퍼값을 바로 읽어들인다.
  

   if( CLTEND == Buffer[0]) //버퍼의 첫번째  값이  Ctrl + E 일때 
   {    
         write(sock, LOGOUT, sizeof(LOGOUT)); // 소켓(서버)으로 로그아웃 값 보냄
         printf(">> LOGOUT <<\n");
         break;// 반복문 탈출하여 소켓닫고 종료    
   }
    
   write(sock, Buffer, iRet); // 소켓(서버)으로 버퍼값 보냄

  }  
  if(1 == FD_ISSET(sock, &fsStatus))//소켓으로 부터 Read 이벤트 발생할때
  {
        iRet = read(sock, Buffer, 500); // 소켓으로부터 값 읽어와서 버퍼에 넣음
   
        if( 0 == strcmp(ENDMSG, Buffer)) // 버퍼의 첫번째  값이 종료 메시지일때 
        {    
             printf(">> 서버가 종료하였으므로 프로그램을 종료합니다. <<\n");
             // 서버가 종료 메시지를 보내온것이므로 반복문 나가서 소켓닫고 종료하게 됨.
             break;   
       }
 

   printf("[server: ");
   fflush(stdout);
   
   write(1, Buffer, iRet-1);// 버퍼의 값들을 모니터에 출력, 개행문자 없애기 위해 iRet-1  
   printf("]\n");

  }


 }
  
 close(sock);

       return 0;
}

 

 

 


 

 

 

________________________________________________________________________________________________________________________________

 

 

 

 

__________실습파일& 수업자료_____________________

 

 

client

 

client.c

 

sample

 

sample.c

 

server

 

server.c

 

smart.h

 

test

 

test.c

 

TCPIP.zip

_______________________________________________________