TCPIP

2013.07.10_Thread를 이용한 Server 구현.

성엽이 2013. 7. 10. 18:48


 TCPserver.c

 #include "smart.h"


#define MAXPENDING   5
#define MAXUSER    10

///// Thread ID 를 알아야하기때문에 구조체로 넘겨줌. //
// User 사람 수 관리.
typedef struct _TInfo
{
  unsigned int    uiUser;  // 위치번호.
  int         iSock;    // 소켓번호.
  pthread_t     t_ID;    // Thread 번호.
}TInfo;
/////

void *ClientRecv(void *);

///// 크리티컬 섹션 시작. /////
unsigned int   uiUser;
TInfo *       stpLink[MAXUSER]; // 포인터 배열. 사람수 만큼 만듦.
///// 크리티컬 섹션 종료. /////

///// 뮤텍스 객체 선언. /////
pthread_mutex_t MLock;

int main(int iArg, char *cpArg[])
{
  int         servSock; 
  TInfo       st_TempInfo;
  struct       sockaddr_in echoServAddr;
  struct       sockaddr_in echoClntAddr;
  unsigned short   echoServPort;
  unsigned int   clntLen;
  int       iRet;
  int       iCnt;
  int       iCnt2;
  unsigned char   ucBuff[500];
  
  if(1 == iArg)
  {
    echoServPort = 9999;
  }
  else if(2 == iArg)
  {  
    echoServPort = atoi(cpArg[1]);
  }
  
  servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(0 > servSock)
  {
    printf("socket() failed");

    return 0;
  }

  memset(&echoServAddr, 0sizeof(echoServAddr));
  echoServAddr.sin_family = AF_INET;
  echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  echoServAddr.sin_port = htons(echoServPort);

  iRet = bind(servSock, (struct sockaddr *)&echoServAddr, sizeof(echoServAddr));
  if(0 > iRet)
  {
    close(servSock);
    printf("bind() failed");

    return 0;
  }

  iRet = listen(servSock, MAXPENDING);
  if(0 > iRet)
  {
    close(servSock);
    printf("listen() failed");
    return 0;
  }

  clntLen = sizeof(echoClntAddr);

  uiUser = 0;
  
  // 뮤텍스 초기화.
  pthread_mutex_init(&MLock, NULL);
  while(1)
  {  // 구조체의 iSock 넣음.
    st_TempInfo.iSock = accept(servSock, (struct sockaddr *)&echoClntAddr, &clntLen);
    if(0 > st_TempInfo.iSock)
    {
      printf("accept() failed");
      continue;  
    }  
    printf("Handling client ip : %s\n", inet_ntoa(echoClntAddr.sin_addr));
    printf("Handling client port : %d\n", ntohs(echoClntAddr.sin_port));
    printf("Handling client socket number : %d\n", st_TempInfo.iSock);
    if(MAXUSER <= uiUser)
    {
      close(st_TempInfo.iSock);
      continue;
    }
    // Lock !
    // uiUser 접근할때마다 Lock/UnLock 을 걸어줘서.
    // 다른 Thread가 접근을 못하도록 한다. 즉 공유자원을 
    // 같이 써서 발생하는 혼선을 막는다.
    pthread_mutex_lock(&MLock);
    
    // 구조체에다가 uiUser 의 값을 넣어둠.
    st_TempInfo.uiUser = uiUser;
  
    // 첫번째 인자에 Thread ID 주소.
    // pthread_create 의 마지막 인자를 구조체 통째로 넘겨줌.
    pthread_create(&st_TempInfo.t_ID, 0, ClientRecv, &st_TempInfo);
    ++uiUser;
    pthread_mutex_unlock(&MLock);
    // Unlock !
    while(0 != st_TempInfo.iSock);  // ClientRecv 에서 Thread를 생성하기 전까지 잡아둠.
    printf("현재 접속자 수 : %d\n", uiUser);
  }

  close(servSock);

  return 0;
}

