2011년7월18일_fork( )를 사용한 멀티테스킹 TCP/IP 서버와 클라이언트간 양방향통신시 문제점 ③


실습했던,

2011년7월18일_fork( )를 사용한 멀티테스킹을 이용해 TCP/IP 서버와 클라이언트간 양방향 통신을 해보자.
http://sumanaki.tistory.com/132

예제코드는 몇 가지 문제를 가지고 있는데 그 중 3번째 프로세스가 남아 있는 경우의 해결법을 알아보자.
수신받는 프로세스가 남아 있어 상대방이 보낸 메세지를 계속 수신하니 자식 프로세스가 살아 남았다고 할 수 있다.
문제를 해결하기 위해 자식 프로세스를 소멸시켜 보자.


● 문제해결법

image  

일단 선생님께서 힌트를 주신대로 부모프로세스와 자식프로세스가 하는 일을 바꾸었다.
부모프로세스는 수신하고, 자식프로세스는 송신한다.
그리고 모두 자식프로세스가 종료될 때 발생하는 시그널을 처리하는 코드를 추가하였다.

   1:  #include <sys/wait.h>
   2:  void z_handler();        //시그널 처리함수
   3:   
   4:  int state;
   5:  struct sigaction act;
   6:   
   7:  // signal설정
   8:  act.sa_handler = z_handler;
   9:  sigemptyset(&act.sa_mask);
  10:  act.sa_flags = 0;
  11:   
  12:  state = sigaction(SIGCHLD, &act, 0);    //자식프로세스 종료시 발생하는 시그널 SIGCHLD이용
  13:   
  14:  if(0 != state)
  15:  {
  16:      fprintf(stderr, "sigaction() error. \n");
  17:      exit(1);
  18:  }


08행: sa_handler 함수포인터에 z_handler의 주소를 넣었다.
       그러니 시그널이 발생하면 z_handler( )가 실행될 것이다.

12행: sigaction( )로 SIGCHLD시그널을 사용한다고 설정하여 자식프로세스가 종료될 경우 시그널이 발생하도록 하였다.



image 

클라이언트측에서 “exit”를 입력하면,
자식프로세스를 통해 서버의 부모프로세스를 향해 “exit”문자열을 전송하고 바로 종료된다.
이 때 시그널이 발생하여 시그널처리함수가 호출되고 waitpid( )로 자식프로세스의 리턴값을 받는다.
그럼 자식프로세스는 좀비프로세스가 되지 않고 깔끔히 커널을 통해 소멸된다.
그런 후 exit( )를 사용해 부모프로세스도 종료한다.

   1:  //시그널 처리함수
   2:  void z_handler()        
   3:  {
   4:      int state;
   5:   
   6:      waitpid(-1, &state, WNOHANG);
   7:      exit(0);
   8:   
   9:      return ;
  10:  }


서버의 부모프로세스는 확실히 종료되지만 자식프로세스는 어떻게 될지 의문이다.
한 번 아래의 소스코드를 컴파일하여 실행해 보자.
 





