멀티플렉싱(multiplexing)>>> 통신분야에서는 (다중화기)

: 지금까지 작성했던 프로그램들은 모두 하나의 단일 채널에서 일어는 입/출력만을 다루었습니다 즉 이전의 모든 버전의 에코 서버는 한번에 하나의 클라이언트 연결만을 처리 하였습니다 하지만 응용프로그램은 여러 채널의 입/출력을 동시에 처리하는 능력을 요구할 때가 자주 있습니다. 예를 들어 동시에 여러 포트를 열어서 에코서버를 할때 서버가 각 소켓을 생성하고 이를 각 포트에 바인딩 한 후 무슨 일이 일어날까를 생각해 봅시다. 기존방식에는 문제점이 발견되는데 서버는 연결을 accept할 준비가 되어있습니다 하지만 어떤 소켓을 선택해야 할지 선택을 하지 못합니다. 아무 서버나 연결하게 된다면 기존의 대기하고 있는 소켓역시 대기가 되어버리는 불편한 상황이 발생합니다. 물론 이러한 문제는 non-bloaking소켓을 이용하여 해결이 가능하지만 그것보다는 특정 소켓의 입/출력이 준비가 될때까지 서버를 bloacking을 하는 것이 좋습니다

 

하지만 단점도 있습니다 정리하자면

: 프로세스 생성에 많은 양의 연산, 메모리 공간 요구. IPC (inner process communcation) 방법도 복잡합니다

 


이런한 일련의 과정들은 가능하게 하는 것이
linux상에서 다시 말하면 unix상에서 제공해주는 select를 사용하면 됩니다 select는 입/출력이 예상되는 소켓의 식별자를 리스트로 명시하고 리스트의 식별자중 준비되어진 식별자를 준비가 되었는지를 반환하여 알리고 blocking이 되지 않을 것이라는 것을 확신하고 진행하게 됩니다

 

 

전반적인 순서는 아래와 같습니다

- 하나의 서버에 여러개의 클라이언트 제어
-
서비스 품질은 멀티프로세스보다 더 떨어지는것 같아보입니다
-
실질적으로 서버와 클라이언트의 통신에서 데이타 송수신은 매우 작다
-
그렇기 때문에 많은 클라이언트에게 서비스 할 수 있습니다.

- 하나의 프로세스가 해당 클라이언트n개 즉...파일 디스크립터를 묶어서
-
관리를 합니다
  
※ 즉 fd_set으로 묶어서 파일 디스크립터정보를 담습니다

 





select()함수>>>
select
함수를 사용하기 위해서 간단한 순서를 확인해보자

참고>>그림


----- select 함수 사용 순서 -------

1. 디스크립터 설정

2. 검사 범위 설정

3. 타임 아웃 설정

4. select 함수 호출

5. 결과 확인

------------------------------

 

 
 

1. 디스크립터 설정


1)
파일 디스크립트 설정

 

fd_set 수형;

0

1

0

1

.........

fd0  fd1  fd2  fd3

 

fd0 : stdin    fd1 : stdout    fd2 : stderr    fd3 : socket

 

변화를 확인할 파일 디스크립터들을 한 묶음으로 모아둔다

 

fd_set 자료형 관련함수

  FD_ZERO(fd_set * fdset);        //fd_set 초기화 함수

  FD_SET(int fd, fd_set * fdset);   //해당 파일디스크립터  fd  1로 셋

  FD_CLR(int fd, fd_set * fdset);   //해당 파일디스크립터  fd  0으로 셋

  FD_ISSET(int fd, fd_set * fdset);  //해당 파일디스크립터  fd  1인지 확인

 

사용방법 예제


2)
검사할 파일 디스크립터의 범위 지정

 - 검사해야할 파일 디스크립터의 개수를 전달

 - 가장 큰 파일 디스크립터 값에 1을 더함(파일 디스크립터 값이 0부터 시작하므로)

 

3)타임 아웃 설정

 - select함수가 blocking 되는 것을 피하기 위해 타임 아웃을 설정함

 

2. select함수 호출

 
헤더파일

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>


원형

