본문 바로가기

Network/중급 TCP/IP socket

04. 부모 자식 프로세서를 이용한 통신

fork()를 이용하여 프로세서를 복사하여 추가로 프로세서를 만들수있다는것을 확인하였습니다
기존의 프로세서를 부모프로세서라고 하면 추가로 생긴 프로세서를 자식 프로세서라고 합니다

초급에서 다루었던 UDP TCP 통신 프로그램의 경우에는 단방향으로 쓰거나 읽는것이 가능했습니다 하지만 fork()를 사용하여 부모 자식 프로세서를 활용하면 좀더 개선된 양방향 통신이 가능합니다

그림참고>>

위 그림에서 처럼 server client 에서 각각 부모 자식을 생성하여 부모에서 자식으로 읽고 쓰는 형태를 만들어 보겠습니다

talk_client.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAXLINE 1024

char *escapechar = "exit";
void z_handler();

int main(int argc, char *argv[])
{
  char line[MAXLINE], sendline[MAXLINE], recvline[MAXLINE+1];
  int n, size, comp, addr_size, state;
  pid_t fork_ret;

  static int s;
  static struct sockaddr_in server_addr;
 
  /* 인자값체크 PORT */

  if(argc != 3)
  {
    printf("Usage : %s serverIP serverPORT \n", argv[0]);
    exit(0);
  }

  /* 소켓생성 */
  if((s = socket(PF_INET, SOCK_STREAM, 0))<0)
  {
    printf("Client : can't open stream socket. \n");
    exit(0);
  }

  /* 소켓주소 구조체에 접속할 서버 주소 세팅 */
  bzero((char*)&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  server_addr.sin_port=htons(atoi(argv[2]));

  /* 서버에 연결 요청 */
  if(connect(s,(struct sockaddr *)&server_addr,sizeof(server_addr))<0)
  {
    printf("Client : can't connect to server. \n");
    exit(0);
  }

  fork_ret = fork();
  
  if(fork_ret == 0)
  {
    /* 자식 프로세스는 키보드 입력을 서버로 송신 */
    while(fgets(sendline,MAXLINE,stdin) != NULL)
    {
      size = strlen(sendline);
      if(write(s, sendline, strlen(sendline)) != size)
        printf("Error in write. \n");
    /* 종료 문자열 입력시 처리 */
      if(strstr(sendline, escapechar) != NULL)
      {
        printf("Good bye. \n");
        close(s);
        exit(0);
      }
    }
  }
  else if(fork_ret > 0)
  {
    /* 부모 프로세스는 서버로 부터 수신된 메시지를 화면에 출력 */
    while(1)
    {
      if((size = read(s,recvline,MAXLINE)) < 0)
      {
        printf("Error if read \n");
        close(s);
        exit(0);
      
      }
      recvline[size] = '\0';
    /* 종료 문자열 입력시 처리 */
      if(strstr(recvline, escapechar) != NULL)
        break;

      printf("%s", recvline); // 화면 출력
    }
  }
  close(s);
  return 0;
}


 talk_server.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAXLINE 512

char *escapechar = "exit";

int main(int argc, char *argv[])
{
  int server_sock, client_sock;
  int clntlen, num, state;
  char sendline[MAXLINE], recvline[MAXLINE+1];
  int size;
  pid_t fork_ret;

  struct sockaddr_in client_addr, server_addr;
 
  if(state != 0)
  {
    printf("sigaction error\n");
    exit(1);
  }
  
  if(argc != 2)
  {
    printf("Usage : %s PORT \n", argv[0]);
    exit(0);
  }

  /* 소켓생성 */
  if((server_sock = socket(PF_INET, SOCK_STREAM, 0))<0)
  {
    printf("Server : can't open stream socket. \n");
    exit(0);
  }

  /* 소켓주소 구조체에 접속할 서버 주소 세팅 */
  bzero((char*)&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port=htons(atoi(argv[1]));

  /* 소켓에 서버 주소 연결 */
  if(bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_addr))<0)
  {
    printf("Server : can't bind to server. \n");
    exit(0);
  }

  printf("Server started.\n Waiting for client..\n");
  listen(server_sock,1);

  /* 클라이언트에 연결요청 수락 */
  clntlen = sizeof(client_addr);
  if((client_sock = accept(server_sock,(struct sockaddr *)&client_addr,&clntlen)) < 0)
  {
    printf("Server : failed in accepting. \n");
    exit(0);
  }
  
  if((fork_ret = fork()) == 0)
  {
    /* 자식 프로세서는 키보드 입력을 클라이언트로로 송신 */
    while(fgets(sendline,MAXLINE,stdin) != NULL)
    {
      size = strlen(sendline);
      if(write(client_sock, sendline, strlen(sendline)) != size)
        printf("Error in write. \n");
    /* 종료 문자열 입력시 처리 */
      if(strstr(sendline, escapechar) != NULL)
      {
        printf("Good bye. \n");
        close(client_sock);
        exit(0);
      }
    }
  }
  else if(fork_ret > 0)
  {
    /* 부모 프로세스는 서버로 부터 수신된 메시지를 화면에 출력 */
    while(1)
    {
      if((size = read(client_sock,recvline,MAXLINE)) < 0)
      {
        printf("Error if read \n");
        close(client_sock);
        exit(0);
      }

      recvline[size] = '\0';
    /* 종료 문자열 입력시 처리 */
      if(strstr(recvline, escapechar) != NULL)
        break;
      printf("%s", recvline); // 화면 출력
    }
  }
  close(server_sock);
  close(client_sock);

  return 0;
}


결과>>

 ./client 127.0.0.1 5000   //실행
안녕하세요 (입력)
반갑습니다 (출력)
exit(종료)
 ./server 5000    //실행
안녕하세요 (출력)
반갑습니다 (입력)
exit(종료)

문제점!!>
메세지는 잘 주고 받지만 종료시 완벽하게 종료 되질 않습니다

ps -a로 확인하면

* ts = server / tc = client
 
종료가 되지 않고 살아 있는 것을 확인 할수있습니다

살아 있는 프로세서를 종료 시키기 위해서
SIGCHLD를 사용하면 됩니다

헤더추가

#include <sys/wait.h>
#include <unistd.h>

main()아래 선언

int state;

act.sa_handler = z_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;

state = sigaction(SIGCHLD, &act, 0);

함수선언

void z_handler()
{
  int state;

  waitpid(-1&state, WNOHANG);
  exit(0);
  return ;
}

exit를 입력시 종료도 잘되고  ps -a입력시 종료되어 나타나지 않는걸 확인 할수 있습니다