● 수정된 talk_server.c

   1:   // 프로세스가 남는 문제가 해결된 talk_server.c
   2:  #include <stdio.h>
   3:  #include <string.h>
   4:  #include <stdlib.h>
   5:  #include <sys/types.h>
   6:  #include <signal.h>
   7:  #include <sys/socket.h>
   8:  #include <sys/wait.h>
   9:  #include <netinet/in.h>
  10:  #include <arpa/inet.h>
  11:  #include <unistd.h>
  12:   
  13:  #define    MAXLINE    512
  14:   
  15:  void z_handler();        //시그널 처리함수
  16:   
  17:  char *escapechar = "exit"; //종료문자열
  18:   
  19:  int main(int argc, char *argv[])
  20:  {
  21:      int server_sock;
  22:      int client_sock;
  23:      int clntlen;
  24:      int num;
  25:      char sendline[MAXLINE];
  26:      char buffer[MAXLINE];
  27:      char recvline[MAXLINE];
  28:      int size;
  29:      pid_t fork_ret;
  30:      struct sockaddr_in client_addr;
  31:      struct sockaddr_in server_addr;
  32:      // signal사용하기 위해 추가된 변수들
  33:      int state;
  34:      struct sigaction act;
  35:   
  36:      if(argc != 2)
  37:      {
  38:          printf("Usage : %s PORT \n", argv[0]);
  39:          exit(0);
  40:      }
  41:      
  42:      // signal설정
  43:      act.sa_handler = z_handler;
  44:      sigemptyset(&act.sa_mask);
  45:      act.sa_flags = 0;
  46:   
  47:      state = sigaction(SIGCHLD, &act, 0);    //자식프로세스 종료시 발생하는 시그널 SIGCHLD이용
  48:   
  49:      if(0 != state)
  50:      {
  51:          fprintf(stderr, "sigaction() error. \n");
  52:          exit(1);
  53:      }
  54:   
  55:      // 소켓 생성
  56:      if((server_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  57:      {
  58:          printf("Server: can't open stream socket. \n");
  59:          exit(0);
  60:      }
  61:   
  62:      // 소켓주소 구조체에 주소 세팅
  63:      bzero((char *)&server_addr, sizeof(server_addr));    //소켓주소 구조체 초기화
  64:      server_addr.sin_family = AF_INET;
  65:      server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  66:      server_addr.sin_port = htons(atoi(argv[1]));
  67:   
  68:      // 소켓에 서버 주소 연결
  69:      if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
  70:      {
  71:          printf("Server: can't bind local address. \n");
  72:          exit(0);
  73:      }
  74:   
  75:      printf("Server started. \nWaiting for client..\n");
  76:      listen(server_sock, 1);
  77:   
  78:      // 클라이언트의 연결요청 수락
  79:      clntlen = sizeof(client_addr);
  80:      if((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &clntlen)) < 0)
  81:      {
  82:          printf("Server: failed in acceptiong. \n");
  83:          exit(0);
  84:      }
  85:   
  86:      fork_ret = fork();
  87:      
  88:      if(0 == fork_ret)
  89:      {
  90:          // 자식 프로세스는 키보드 입력을 클라이언트로 송신
  91:          while(fgets(sendline, MAXLINE, stdin) != NULL)
  92:          {
  93:              strcpy(buffer, "수만 : ");
  94:              strcat(buffer, sendline);
  95:              size = strlen(buffer);
  96:              if(write(client_sock, buffer, strlen(buffer)) != size)
  97:              {
  98:                  printf("Error in write. \n");
  99:              }
 100:   
 101:              if(strstr(sendline, escapechar) != NULL)    //종료문자열 입력시 처리
 102:              {
 103:                  printf("Good bye. \n");
 104:                  close(client_sock);
 105:                  break;                
 106:              }
 107:          }
 108:      }
 109:      else if(0 < fork_ret)
 110:      {
 111:          // 부모 프로세스는 클라이언트로부터 수신된 메시지를 화면에 출력
 112:          while(1)
 113:          {
 114:              if((size = read(client_sock, recvline, MAXLINE)) < 0)
 115:              {
 116:                  printf("Error if read. \n");
 117:                  close(client_sock);
 118:                  exit(0);
 119:              }
 120:   
 121:              recvline[size] = '\0';
 122:   
 123:              if(strstr(recvline, escapechar) != NULL)    //종료문자열 입력시 처리
 124:              {
 125:                  write(client_sock, escapechar, sizeof(escapechar));
 126:                  break;
 127:              }
 128:              
 129:              printf("%s", recvline);        //화면 출력
 130:          }
 131:      }
 132:   
 133:      close(server_sock);
 134:      close(client_sock);
 135:   
 136:      return 0;
 137:  }
 138:   
 139:  //시그널 처리함수
 140:  void z_handler()        
 141:  {
 142:      int state;
 143:   
 144:      waitpid(-1, &state, WNOHANG);
 145:      exit(0);
 146:   
 147:      return ;
 148:  }

 



● 수정된 talk_client.c

   1:  // 프로세스가 남는 문제가 해결된 talk_client.c
   2:  #include <stdio.h>
   3:  #include <string.h>
   4:  #include <stdlib.h>
   5:  #include <sys/types.h>
   6:  #include <signal.h>
   7:  #include <sys/socket.h>
   8:  #include <netinet/in.h>
   9:  #include <arpa/inet.h>
  10:  #include <sys/wait.h>
  11:   
  12:  #define    MAXLINE    1024
  13:   
  14:  void z_handler();        //시그널 처리함수
  15:   
  16:  char *escapechar = "exit"; //종료문자열
  17:   
  18:  int main(int argc, char *argv[])
  19:  {
  20:      char line[MAXLINE];
  21:      char sendline[MAXLINE];
  22:      char recvline[MAXLINE + 1];
  23:      char buffer[MAXLINE];
  24:      int n;
  25:      int size;
  26:      int comp;
  27:      int addr_size;
  28:      pid_t fork_ret;
  29:      static int s;
  30:      static struct sockaddr_in server_addr;
  31:      // signal사용하기 위해 추가된 변수들
  32:      int state;
  33:      struct sigaction act;
  34:      
  35:      if(argc != 3)
  36:      {
  37:          printf("Usage : ./%s serverIP serverPORT \n", argv[0]);
  38:          exit(0);
  39:      }
  40:   
  41:      // signal설정
  42:      act.sa_handler = z_handler;
  43:      sigemptyset(&act.sa_mask);
  44:      act.sa_flags = 0;
  45:   
  46:      state = sigaction(SIGCHLD, &act, 0);    //자식프로세스 종료시 발생하는 시그널 SIGCHLD이용
  47:   
  48:      if(0 != state)
  49:      {
  50:          fprintf(stderr, "sigaction() error. \n");
  51:          exit(1);
  52:      }
  53:      
  54:      // 소켓 생성
  55:      if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  56:      {
  57:          printf("Client: can't open stream socket. \n");
  58:          exit(0);
  59:      }
  60:   
  61:      // 소켓주소 구조체에 주소 세팅
  62:      bzero((char *)&server_addr, sizeof(server_addr));    //소켓주소 구조체 초기화
  63:      server_addr.sin_family = AF_INET;
  64:      server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  65:      server_addr.sin_port = htons(atoi(argv[2]));
  66:   
  67:      // 서버에 연결 요청
  68:      if(connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
  69:      {
  70:          printf("Client: can't connect to server. \n");
  71:          exit(0);
  72:      }
  73:      
  74:      fork_ret = fork();
  75:      
  76:      if(0 == fork_ret)
  77:      {
  78:          // 자식 프로세스는 키보드 입력을 클라이언트로 송신
  79:          while(fgets(sendline, MAXLINE, stdin) != NULL)
  80:          {
  81:              strcpy(buffer, "수만: ");
  82:              strcat(buffer, sendline);
  83:              size = strlen(buffer);
  84:              if(write(s, buffer, strlen(buffer)) != size)
  85:              {
  86:                  printf("Error in write. \n");
  87:              }
  88:   
  89:              if(strstr(sendline, escapechar) != NULL)    //종료문자열 입력시 처리
  90:              {
  91:                  printf("Good bye. \n");
  92:                  close(s);
  93:                  exit(0);
  94:              }
  95:          }
  96:      }
  97:      else if(0 < fork_ret)
  98:      {
  99:          // 부모 프로세스는 클라이언트로부터 수신된 메시지를 화면에 출력
 100:          while(1)
 101:          {
 102:              if((size = read(s, recvline, MAXLINE)) < 0)
 103:              {
 104:                  printf("Error if read. \n");
 105:                  close(s);
 106:                  exit(0);
 107:              }
 108:   
 109:              recvline[size] = '\0';
 110:   
 111:              if(strstr(recvline, escapechar) != NULL)    //종료문자열 입력시 처리
 112:              {
 113:                  write(s, escapechar, sizeof(escapechar));
 114:                  break;
 115:              }
 116:              
 117:              printf("%s", recvline);        //화면 출력
 118:          }
 119:      }
 120:   
 121:      close(s);
 122:   
 123:      return 0;
 124:  }
 125:   
 126:  //시그널 처리함수
 127:  void z_handler()        
 128:  {
 129:      int state;
 130:   
 131:      waitpid(-1, &state, WNOHANG);
 132:      exit(0);
 133:   
 134:      return ;
 135:  }



<실행결과>

image image
                         [스크린샷] 서버                                                             [스크린샷] 클라이언트

클라이언트측에서 먼저 exit를 입력하여 종료했을 때,
잔존 프로세스가 없고 서버도 정상적으로 종료되었다.

exit를 입력하면 클라이언트의 자식프로세스가 먼저 종료되고 동시에 “exit” 이스케이프문자열을 서버로 보낸다.
자식프로세스가 종료되면 SIGCHLD시그널이 발생하여 z_handler( )를 실행하게 된다.
waitpid( )로 자식프로세스의 리턴값(state)을 부모프로세스가 받은 뒤에 exit( )로 종료된다.
이로써 클라이언트는 완전히 종료된다.

서버는 “exit” 이스케이프문자열을 받아 부모프로세스가 종료된다.
자식프로세스는 fgets( )로 계속 stdin(키보드)로 부터 입력을 계속 대기 중인...블로킹상태로 있는데.
어떻게 소멸된 것일까?
시그널이 발생하려면 자식프로세스가 종료되어야 한다고 배웠다.
그런데 여기선 자식프로세스가 죽을 수가 없다.
그러나 실행해 보니 깔끔하게 모두 종료된 것을 볼 수 있다. 어떻게 이런 결과가 나오는지 아직 잘 모르겠다.

image

서버의 자식프로세스는 어떻게 소멸된 것일까?
궁금하다…누군가 댓글을 달아줘~

코드가 잘 못 되었다~

image

소스파일명에 2를 붙여 기존 코드를 다시 실험한 결과..
서버에서 “exit”를 입력받았을 때,
# ps –al 명령으로 프로세스들을 확인하여 보니 클라이언트의 자식프로세스가 살아 있다.

image

Client의 자식프로세스는 fgets( )가 블로킹되어 종료되지 않는다.
부모프로세스는 “exit” 문자열을 수신받아 종료되고 자식프로세스는 부모없는 고아프로세스가 되어..
PID가 1인 init가 부모를 대신해 자식프로세스를 입양한다.
그래서 상기의 스크린샷의 PPID의 값이 1인 것이다.

이 문제에 대한 해결은 다음 보고서에 기록할 것이다. (왜? 포스트 수가 늘어나니까!)