int select(int n fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

                                                                                                  리턴값 : 성공시 0  이상... 오류 발생시 -1 리턴
                                                                                 0을 리턴하는 경우에는 타임아웃에 의해 리턴되었음을 의미
                                                                                 0 보다 큰경우는 변경된 파일 디스크립터의 수를 의미한다. 

인자 값 분석>>


n :
검색 대상이 되는 파일 디스크립터의 수

 

readfds : "입력스트림에 변화가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달합니다. 여기서 입력 스트림에 변화가 발생했다는 것은 수신할 데이터가 있다는 뜻

 

writefds : "데이터 전송 시, 블로킹되지 않고 바로 전송이 가능한지" 확인하고자 하는 소켓들의 정보를 전달
 

excepfds : "예외가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달
 

timeout : 함수 호출 후, 무한 대기 상태에 빠지지 않도록 타임-아웃(time-out)을 설정하기 위해 인자를 전달

 

예제>>

select(1,&reads,0,0,5)

<감지>5초동안 감지 수신에 변화가 없는지 감지하고 나와라 리턴값 = 1-

검사 디스크립트 수는 1


위 예제의 select같이 동작하는 프로그램을 작성합니다
 

select.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  fd_set reads, temps;
  int result;

  char message[BUFSIZE];
  int str_len;
  struct timeval timeout;

  FD_ZERO(&reads); //0
으로 초기화
  FD_SET(0,&reads); //
파일 디스크립터 0(stdin) 설정

  /*
  timeout.tv_sec 
= 5;
  timeout.tv_usec 
= 100000;
  */
  //
잘못된 timeout 설정(구조체 내에서만 업데이트된다)
  
  while(1)
  {
    temps = reads;
    
    timeout.tv_sec = 5//
실행후 다시 재설정해야한다
    timeout.tv_usec = 0;
    
    result = select(1&temps, 00&timeout);
    if(result == -1)
    {
      puts("select(): 
오류발생");
      exit(1);
    }
    else if(result == 0)
    {
      puts("select():
시간이 초과 되었습니다. ");
    }
    else
    {
      if(FD_ISSET(0,&temps))
      {
        str_len = read(0, message, BUFSIZE);
        message[str_len] = 0;
        fputs(message, stdout);
      }
    }
  }
  return 0;
}

 

결과>> 5초간 입력이 없을 경우 시간이 초과되었다는 경고 메시지가 출력됩니다

 

분석해보기>>
 
1)FD_ZERO(&reads); //0으로 초기화
    FD_SET(0,&reads); //
파일 디스크립터 0(stdin) 설정

1

0

0

0

.........

 

마지막 파일 디스크립터에 +1되게 되어있다?

 

1

0

0

1

.........

 

result = select(1&temps, 00&timeout);

수신데이터가 있는지 확인합니다 <5초설정>

확인할 파일디스크립트는 1개입니다

입력이 되었다면 result = 1



2)else
 if(result == 0) 5
초간 아무 변화가 없을때

 

0

0

0

0

.........


3)temps 
= reads;
원본값으로 초기화 시켜주질 않을 경우

 while문을 한바퀴 돌고나면

1

0

0

0

.........

에서

0

0

0

0

.........

으로 변화되고 이후로는 계속 0인 상태가 됩니다

 

select사용시에는 원본을 저장 할 수 있는 변수를 써야 합니다


3. 결과 확인


참고>>
select()함수의 인자를 보면 이 중 n은 검사가 필요없는 가장 작은 식별자값으로써 최대 식별자  값보다 1이 작다 위에서 보면 소켓셋에 담을 수 있는 소켓 디스크립터의 최대갯수는 시스템 정의 상수인 FD_SETSIZE로 정의 되어 있지만 그 수가 상당히 크므로 매번 그 크기만큼 검사하면 비효율적이므로 이를 효율적으로 검사하기 위해서 정수 n을 전달하여 그 크기+1까지만 검사한다

 그 다음에는 각 소켓셋(읽기셋, 쓰기셋, 예외셋)이 파라미터로 들어가며 만약 NULL이 들어가면 그 소켓셋은 대상 리스트에 대한 입/출력 감시를 하지 않는다
 마지막 파라미터인 timeout은 NULL로 설정하면 읽기셋, 쓰기셋, 예외셋에 삽입한 소켓 중에 변화가 생길 때까지 대기하고 있다가 변화가 발생한 소켓의 수를 리턴하게 된다.

