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



● fork( )를 이용해 서버와 클라이언트에 각 자식프로세스를 생성.

image 

서버와 클라이언트의 각 부모프로세스들은 send( )또는 write( )를 사용해 상대방에게 전송한다.
                             자식프로세스들은 recv( ) 또는 read( )를 사용해 상대방이 전송한 메세지를 수신한다.
프로세스가 따로 돌아가고 있으므로 하나의 프로세스로 통신을 할 때와 다르게,
수신과 송신을 동시에 할 수 있다.




image 

부모프로세스와 자식프로세스는 서로 소켓을 공유한다.



● 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 <netinet/in.h>
   9:  #include <arpa/inet.h>
  10:  #include <unistd.h>
  11:   
  12:  #define    MAXLINE    512
  13:   
  14:  char *escapechar = "exit"; //종료문자열
  15:   
  16:  int main(int argc, char *argv[])
  17:  {
  18:      int server_sock;
  19:      int client_sock;
  20:      int clntlen;
  21:      int num;
  22:      char sendline[MAXLINE];
  23:      char buffer[MAXLINE];
  24:      char recvline[MAXLINE];
  25:      int size;
  26:      pid_t fork_ret;
  27:      struct sockaddr_in client_addr;
  28:      struct sockaddr_in server_addr;
  29:   
  30:      if(argc != 2)
  31:      {
  32:          printf("Usage : %s PORT \n", argv[0]);
  33:          exit(0);
  34:      }
  35:   
  36:      // 소켓 생성
  37:      if((server_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  38:      {
  39:          printf("Server: can't open stream socket. \n");
  40:          exit(0);
  41:      }
  42:   
  43:      // 소켓주소 구조체에 주소 세팅
  44:      bzero((char *)&server_addr, sizeof(server_addr));    //소켓주소 구조체 초기화
  45:      server_addr.sin_family = AF_INET;
  46:      server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  47:      server_addr.sin_port = htons(atoi(argv[1]));
  48:   
  49:      // 소켓에 서버 주소 연결
  50:      if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
  51:      {
  52:          printf("Server: can't bind local address. \n");
  53:          exit(0);
  54:      }
  55:   
  56:      printf("Server started. \nWaiting for client..\n");
  57:      listen(server_sock, 1);
  58:   
  59:      // 클라이언트의 연결요청 수락
  60:      clntlen = sizeof(client_addr);
  61:      if((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &clntlen)) < 0)
  62:      {
  63:          printf("Server: failed in acceptiong. \n");
  64:          exit(0);
  65:      }
  66:   
  67:      if((fork_ret = fork()) > 0)
  68:      {
  69:          // 부모 프로세스는 키보드 입력을 클라이언트로 송신
  70:          while(fgets(sendline, MAXLINE, stdin) != NULL)
  71:          {
  72:              strcpy(buffer, "수만 : ");
  73:              strcat(buffer, sendline);
  74:              size = strlen(buffer);
  75:              if(write(client_sock, buffer, strlen(buffer)) != size)
  76:              {
  77:                  printf("Error in write. \n");
  78:              }
  79:   
  80:              if(strstr(sendline, escapechar) != NULL)    //종료문자열 입력시 처리
  81:              {
  82:                  printf("Good bye. \n");
  83:                  close(client_sock);
  84:                  exit(0);
  85:              }
  86:          }
  87:      }
  88:      else if(0 == fork_ret)
  89:      {
  90:          // 자식 프로세스는 클라이언트로부터 수신된 메시지를 화면에 출력
  91:          while(1)
  92:          {
  93:              if((size = read(client_sock, recvline, MAXLINE)) < 0)
  94:              {
  95:                  printf("Error if read. \n");
  96:                  close(client_sock);
  97:                  exit(0);
  98:              }
  99:   
 100:              recvline[size] = '\0';
 101:   
 102:              if(strstr(recvline, escapechar) != NULL)    //종료문자열 입력시 처리
 103:              {
 104:                  break;
 105:              }
 106:              
 107:              printf("%s", recvline);        //화면 출력
 108:          }
 109:      }
 110:   
 111:      close(server_sock);
 112:      close(client_sock);
 113:   
 114:      return 0;
 115:  }




● 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:   
  11:  #define    MAXLINE    1024
  12:   
  13:  char *escapechar = "exit"; //종료문자열
  14:   
  15:  int main(int argc, char *argv[])
  16:  {
  17:      char line[MAXLINE];
  18:      char sendline[MAXLINE];
  19:      char recvline[MAXLINE + 1];
  20:      char buffer[MAXLINE];
  21:      int n;
  22:      int size;
  23:      int comp;
  24:      int addr_size;
  25:      pid_t fork_ret;
  26:      static int s;
  27:      static struct sockaddr_in server_addr;
  28:   
  29:      if(argc != 3)
  30:      {
  31:          printf("Usage : ./%s serverIP serverPORT \n", argv[0]);
  32:          exit(0);
  33:      }
  34:   
  35:      // 소켓 생성
  36:      if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  37:      {
  38:          printf("Client: can't open stream socket. \n");
  39:          exit(0);
  40:      }
  41:   
  42:      // 소켓주소 구조체에 주소 세팅
  43:      bzero((char *)&server_addr, sizeof(server_addr));    //소켓주소 구조체 초기화
  44:      server_addr.sin_family = AF_INET;
  45:      server_addr.sin_addr.s_addr = inet_addr(argv[1]);
  46:      server_addr.sin_port = htons(atoi(argv[2]));
  47:   
  48:      // 서버에 연결 요청
  49:      if(connect(s, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
  50:      {
  51:          printf("Client: can't connect to server. \n");
  52:          exit(0);
  53:      }
  54:      
  55:      fork_ret = fork();
  56:      
  57:      if(fork_ret > 0)
  58:      {
  59:          // 부모 프로세스는 키보드 입력을 클라이언트로 송신
  60:          while(fgets(sendline, MAXLINE, stdin) != NULL)
  61:          {
  62:              strcpy(buffer, "수만: ");
  63:              strcat(buffer, sendline);
  64:              size = strlen(buffer);
  65:              if(write(s, buffer, strlen(buffer)) != size)
  66:              {
  67:                  printf("Error in write. \n");
  68:              }
  69:   
  70:              if(strstr(sendline, escapechar) != NULL)    //종료문자열 입력시 처리
  71:              {
  72:                  printf("Good bye. \n");
  73:                  close(s);
  74:                  exit(0);
  75:              }
  76:          }
  77:      }
  78:      else if(0 == fork_ret)
  79:      {
  80:          // 자식 프로세스는 클라이언트로부터 수신된 메시지를 화면에 출력
  81:          while(1)
  82:          {
  83:              if((size = read(s, recvline, MAXLINE)) < 0)
  84:              {
  85:                  printf("Error if read. \n");
  86:                  close(s);
  87:                  exit(0);
  88:              }
  89:   
  90:              recvline[size] = '\0';
  91:   
  92:              if(strstr(recvline, escapechar) != NULL)    //종료문자열 입력시 처리
  93:              {
  94:                  break;
  95:              }
  96:              
  97:              printf("%s", recvline);        //화면 출력
  98:          }
  99:      }
 100:   
 101:      close(s);
 102:   
 103:      return 0;
 104:  }


<실행결과>

imageimage
[스크린샷] 서버와 클라이언트의 대화내용 –_—;

이 TCP서버&클라이언트 소스코드의 문제점은,
① 내가 치고 있는 동안 상대방이 메시지를 보내면 내가 보낼 문자열 뒤에 수신문자열이 붙어 혼란스럽다.
② 1 대 1 접속밖에 되지 않는다.
③ 서버 & 클라이언트 모두 자식프로세스가 종료되지 않아 메시지 수신을 계속한다.

①번과 ②번은 조금 불편한 수준에 그치지만 ③은 조금 치명적인 에러가 아닌가 생각된다.

image

이와 같이 프로세스가 종료되지 않아 자원을 차지하고 있어 낭비이고,
메시지를 계속 수신하니 그 또한 문제이다.
프로세스가 실행 중이라는 사실을 모른체 계속 클라이언트나 서버를 실행하면,

image

상기와 같이 쓸데없이 자원을 차지하는 프로세스가 계속 불어난다.

문제해결은 집에 가서 작성하겠음. 바쁘다 바뻐 >_<”