// Thread ID 를 받아서 Client Sock을 확보
void *ClientRecv(void *vp)
{
  unsigned char   uc_Buffer[500];
  unsigned char   ucS_Buffer[500];
  int       iRet;
  unsigned int   uiCnt;
  // stMyInfo 구조체를 하나 더 생성해서 stTempInfo의 값을
  // 몽땅 넣어줌. (uiUser, iSock, Thread ID)
  TInfo stMyInfo = *((TInfo *)vp);

  // 전역변수에 선언해놓은 포인터를 이용해서 stMyInfo를 가리킨다.
  stpLink[stMyInfo.uiUser] = &stMyInfo;    
  
  // 구조체안의 iSock 의 번호를 삭제.
  ((TInfo *)vp)->iSock = 0;
  // 구조체 안의 iSock 번호가 0 이므로 main 함수의 while(0 != st_TempInfo.iSock) 을 통과.
  // Thread 중복을 방지하기 위해서 사용.
  // Thread 와 Main 이 여기서 부터 따로 돌겠다.
  
  while(1)
  {
    iRet = read(stMyInfo.iSock, uc_Buffer, 500);
    if(1 >= iRet)
    {  // Ctrl+C로 오류가 뜨면 멈춤.
      break;
    }
    uc_Buffer[iRet - 1= 0// Enter 방지.

      // 서버에 직접 적힘.
    printf("[MyUserNum:%d]:[%s]\n", stMyInfo.uiUser, uc_Buffer);
    if('$' == uc_Buffer[0])
    {  
      break;
    }
    // 다른 Thread 에 보내기위해서 ucS_Buffer에다가 글을 넣어둠.
    iRet = sprintf(ucS_Buffer, "[MyUserNum:%d]:[%s]\n", stMyInfo.uiUser, uc_Buffer);
  
    // 메모리에 출력할 갯수(iRet)를 크기만큼 넣어주고,
    // uiUser의 사람수만큼 for 문 돌림.
    for(uiCnt=0 ; uiCnt<uiUser ;++uiCnt)
    {  
      // 내가 적은 문자출력은 나에게 보내주지 않음.
      if(&stMyInfo == stpLink[uiCnt])
      {  
        continue;
      }
      // stpLink[] 포인터가 가리키는 iSock의 번호에다가 각각 보낸다.
      write(stpLink[uiCnt]->iSock, ucS_Buffer, iRet);
    }
  }
  // Thread 종료.
  pthread_mutex_lock(&MLock);
  
  // uiUser 감소.
  --uiUser;
  // stMyInfo 안의 uiUser에다가 , 
  // 현재 감소된 uiUser의 수를 넣어준다.
  // 포인터로 가르킨다.
  stpLink[stMyInfo.uiUser] = stpLink[uiUser];

  // 두명이 접속 했을시에 전역번수의 uiUser의 값은 2명이다.
  // 이때 stpLink가 가리키는 stMyInfo.uiUser의
  // 배열 갯수가 [0],[1] 로 2개이다. 
  // 만약 [0]번의 손님이 나가면, stpLink[0]이 가리키고있는 곳을 
  // [1]번이 가리키는 곳의 구조체로 바꿔주고, 구조체의 uiUser 번호를
  // 바꿔줘서 uiUser로 유저의 현재 수를 체크할수있다.
  stpLink[stMyInfo.uiUser]->uiUser = stMyInfo.uiUser;
  
  pthread_mutex_unlock(&MLock);
  close(stMyInfo.iSock);
  return 0;

}


int sprintf(char *str, const char *format, ...);             <=   sprintf() 함수          

#include <stdio.h>


int main()
{

 char cBuff[500] = "안녕하세요\n"; 
 
 sprintf(cBuff,"1234는 16진수로 %X입니다.\n", 1234);
 // cBuff(메모리) 안에 " " 안의 내용이 프린트(저장) 됨.
 // 반환값은 프린트된 문자 갯수임. 
 

 printf(cBuff); // cBuff안의 문자열이 출력됨.
 

 return 0;


}

 

 

 

 

 

 


 



 : stTempInfo가 ClientRecv로 넘어오면 그때의 구조체를 stMyInfo에 넣어두고 사용하는 방식으로 테스트 해보았다.

 






 TCPclient.c

#include "smart.h"


#define RCVBUFSIZE   500

void My_Memset(void *, unsigned charunsigned int);
void My_Bzero(void *, unsigned int);

int main(int argc, char *argv[])
{
  int sock;
  struct sockaddr_in echoServAddr;
  char *servIP;
  char cBuff[RCVBUFSIZE];
  int iRet;
  fd_set fs_status;
  
  if(1 == argc)
  {
    servIP = "192.168.10.200";
  }
  else
  {
    servIP = argv[1];
  }

  sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(0 > sock)
  {
    printf("socket() failed\n");
    
    return 0;
  }

  My_Bzero(&echoServAddr, sizeof(echoServAddr));
  echoServAddr.sin_family = AF_INET;
  echoServAddr.sin_addr.s_addr = inet_addr(servIP);
  echoServAddr.sin_port = htons(9999);

  iRet = connect(sock, (struct sockaddr *)&echoServAddr, sizeof(echoServAddr));
  if(0 > iRet)
  {
    close(sock);
    printf("connect() failed\n");

    return 0;
  }
    
  while(1)
  {
    FD_ZERO(&fs_status);
    FD_SET(0&fs_status);
    FD_SET(sock, &fs_status);
    iRet = select(sock+1&fs_status, 000);
    if(0 > iRet)
    {
      write(sock, LOGOUT, sizeof(LOGOUT));
      printf("select() failed\n");

      break;
    }
    if(1 == FD_ISSET(0&fs_status))
    {
      iRet = read(0, cBuff, 500);
      if(CLTEND == cBuff[0])
      {
        write(sock, LOGOUT, sizeof(LOGOUT));
        printf("Log out\n");

        break;  
      }
      write(sock, cBuff, iRet);
      
    }
    if(1 == FD_ISSET(sock, &fs_status))
    { // Ctrl+C 했을시 조건문.
      iRet = read(sock, cBuff, 500);
      if(1 > iRet)
      {
        printf("Server Down\n");
        break;  
      }
      if(0 == strcmp(ENDMSG, cBuff))
      {
        printf("Server Down\n");
        break;  
      }

      printf("[Server: ");
      fflush(stdout);
      write(1, cBuff, iRet-1);
      printf("]\n");
    }
  }
  close(sock);

  return 0;
}

void My_Memset(void *vp, unsigned char ucPad, unsigned int uiSize)
{
  while(0 != uiSize)
  {
    *(unsigned char *)vp = ucPad;
    --uiSize;
    vp = (unsigned char *)vp + 1;
  }
  
  return;
}

void My_Bzero(void *vp, unsigned int uiSize)
{
  My_Memset(vp, 0x00, uiSize);

  return;
}