timeout을 양수로 설정한 경우에는 변화가 발생한 소켓이 생길 때까지 대기하고 있다가 설정한 시간이 되면 변화가 발생한 소켓이 없더라도 대기상태를 해제하게 된다.


 이때 변화가 발생한 소켓이 없다면 0을 리턴하게 된다. 그리고 timeout 값이 0으로 설정되면 대기시간 없이 바로 리턴하게 된다


Posted by mantwo
멀티 프로세스 이용시 문제점
: 프로세스는 메모리가 독립적으로 존재하기 때문에 메모리를 프로세스간 프로세스간 데이터를 주고 받는 것은 불가능합니다. 쉽게 설명하자면 fork()이용 하여 server 혹은 client 쪽에서 생성된 부모 자식 프로세스 들은 프로그램내 프로세스끼리는 데이터를 주고 받을 방법이 없습니다.

해결책
: 서로 독립된 프로세스들이 데이터를 주고 받기 위해 운영체제는 '파이프'라는 함수를 제공하고 있습니다

pipe() 파이프 생성 함수
-하나의 파이프 및 파이프에 대한 두 개의 파일 디스크립터가 생성
-하나의 파이프를 프로세스들이 공유

#include <unistd.h>
 int pipe(int fd[2]);
성공시 0, 실패시 -1 리턴

-fd : 크기가 2인 int형 배열을 요구
-fd[0] : 함수 호출 후 fd[0]에 데이터를 입력 받을 수 있는 파일 디스크립터가 담김(파이프출구)
-fd[1] : 함수 호출 후 데이터를 출력할 수 있는 파일 디스크립터가 담김(파이프 입구)

 


간단하게 사용해보도록 하겠습니다

pipe1.c 프로그램 작성 예제

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  int fd[2];
  char buffer[BUFSIZE];
  pid_t pid;
  int state;

  state = pipe(fd);
  if(state == -1)
  {
    puts("pipe() error");
    exit(1);
  }

  pid = fork();

  if(pid == -1)
  {
    puts("fork() error");
    exit(1);
  }
  else if(pid == 0)
  {
    write(fd[1], "연결성공!!\n"25);
  }
  else
  {
    read(fd[0],buffer, BUFSIZE);
    puts(buffer);
  }
  return 0;
}

결과>>

pipe1.c의 동작은 아래 그림과 같습니다. 자식프로세스에서 fd[1]으로 write를 하게되면 부모 프로세스에서 fd[0]으로 read를 받아 buffer에 저장하게 됩니다


파이프의 특징
-파이프 자체는 fork함수에 의해 복사되지 않습니다
-파이프는 방향성이 존재하지 않습니다

 



그러면 이번에는 자식에서 write를 해서 부모에서 read하고 다시 write해서 자식프로세서에서 read하는 프로그램을 작성하겠습니다 위의 특징과 비교하여 설명하자면 pipe함수는 fork함수에 의해 2개 3개 로 늘어나지 않고 하나만 존재합니다 그리고 출구와 입구가 지정되어 있어써 출구로 데이터가 들어갈수 없습니다 양쪽데이터가 왔다 갔다 하는 방향성이 존재하지 않습니다

pipe2.c 프로그램 작성예제
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  int fd[2];
  char buffer[BUFSIZE];
  pid_t pid;
  int state;

  state = pipe(fd);
  if(state == -1)
  {
    puts("pipe() error");
    exit(1);
  }

  pid = fork();

  if(pid == -1)
  {
    puts("fork() error");
    exit(1);
  }
  else if(pid == 0)
  {
    write(fd[1], "연결성공!!\n"25);
    sleep(2);
    read(fd[0], buffer, BUFSIZE);
    printf("Output of child process : %s \n\n", buffer);
  }
  else
  {
    read(fd[0],buffer, BUFSIZE);
    printf("Output of parent process : %s \n\n", buffer);
    write(fd[1], "정말 좋아!!"25);
    sleep(2);
  }
  return 0;
}


