TCPIP

2013.07.09_TCP/IP를 Thread로 통신_

성엽이 2013. 7. 9. 14:46


 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.58";
  }
  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))
    {
      iRet = read(sock, cBuff, 500);
      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;
}



 TCPserver.c

 #include "smart.h"


#define MAXPENDING   5
#define MAXUSER    10

///// Thread ID 를 알기위해서 구조체로 넘겨줌. //
typedef struct _TInfo
{
  unsigned int uiUser;
  int       iSock;
}TInfo;
/////

void *ClientRecv(void *);

///// 크리티컬 섹션 시작. /////
unsigned int   uiUser;
pthread_t     t_ID[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)
  {
    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;
    
    // pthread_create 의 마지막 인자를 구조체 통째로 넘겨줌.
    pthread_create(&t_ID[uiUser], 0, ClientRecv, &st_TempInfo);
    ++uiUser;
    pthread_mutex_unlock(&MLock);
    // Unlock !
    while(0 != st_TempInfo.iSock);  // ClientRecv 동작 하는지 확인.
    printf("현재 접속자 수 : %d\n", uiUser);
  }

  close(servSock);

  return 0;
}

// Thread ID 를 받아서 Client Sock을 확보
void *ClientRecv(void *vp)
{
  unsigned char uc_Buffer[500];
  int iRet;
  
  // 구조체에 있는 값을 받아서 iSock에다가 Thread ID
  // uiUser 에다가 유저 숫자 대입.
  int iSock = ((TInfo *)vp)->iSock;
  unsigned int ui_MyUser = ((TInfo *)vp)->uiUser;
  
  // 구조체안의 iSock 의 번호를 삭제.
  ((TInfo *)vp)->iSock = 0;
  // 구조체 안의 iSock 번호가 0 이므로 112줄의 while문을 통과.
  // Thread 중복을 방지하기 위해서 사용.
  // Thread 와 Main 이 여기서 부터 따로 돌겠다.
  
  while(1)
  {
    iRet = read(iSock, uc_Buffer, 500);
    if(1 >= iRet)
    {  // Ctrl+C로 오류가 뜨면 멈춤.
      break;
    }
    uc_Buffer[iRet - 1= 0;
    printf("[%dSock]:[%s]\n", iSock, uc_Buffer);
    if('$' == uc_Buffer[0])
    {  
      break;
    }
  }
  // uiUser 나감.
  pthread_mutex_lock(&MLock);
  
  --uiUser;
  t_ID[ui_MyUser] = t_ID[uiUser];
  
  pthread_mutex_unlock(&MLock);
  close(iSock);
  return 0;

}



<출력 화면>


뮤텍스 출처 :

출처 : http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/Thread/Beginning/PthreadApiReference 


  • Semaphore는 언제 사용하는가?
    • 프로세스 간 메시지를 전송하거나, 혹은 공유메모리를 통해서 특정 data를 공유하게 될 경우 발생하는 문제는, 공유된 자원에 여러 개의 프로세스가동시에 접근 하면서 발생한다. 단지, 한번에 하나의 프로세스만 접근 가능하도록 만들어 줘야 하고, 이때 Semaphore를 쓴다.
    • Thread 에서는 뮤텍스, 프로세스에서는 세마포어
    • 교착상태를 방지하기 위해, A 프로세스가 접근했을 때 다른 프로세스의 접근을 막고, 끝난 후 접근을 풀어준다. 이때 세마포어로 접근을 막고 접근을허용할 수 있다.
  • Semaphore 작동 방식
    • 차단을 원하는 자원에 대해서 semaphore를 생성하면 해당 자원을 가리키는 semaphore 값이 할당된다.
    • 이 값이 0 이면 해당 자원에 접근할 수 없고, 0보다 크면 해당 자원에 접근할 수 있다.

    • 그러므로, 자원에 접근하기 전에 semaphore 값을 검사해서 값이 0 이면 자원을 사용할 수 있을 때까지 기다리고, 0 보다 크면 자원에 접근해서semaphore 값을 0으로 감소 시켜서, 다른 프로세스가 자원에 접근할 수 없도록 하고, 자원의 사용이 끝나면 다시semaphore 값을 증가 시켜 다른프로세스가 사용할 수 있게 해준다.

  • 뮤텍스와 세마포어
    • 뮤텍스(Mutex) – 상호 배제
      • 화장실에 들어가기 위한 열쇠를 한 사람이 가지고 있다면, 그 사람만이 들어갈 수 있다. 대기열(큐)에 기다리게 됨. à 세마포어의 일종이다.
      • Critical Section을 가진 Thread 들이 running time이 서로 겹치지 않게, 각각 단독으로 실행하게 하는 기술
      • 뮤텍스는 한 번에 하나의 Thread만이 실행되도록 하는 재 입장할 수 있는 코드 섹션 직렬화된 접근이 가능하게 할 때 사용
      • 뮤텍스 객체는 제어되는 섹션에 하나의 Thread만을 허용하기 때문에 해당 섹션에 접근하려는 다른 Thread들을 강제적으로 막음으로써 첫 번째 Thread가 해당 세션을 빠져 나올 때까지 기다린다.
    • 세마포어(Semaphore)
      • 화장실이 n칸(접근할 수 있는 최대 허용치 만큼 동시에 사용자 접근을 할 수 있게 함), 세마포어 카운트가 0이면 대기
      • Dead Lock을 피하기 위한 기술 중 하나
      • Thread가 Critical Section에 접근할 때, 해당 Thread는 Semaphore 카운트를 감소시키고, 수행 종료 후 원래대로 증가.
      • 공유 리소스에 접근할 수 있는 최대 허용치 만큼 동시에 사용자 접근을 할 수 있게 하는 기술
      • 동기화 대상이 여러 개일 경우에 사용
  • “임계 영역에 동시 접근할 수 있는 쓰레드의 개수는 10개” 의 의미
    • 세마포어는 내부에 카운트를 두어서 소유권을 가질 수 있는 쓰레드 들의 수를 설정할 수 있다.
    • 동시에 실행될 수 있는 쓰레드를 설정 할 수 있다는 뜻.
    • 바이너리 세마포어가 아닌 경우는 세마포어(내부 카운트 2이상)는 스레드가 동시에 같은 공유 자원에 접근할 수 있기 때문에 Critical Section이나 뮤텍스 같은 다른 동기화 자원과 함께 사용해야한다.
    • 동시에 접근 할 수 있는 쓰레드의 수를 설정하기 위해 쓰레드 풀링을 사용한다.
  • 출처 : http://mizar.co.kr/?p=703