결과>>


참고>>
위의 프로그램에서 sleep을 해준이유는 write하고 read하기까지 딜레이를 주기위해서 입니다 sleep 없으면 컴퓨터는 대단히 빨리 처리하게되어 파이프로 가기도전에 해당프로세스에서 wrire read를 한번에 처리해 버리게됩니다!

양방향통신을 윈한 파이프 생성
-하나의 파이프를 하나의 용도로 사용하도록 합니다
 : A프로세스에서 B프로세스로 데이터를 전송하기 위한 파이프 하나
 : B프로세스에서 A프로세스로 데이터를 전송하기 위한 파이프 하나


pipe3.c 프로그램 작성 예제
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  int fd1[2], fd2[2];
  char buffer[BUFSIZE];
  pid_t pid;
  
  if(pipe(fd1) == -1||pipe(fd2) == -1)
  {
    puts("pipe() error");
    exit(1);
  }

  pid = fork();

  if(pid == -1)
  {
    puts("fork() error");
    exit(1);
  }
  else if(pid == 0)
  {
    write(fd1[1], "연결성공!!\n"25);
    read(fd2[0], buffer, BUFSIZE);
    printf("Output of child process : %s \n\n", buffer);
  }
  else
  {
    read(fd1[0],buffer, BUFSIZE);
    printf("Output of parent process : %s \n\n", buffer);
    write(fd2[1], "정말 좋아!!"25);
    sleep(1);
  }
  return 0;
}


결과>>


Posted by mantwo

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입력시 종료되어 나타나지 않는걸 확인 할수 있습니다
 




 

Posted by mantwo

fork()함수


프로그램내 동작하고 있는 프로세스를 복사하고 복사한 프로세스를 독립적으로 돌려주는 함수입니다 흔히 원복 프로세서를 부모프로세스라 부르고 복사한 프로세스를 자식프로세스라 부릅니다
.

 

헤더    unistd.h

형태    pid_t fork(void); 

반환    pid_t 실행에 실패하면 -1 을 반환. 부모에게는 새로 생성된 자식 프로세스 PID가 반환되며, 자식 프로세스에는 0이 반환됩니다.

 

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

int global_var = 0;

int main()
{
  pid_t ret_pid, child;
  int local_var = 0;

  ret_pid = fork();
  if(ret_pid < 0)
  {
    printf("fork() error\n");
    return -100;
  }
  else if(ret_pid == 0)
  {
    global_var++;
    local_var++;
    printf("CHILD - my PID : %d parents PID : %d \n", getpid(),getppid());
  }
  else
  {
    sleep(10);

    global_var += 5;
    local_var += 5;
    printf("PARENT - my pid : %d chail's PID : %d\n", getpid(),ret_pid);
  }
  printf("\t global_var : %d \n",global_var);
  printf("\t local_var : %d \n",local_var);
  return 0;
}


 

결과>>


 

좀비 프로세스

-  프로세스 종료 후 메모리상에서 사라지지 않은 프로세스

 

좀비 프로세스 생성 이유

-  자식  프로세스가  종료하면서  반환된    0  커널이  부모  프로세스에  전달한    자식

프로세스를 소멸시킴.  

-  반환값을 부모 프로세스에 전달하지 못한 경우 자식 프로세스는 좀비로 존재.

-  부모 프로세스가 커널에게 종료된 자식 프로세스의 리턴값을 전달 요청을 해야만  

커널이 리턴값 전달 가능함

 

종료된 자식 프로세스의 리턴값 요청 함수
 
1. wait 함수
 
#include <sys/types.h>
#include <sys/wait.h>
 
pid_t wait(int * status);
성공시 종료된 자식 프로세스  ID, 실패시  -1 리턴
 
-  status : 포인터  status가 가리키는 변수에 자식 프로세스 종료시 리턴하거나  
exit 함수 호출시 전달한 인자값이 저장됨
-  매크로함수 WEXITSTATUS(status)로 종료시 리턴값이나  exit() 인자로 넘겨진 값 확인
-  호출 시점에 종료된 자식 프로세스가 없으면  blocking 상태에 빠짐
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int global_var = 0;

int main()
{
  pid_t ret_pid, child;
  int local_var = 0;
  int state;

  ret_pid = fork();
  if(ret_pid < 0)
  {
    printf("fork() error\n");
    return -100;
  }
  else if(ret_pid == 0)
  {
    global_var++;
    local_var++;
    printf("CHILD - my PID : %d parents PID : %d \n", getpid(),getppid());
  }
  else
  {
    global_var += 5;
    local_var += 5;
    printf("PARENT - my pid : %d chail's PID : %d\n", getpid(),ret_pid);
    
    child = wait(&state);
    printf("\t Child PID = %d \n", child);
    printf("\t return value = %d \n", WEXITSTATUS(state));
  
    sleep(10);
  }

  printf("\t global_var : %d \n",global_var);
  printf("\t local_var : %d \n",local_var);
  
  return 0;
}


 

결과>>


 

 2. waitpid 함수  (wait함수의  blocking 상태 해결)

 

#include <sys/types.h>

#include <sys/wait.h>

 

pid_t waitpid(pid_t pid, int * status, int options);

성공시 종료된 자식 프로세스ID(경우에 따라 0), 실패시  -1 리턴

 

-  pid :   종료 확인을 원하는 자식 프로세스  ID. 임의의 자식 프로세스인 경우  -1 대입.

-  status :  wait 함수의  status와 같은 역할

-  options :  sys/wait.h에 정의된  WNOHANG’ 상수를 인자로 전달하게 되면 이미 종료한

          자식 프로세스가 없는 경우 blocking 상태로 가지않고 바로 리턴함.

       이때 waitpid 함수의 리턴값은  0이 된다.

  

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

int global_var = 0;

int main()
{
  pid_t fork_ret, child;
  int local_var = 0;
  int state;

  fork_ret = fork();
  if(fork_ret < 0)
  {
    printf("fork() error\n");
    exit(1);
  }
  else if(fork_ret == 0)
  {
    global_var++;
    local_var++;
    printf(" CHILD - my PID : %d parent's PID : %d \n", getpid(),getppid());
    sleep(10);
  }
  else
  {
    global_var += 5;
    local_var += 5;
    printf("PARENT - my PID : %d child's PID : %d \n", getpid(),fork_ret);

    do
    {
      sleep(3);
      puts("3초 대기");

      child = waitpid(-1&state, WNOHANG);
    }
    while(child == 0);

    printf("\t 종료된 자식 프로세스 ID = %d \n", child);
    printf("\t 종료된 자식 프로세스의 리터 값 = %d \n", WEXITSTATUS(state));
  }
  printf("\t global_var : %d \n", global_var);
  printf("\t local_var : %d \n", local_var);
  
  return 0;

}


 

 결과>>

 

waitpid 함수의 호출 시점은??

-  자식 프로세스가 종료되는 순간 부모 프로세스가 waitpid 함수를 호출하도록 해야함

-  자식 프로세스가 종료된 시점에 발생하는 시그널  SIGCHLD를 이용

-  sigaction 함수를 사용하여  SIGCHLD가 발생시 부모 프로세스가 자식 프로세스의 리턴

값을 읽도록 함

 


좀비 프로세스확인 

 

 결과>> wait함수 실행시 ps -u를 입력하여 확인합니다


STAT의 Z가 zombie를 뜻합니다


STAT에 Z가 없는 것을 확인 할 수 있습니다



시그널(SIGCHLD)의 이용

SIGCHLD : (시그널무시)자식프로세서 종료

sigemptyset() 주어진 마스크 셋의 모든 필드를 설정하거나 헤제한다

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

void z_handler();

int global_var = 0;

int main()
{
  pid_t fork_ret, child;
  int local_var = 0;
  int state;

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

  state = sigaction(SIGCHLD, &act,0);
  if(state != 0)
  {
    puts("sigaction() error\n");
    exit(1);
  }
  
  fork_ret = fork();

  if(fork_ret < 0)
  {
    printf("fork() error\n");
    exit(1);
  }
  else if(fork_ret == 0)
  {
    global_var++;
    local_var++;
    printf(" CHILD - my PID : %d parent's PID : %d \n", getpid(),getppid());
  }
  else
  {
    global_var += 5;
    local_var += 5;
    printf("PARENT - my PID : %d child's PID : %d \n", getpid(),fork_ret);
    
    sleep(5);
  }
  

  printf("\t global_var : %d \n", global_var);
  printf("\t local_var : %d \n", local_var);
  
  return 0;

}

void z_handler()
{
  pid_t child;
  int state;
  
  child = waitpid(-1&state, WNOHANG);

  printf("\t 소멸된 자식 프로세스 ID = %d \n", child);
  printf("\t 소멸된 자식 프로세스의 리턴 값 = %d \n", WEXITSTATUS(state));
}

 

결과>>

 
**정상적으로 자식프로세스가 종료된것을 확인 할 수있습니다

Posted by mantwo

시그널(signal) : 어떤 이벤트(인터럽트, 타이머종료)가 발생 했을 때 운영체제가 프로그램에 이를 알리는 기법을 뜻합니다

 

현재 실행되고 있는 프로그램에 시그널이 전달되었을시 4가지 상황이 발생합니다

 

1. (운영체제의 의해) 시그널이 무시된다. 프로세서는 시그널이 도착한 것을 알지 못한다

2. 운영체제는 프로그램을 강제로 종료한다

3. 프로그램 실행이 인터럽트 되면 이후에 프로그램이 지정한 시그널 처리 루틴이 실행

4. 시그널이 블로킹된다 프로그램이 시그널을 허용 할 때 까지 아무런영향을 미치지 못한다 해당프로세서에서는 어떠시그널이 블록되었는지 마스크를 가지고 있다

 

종류

이벤트

기본동작

SIGALARM

알람 타이머의 만료

프로그램 종료

SIGCHLD

자식 프로세서가 종료됨

시그널 무시

SIGINT

인터럽트 문자(Ctrl C)입력됨

프로그램 종료

SIGIO

소켓에 대해 I/O 가 준비됨

시그널 무시

SIGPIPE

종료된 소켓에 쓰기를 시도할때

프로그램 종료

 

시그널은 상당히 복잡해서 전채를 다 아는 것은 어렵습니다 몇가지 시그널이 소켓에 자주 등장하여서 아주 중요합니다!!

sigaction
구조체를 이용한  sigaction 함수 그리고 원형 

 

#include <signal.h> 
 
int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact); 
성공시 0실패   -1 리턴 
 
struct
 sigaction 
    { 
           void (*sa_handler)(int)  //
시그널 처리할 함수 
           sigset_t sa_mask; //
시그널처리함수가 실행되는 동안 블로킹될 시그널을 설정 
           int sa_flags;            //
옵션 설정 
    }


참고>>
시그널은 큐(queue)에 대기 되지 않고 계류 되거나 또는 버려지거나 둘중하나입니다 만약 시그널이 처리되는 도중에 똑 같은 종류의 시그널이 두 개 이상 도착한다면 시그널 핸들러는 기존에 진행하던 핸들러를 마치고 한번더 실행하게 됩니다

 
SIGINT를 활용하여 Ctrl -c 입력시 종료하는 프로그램

/* sigaction.c */

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int my_signal();  //새로운 시그널 처리함수 선언
int count = 0;     //Ctrl-C 입력 횟수 카운터

int main()
{
  int i = 0;

  struct sigaction act;         //sigaction 구조체 변수
  act.sa_handler = my_signal;   //시그널 처리함수 지정
  sigemptyset(&act.sa_mask);    //sm_mask의 모든 비트를 0으로 설정
  act.sa_flags = 0;

  if(sigaction(SIGINT, &act, 0== SIG_ERR)
  {
    printf("sigaction() error \n");
    exit(1);
  }

  while(count < 3)
  {
    sleep(1);   //1초간 대기
    printf("%d \n", i++);
  }
  return 0;
}

int my_signal()
{
  printf("\nCtrl-C pressed \n");
  count++;

  return 0;
}


결과>>


Ctrl - C을 3번 눌렀을 경우 종료되는 것을 확인 할 수 있습니다.


SIGALARM을 활용하여 정해진 시간에 종료하는 프로그램
/* sigtimer.c */

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

void timer(int sig);

int main()
{
  int i = 0;

  struct sigaction act;         //sigaction 구조체 변수
  act.sa_handler = timer;   //시그널 처리함수 지정
  sigemptyset(&act.sa_mask);    //sm_mask의 모든 비트를 0으로 설정
  act.sa_flags = 0;

  if(sigaction(SIGALRM, &act,0)  == SIG_ERR)
  {
    puts("sigaction() error \n");
    exit(1);
  }

  alarm(5);
  
  while(1)
  {
    puts("대기중");
    sleep(1);   
  }
  return 0;
}

void timer(int sig)
{
  printf("\n시간이 되었습니다 종료합니다\n");
  exit(0);
}



결과>>


5초후(대기중 1초) 프로그램이 종료되는 것을 확인 할 수 있습니다
Posted by mantwo

소켓옵션 소켓동작 자체에 관련된 여러가지 옵션의 값을 조정할수 있고 바꿀수 있습니다

소켓옵션을 이용하여 특히 소켓 설정 정보를 읽어오는 getsockopt를 이용하여 TCP / UDP타입을 확인하여 봅시다

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char * message);

int main()
{
  int tcp_sock, udp_sock;
  int sock_type = -1;
  socklen_t optlen;
  int state;
  
  optlen = sizeof(sock_type);
  tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
  udp_sock = socket(PF_INET, SOCK_DGRAM, 0);

  printf("SOCK_STREAM : %d \n", SOCK_STREAM);
  printf("SOCK_DGRAM : %d \n", SOCK_DGRAM);
  
  state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, &sock_type, &optlen);
  if(state)
    error_handling("getsockopt() error");
  printf("첫번째 소켓의 타입은 %d\n", sock_type);
  
  
  state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, &sock_type, &optlen);
  if(state)
    error_handling("getsockopt() error");
  printf("두번째 소켓의 타입은 %d\n", sock_type);
  return 0;
  
}
void error_handling(char * message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);  
}


결과>>


소켓의 다양한 옵션

 

getsockopt / setsockopt

 

1. getsockopt 소켓 설정 정보(옵션의값) 읽어오는 함수

함수는 특정한 형태(type), 특정한 상태(state)로 연관 되어있는 소켓옵션에 대해 설정되어 있는 값을 optval 매개변수에 저장해서 얻어내는 함수입니다.

 

#include <sys/types.h>

#include <sys/socket.h>

 

int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);

성공시 0, 실패 시  -1 리턴

 

예제소스

getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &len);

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char *message);

int main()
{
  int sock;
  int snd_buf, rcv_buf;
  int state;
  socklen_t  len;

  sock = socket(PF_INET, SOCK_STREAM,0);
  
  len = sizeof(snd_buf);
  state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf, &len);
  if(state)
    error_handling("getsockopt() error ");

  len = sizeof(rcv_buf);
  state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf, &len);
  if(state)
    error_handling("getsockopt() error ");

  printf("데이터 입력받기 위한 소켓의 버퍼 크기 : %d(수신버퍼) \n", rcv_buf);
  printf("데이터 출력받기 위한 소켓의 버퍼 크기 : %d(송신버퍼) \n", snd_buf);

  return 0;
}

void error_handling(char * message)
{
  fputs(message,stderr);
  fputc('\n', stderr);
  exit(1);
}


결과>>


2. setsockopt 소켓 옵션을 변경(설정)하는 함수

함수는 지정된 소켓의 옵션을 특정한 형태(type), 상태(state)로 결합하기 위해서 지정한 소켓옵션 값을 셋팅하는 함수입니다.

 

#include <sys/types.h>

#include <sys/socket.h>

 

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

성공시 0, 실패 시  -1 리턴 

 

예제소스

setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf,sizeof(rcv_buf));


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char *message);

int main()
{
  int sock;
  int snd_buf = 500;
  int rcv_buf = 1000;

  int state;
  socklen_t  len;

  sock = socket(PF_INET, SOCK_STREAM,0);
  /*입출력 버퍼 크기 설정*/
  state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &rcv_buf,sizeof(rcv_buf));
  if(state)
    error_handling("setsockopt() error ");

  state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &snd_buf,sizeof(snd_buf));
  if(state)
    error_handling("setsockopt() error ");

  /*입출력 버퍼 크기 확인*/  
  len = sizeof(rcv_buf);
  state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, &rcv_buf, &len);
  if(state)
    error_handling("getsockopt() error ");

  len = sizeof(snd_buf);
  state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &snd_buf, &len);
  if(state)
    error_handling("getsockopt() error ");

  printf("데이터 입력받기 위한 소켓의 버퍼 크기 : %d(수신버퍼) \n", rcv_buf);
  printf("데이터 출력받기 위한 소켓의 버퍼 크기 : %d(송신버퍼) \n", snd_buf);

  return 0;
}

void error_handling(char * message)
{
  fputs(message,stderr);
  fputc('\n', stderr);
  exit(1);
}


결과>>


참고
snd와 rcv에 각각 500 1000의 값을 넣었지만 적용되지 않았습니다
이 경우에는 사용자가 시스템에게 '이값을 원한다'라고 알려주는 힌트정도로 생각하면 됩니다
리소르의 관리는 시스템의 몫이며 버퍼 크기 조정시 다른부분도 고려를 하기 때문에 이러한 결과가 나오게 됩니다

getsockopt / setsockopt의 인자  설명 

int socket
   [입력] 작업 대상 소켓의 기술자(descriptor)를 명시합니다.

int level
   [
입력] 소켓 옵션 레벨이 정의 되며, SOL_SOCKET IPPROTO_TCP 중 하나가 될 수 있습니다.

int optname
   [
입력]이 함수에 의해서 반환될(검색될) 값에대한 소켓 옵션을 명시합니다.

voif *optval
   [
출력] 요청된 옵션에 대한 반환되는 옵션값이 저장될 버퍼에 대한 포인터 입니다.

sockeln_t *optlen
   [
/출력] optval 버퍼의 크기를 나타내는 정수로 포인트 합니다.


 

 int optname 종류 및 설명

옵션값(Value)

데이터형태(Type)

의미(Meaning)

SO_ACCEPTCONN

BOOL

TRUE 일 경우 소켓이 리슨 하고 있습니다.

SO_BROADCAST
 

BOOL
 

TRUE 일 경우 소켓은 브로드캐스트(broadcast) 메시지를

전송하기 위해 만들어졌습니다.

SO_DEBUG

BOOL

TRUE 일 경우 디버깅이 가능합니다.

SO_DONTLINGER

BOOL

TRUE 일 경우 SO_LINGER 옵션은 무시됩니다.

SO_DONTROUTE

BOOL

TRUE 일 경우 라우팅이 무시됩니다.

SO_ERROR

int

에러상태를 반환하고, 클리어 됩니다.

SO_GROUP_ID

GROUP

소켓이 속해있는 그룹 식별자를 의미합니다.

SO_GROUP_PRIORITY
 

int
 

소켓에 관련된 우선사항은 소켓 그룹의 부분 입니다.

SO_KEEPALIVE

BOOL

TRUE 일 경우 keepalive가 전송되고 있습니다.

SO_LINGER

struct LINGER

현재의 linger 옵션을 반환 합니다.

SO_MAX_MSG_SIZE
 
 

unsigned int
 
 

SOCK_DGRAM 과 같은 소켓 형태에서 메시지의 최대 크기입니다. 스트림 소켓에서는 아무런 의미가 없습니다.

SO_OOBINLINE
 

BOOL
 

TRUE 일 경우 아웃오브밴드 데이터가 정상적인 데이터 스트림으로 수신되고 있습니다.

SO_PROTOCOL_INFO
 

WSAPROTOCOL_INFO
 

소켓에 대해 바인드된 프로토콜 정보를 나타냅니다.

SO_RCVBUF

int

수신 버퍼크기를 의미합니다.

SO_REUSEADDR
 

BOOL
 

TRUE일 경우 소켓은 이미 사용중인 어드레스로 묶였습니다.

SO_SNDBUF

int

전송 버퍼의 크기

SO_TYPE
 

int
 

소켓의 타입 (:SOCK_STREAM, SOCK_DGRAM)

 

 


Posted by mantwo