[minitalk] Inter-Process Communication

24siefil·2022년 4월 2일
0

42 SEOUL

목록 보기
14/18
post-thumbnail

Created: June 17, 2021 1:04 AM

minitalk 프로젝트는 Inter-Process Communication을 활용하여 간단한 Server, Client 프로그램을 구현하는 것이다. 본 페이지는 minitalk 프로젝트를 위한 선행지식인 Signal, Sigaction 함수, UTF 등에 대해 정리한 것이다.

1. Signal 함수


signal은 UNIX, POSIX 호환 운영체제의 IPC(프로세스 간 통신)에서 쓰이는 방법 중 하나이며, 프로세스에게 직접적으로 어떤 의미를 전달할 수 있는 신호체계이다. IPC는 프로세스들 사이에서 서로 데이터를 주고받는 행위나 체계를 의미한다. (Windows의 경우 <signal.h>에서 정의하는 기본적인 6종의 시그널 외에는 지원하지 않는다.

Kill*

#include <signal.h>

int kill(pid_t pid, int sig); // 성공시 0, 실패시 -1 리턴

특정 프로세스 인식자(pid)에 등록되어 있는 프로세스에게 지정한 시그널을 보낸다. pid 유형에 따라 시그널을 보낼 프로세스 대상을 다르게 설정할 수 있다.

pid대상
양수지정한 pid에만 전송
0함수를 호출하는 pid와 같은 그룹의 모든 프로세스에 전송
-1함수를 호출하는 pid가 전송 가능한 권한을 가진 모든 프로세스에 전송
음수음수인 pid를 절대값으로 바꾸고, 바꾼 후 pid와 같은 그룹에 있는 모든 프로세스에 전송

보낼 수 있는 시그널은 다음과 같이 터미널에서 kill -l을 입력하여 확인할 수 있다.

raise

#include <signal.h>

int raise(int sig); // 성공시 0, 실패시 0 이외의 값 리턴

프로세스 자신에게 시그널을 보낼 때 사용한다. 보통 실행되고 있는 프로그램에 시그널을 걸어줄 때 사용하며, kill함수로 표현한다면 다음과 동치이다.

kill(getpid(), sig);

getpid*, getppid

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

pid_t getpid(void); // 현재 프로세스의 pid리턴
pid_t getppid(void); // 현재 프로세스의 부모 프로세스 pid리턴
  • getpid 함수는 현재 프로세스의 pid를 가져온다.
  • getppid 함수는 현재 프로세스의 부모 프로세스의 pid를 가져온다.
  • 두 함수는 항상 정상적으로 실행된다.

fork

#include <unistd.h>

pid_t fork(void); // 성공 시 생성된 자식 프로세스의 pid, 실패 시 -1을 리턴

현재 프로세스를 복사하여 새로운 프로세스를 생성한다. 현재 프로세스는 새로운 프로세스의 부모 프로세스가 되고, 새롭게 생성된 프로세스는 현재 프로세스의 자식 프로세스가 된다.

현재 프로세스를 복사하여 새로운 프로세스를 생성하기 때문에 완전히 동일한 프로그램이 구동되는 복수의 프로세스가 생성되며, 이에 대한 제어는 fork 함수의 리턴값인 자식 프로세스의 pidgetppid를 통해서 가능하다. 또한 복사하여 새로 생성하는 것이기에 메모리 영역 등 시스템 자원을 서로 공유한다.

signal*

#include <unistd.h>

void (*signal(int signum, void (*handler)(int)))(int);
// 성공 시 handler 함수, 실패 시 -1을 리턴

시그널 처리를 설정한다. 첫번째 인자로 시스템에 설정된 signal 넘버를, 두번째 인자로 시그널 발생 시 동작할 핸들러 함수를 받는다. signal 함수를 사용할 때 두번째 매개변수를 통해 특정 시그널이 발생할 시 다음의 동작을 설정할 수 있다. 동작에 성공하면 두번째 매개변수인 시그널 핸들러 함수를 리턴한다. 실패하면 SIG_ERROR을 리턴한다.

매개변수태그
SIG_DFL기존 방법을 따른다. 이미 signal 함수를 통해 변경했다면 원래의 설정으로 초기화한다.
SIG_IGN시그널을 무시한다.
함수 포인터시그널 발생 시 해당 함수를 호출하도록 한다.

리눅스 signal - part1

sleep, usleep*

#include <unistd.h>

unsigned int sleep(unsigned int seconds); // 성공 시 0, 실패 시 -1 리턴
void usleep(unsigned int mseconds);
  • sleepusleep 함수 모두 지정한 시간 동안 프로세스를 대기 상태로 만든다. sleep에 인자로 입력하는 시간의 단위는 sec이고, usleep의 인자 단위는 micro sec이다. (1sec == 1,000,000μs)
  • 두 함수 모두 지정한 시간만큼 대기하는데 성공하면 0을 리턴한다. sleepusleep 모두 대기상태 중간에 현재 프로세스가 시그널을 받으면 작동이 중단된다. 이 경우 두 함수 모두 남은 시간을 리턴한다.
  • 내부적으로 SIGALRM 시그널을 활용하여 구현되어 있으므로 alarm 함수와 함께 사용하는 것은 좋은 선택이 아니다. 하나 이상의 alarm을 성정할 경우 먼저 지정된 alarm은 동작하지 않기 때문이다.

pause*

#include <unistd.h>

int pause(void); // -1 리턴
  • 해당 함수를 호출하면 현재 프로세스는 이후 시그널이 입력될 때까지 대기상태에 머무른다. 어떤 시그널이 들어올때까지 프로세스를 정지시키거나 다음의 기법을 활용하여 연속된 프로세스의 응답을 받는 동작을 구현할 때에 유용하다.
  • 시그널을 받기 위해 아래와 같이 pause를 사용하는 경우 시스템 자원의 소모량이 늘어나지 않고 현재 상태를 유지하기 때문에 다른 방법(무한 루프 등) 보다 효율성이 뛰어나다.
while (1)
{
	pause();
}

2. Sigaction 함수


sigactionsignal 함수 보다는 조금 더 다양한 기능을 지원하는 시그널 함수이다. sigaction 함수는 <sys/signal.h>에 정의된 sigaction 구조체를 활용하며, sigaction 구조체가 지원하는 다양한 기능을 사용할 수 있다.

💡 signal은 핸들러를 구축하는 오래되고 간단한 방법이지만 더이상 사용하지 않는다. Unix에서 시그널을 받은 후에 디폴트 값으로 핸들러를 리셋해버리기 때문이다. 만약 죽는 프로세스를 잡기 위해 SIGCHLD 각각을 별도로 핸들링 할 필요가 있는 경우 경쟁이 필요하다. 이 때문에 핸들러 내부에 핸들러를 설정할 필요가 있으며 signal을 호출하기 전에 다른 시그널이 도착할 것이다.

sigaction

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// 성공 시 0, 실패 시 -1 할당

특정 시그널 넘버에 대해 작동할 sigaction 구조체를 지정한다.

sigaction 구조체

struct sigaction {
	void (*sa_handler)(int); // signal 핸들러 함수
	void (*sa_sigaction)(int, siginfo_t *, void *);
	// 다음의 sa_flags가 SA_SIGINFO일때 sa_handler 대신에 동작하는 핸들러
	sigset_t sa_mask; // 시그널 처리 시 블록 지정할 시그널 마스크
	int sa_flags; // sig_flag 기능 지정을 위한 상수
	void (*sa_restorer)(void); // 응용프로그램 코드에서는 사용 지양

sigaction 함수를 사용하기 위해서는 sigaction 구조체를 프로그램 코드 내부에 선언해야 한다. signal 함수는 핸들러 함수를 등록하기 위해 사용하지만 sigaction 함수는 sigaction 구조체를 선언하고 내부에 핸들러를 등록한 뒤 그 구조체를 사용하기 때문이다.

void signal_handler(...)
{
	...
}

...
struct sigaction sig_strct;
...

sig_struct.sa_handler = signal_handler;
sigaction(SIGUSR1, sig_struct, NULL);

sa_handler, sa_sigaction

  • 두 맴버는 메모리 영역 단위로 보았을때 중첩된다. 즉, 두 맴버 모두 시그널이 들어왔을 때에 어떤 동작을 할지 핸들러 함수를 지정할 때 사용한다.
  • 두 맴버의 차이는 아래의 sa_flag에서 SIGINFO의 사용 여부에 따른다. SIGINFO를 사용하지 않는 경우 sa_handler에 시그널 핸들러 함수를 할당한다. 그렇지 않고 SIGINFO를 사용하는 경우라면 sa_sigaction에 시그널 핸들러 함수를 할당한다.
// error
sig_strct.sa_handler = ft_signal_handler;
sig_strct.sa_flag = SA_SIGINFO;
sigaction(SIGUSR1, sig_str, NULL);

// correct
sig_strt.sa_sigaction = ft_signal_handler;
sig_strct.sa_flag = SA_SIGINFO;
sigaction(SIGUSR1, sig_str, NULL);

sa_mask

  • sigaction 구조체의 핸들러 동작중일때 처리를 블록할 signal_set을 정한다. 기존에 블록하고 있던 시그널셋이 있다면 이 구조체의 핸들러가 동작할 때 여기에서 지정한 시그널셋을 기존에 블록하고 있던 시그널셋과 더하여 블록한다. (시그널 핸들러 발동 시 기존에 블록되던 시그널셋 + 해당 구조체의 sa_mask에 있는 시그널셋 모두 블록)
  • sa_flagSIG_NODEFER을 할당하면 시그널 핸들러를 호출하게 한 시그널은 블록하지 않는다.

sa_flag

signal 함수와 sigaction 사이에 가장 큰 차이점은 sa_flag를 이용한 다양한 기능의 지원이다. 다음의 플래그들을 활용하여 해당 sigaction 구조체의 핸들러가 발동될 때에 다양한 옵션들을 사용할 수 있도록 한다.

sa_flag에서는 ||연산자를 활용하여 다수의 플래그를 넣어줄 수도 있다.

flag동작 내용
SA_NOCLDSTOP이 값을 설정하고 시그널 SIGCHLD을 처리하는 경우, 자식 프로세스가 중지 혹은 재시작될 때에 부모 프로세스에 SIGCHLD를 전달하지 않는다.
SA_NOCLDWAIT이 값을 설정하고 시그널 SIGCHLD을 처리하는 경우, 시스템은 자식 프로세스가 종료될 때 좀비 프로세스를 만들지 않는다.
SA_NODEFER이 값을 설정하고 시그널을 받으면, 해당 시그널 처리 중에 Unix 커널이 해당 시그널을 자동으로 블록하지 못하도록 한다.
SA_NOMASKSA_NODEFER 와 동일
SA_ONSTACK이 값을 설정하고 시그널을 받으면, sigaltstack 함수를 호출하여 대체 시그널 스택이 있을 시 대체 스텍에서 처리하게 한다.
SA_RESETHAND시그널을 받고 핸들러 함수를 호출한 이후, 해당 시그널을 SIG_DFL로 설정한다. 즉 시스템 초기값으로 재설정한다. 처리 중 해당 시그널을 블록하지 않는다.
SA_ONESHOTSA_RESETHAND 와 동일
SA_RESTART시그널은 프로세스의 진행을 비동기적으로 interupt하는 성질이 있는데, 이 값을 설정하고 시그널을 발동시키면 원래 진행되던 프로세스는 시그널 핸들러 호출 및 처리 이후 재개된다.
SA_SIGINFO이 값을 설정하고 시그널을 받으면, sa_handler 대신 sa_sigaction 이 발동됩니다. 시그널 핸들러에 개별 시그널에 대한 더 많은 정보를 제공하는 추가적인 인자를 넣을 수 있습니다.

SIGINFO 구조체

sigaction 구조체의 sa_flag 맴버에 SA_SIGINFO를 할당하고 핸들러 함수에 siginfo_t siginfo*를 매개변수로 전달하면 사용이 가능한 SIGINFO 구조체는 <sys/signal.h>에 명시되어 있으며, 개별 시그널에 대한 더 많은 정보를 제공한다.

siginfo_t {
               int      si_signo;     /* 시그널 넘버 */
               int      si_errno;     /* 에러 넘버 */
               int      si_code;      /* 시그널 발생 이유 */
               int      si_trapno;    /* 하드웨어 송신 시그널의 트랩넘버
               				(대부분 아키텍처에서 사용 x)*/
               pid_t    si_pid;       /* 시그널을 보낸 프로세스의 pid */
               uid_t    si_uid;       /* 시그널을 보낸 프로세스의 effective user id */
               int      si_status;    /* EXIT 값 혹은 시그널 */
               clock_t  si_utime;     /* 소요된 User time */
               clock_t  si_stime;     /* 소요된 System time */
               union sigval si_value; /* 시그널 발생시 전달할 값 */
               int      si_int;       /* POSIX.1b signal */
               void    *si_ptr;       /* POSIX.1b signal */
               int      si_overrun;   /* Timer overrun count;
                                         POSIX.1b timers */
               int      si_timerid;   /* Timer ID; POSIX.1b timers */
               void    *si_addr;      /* Memory location which caused fault */
               long     si_band;      /* Band event (was int in
                                         glibc 2.3.2 and earlier) */
               int      si_fd;        /* File descriptor */
               short    si_addr_lsb;  /* Least significant bit of address
                                         (since Linux 2.6.32) */
               void    *si_lower;     /* Lower bound when address violation
                                         occurred (since Linux 3.19) */
               void    *si_upper;     /* Upper bound when address violation
                                         occurred (since Linux 3.19) */
               int      si_pkey;      /* Protection key on PTE that caused
                                         fault (since Linux 4.6) */
               void    *si_call_addr; /* Address of system call instruction
                                         (since Linux 3.5) */
               int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

si_code에서는 시그널의 발생 원인에 대해 추적할 수 있다. 이때 사용되는 값은 다음과 같다. 대개 음수나 0이 들어오는 시그널이 특정 프로세스에서 인위적으로 발생시킨 것이고, 양수인 경우에는 커널에서 프로세스에 대해 시그널을 전달해주었다고 볼 수 있다.

si_code 값시그널 발생 원인
SI_SIGIO대기된 SIGIO에 의해서 발생된 시그널
SI_ASYNCIOAIO (Asynchronous I/O)의 완료에 의해서 발생한 시그널
SI_MESGQ실시간 메시지 큐 (POSIX MQ)의 상태가 변화되어 발생한 시그널
SI_TIMER타이머가 만료되어 발생한 시그널
SI_QUEUEsigqueue 함수에 의해서 전달되어진 시그널
SI_USERkill, raise와 같은 함수에 의해서 전달되어진 시그널

3. Implemenation


  • 프로젝트 주요 요구사항
    • Mandatory - Your server should be able to receive strings from several clients in a row, without needing to be restarted.
    • Bonus - Add a small reception acknowledgment system.(ACK)
    • Bonus - support Unicode characters!
    • The server must be able to display the string pretty quickly. By quickly we mean that if you think it is too long, then it is probably too long (hint: 1 second for 100 characters is COLOSSAL) → "10자 1초, 100자 1초, 1000자 2초"

3.1. 문자열 전송

본 프로젝트의 목표는 클라이언트에서 서버로 문자열을 전송하고, 서버에서 그 문자열을 출력하는 clientserver프로그램을 만드는 것이다. 둘은 서로 다른 프로세스로 동작하기 때문에 UNIX signal을 활용한다.

문자는 어떻게 전송할까? C언어에서 문자는 아스키 코드로 표현되며, 문자는 각각의 숫자에 대응된다. 예를 들면 문자 a는 숫자 97에 대응한다. 이때 숫자 97은 2진법으로 표현하면 110 0001과 같다. 본 프로젝트에서 활용가능한 UNIX signalSIGUSR1, SIGUSR2 단 두가지이다. 비트연산을 활용하여 문자열의 각 문자의 비트단위 정보(0 또는 1)를 SIGUSR1, SIGUSR2 대응시켜 Inter-Process Commucation을 구현할 수 있다.

C 언어 코딩 도장

서로 다른 프로세스에 어떻게 '무엇인가'를 전달해 줄 수 있을까? signal 함수와 kill 함수의 용도에서 그 답을 찾을 수 있다. kill 함수를 통해 특정 프로세스 인식자(process identifier, pid)를 받아 실행중인 프로세스에 특정 시그널을 보낼 수 있고, signal 함수를 통해 특정 시스널이 입력되었을 때 어떠한 기능을 수행할지 핸들러 함수를 정의하고 할당할 수 있다.

3.2. PID_MAX

리눅스 커널에서 pid 한계값으로 명시된 값은 pid_max이다. 해당 값은 32768로 , 리눅스 기준으로 pid의 한계값은 32768번이다. 그렇다면 Mac OS에서의 최대값은 얼마일까?

Looking at sys/proc_internal.h in xnu-1699.24.23, I find that PID_MAX is 99999. The value is used in kern_fork.c in the function forkproc. Looking at that function, process IDs are not assigned equal to PID_MAX, so the highest possible pid is 99998.

What's the maximum pid for Mac OS X?

3.3. 전송속도 측정

#include <time.h>
#include <stdio.h>

static void	send_msg(pid_t pid_server, char *str)
{
	int		i;
	int		j;
	int		signal;
	char	buf;

	pause();
	clock_t s = clock(); // timer
	i = 0;
	while (str[i])
	{
		buf = str[i];
		j = 0;
		while (j < 8)
		{
			signal = get_signal(buf, j);
			usleep(50);
			kill(pid_server, signal);
			pause();
			if ((get_jth_bit(buf, j) != g_received_signal))
				terminate();
			++j;
		}
		++i;
	}
	send_null(pid_server);
	ft_putstr_fd("Minitalk succeeded!\n", STDOUT_FILENO);
	clock_t f = clock(); // timer
	printf("Duration: %lf [sec]\n", (double)(f - s)/CLOCKS_PER_SEC); // timer
}
// 10자
1234567890
Duration: 0.001996 [sec]
Duration: 0.002196 [sec]
Duration: 0.002376 [sec]

// 100자
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
Duration: 0.010113 [sec]
Duration: 0.009011 [sec]
Duration: 0.009932 [sec]

// 1000자
1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
Duration: 0.072237 [sec]
Duration: 0.072376 [sec]
Duration: 0.071909 [sec]

3.4. Uni-Code

컴퓨터는 메모리에 01만을 저장할 수 있으므로 컴퓨터는 문자를 표현하기 위해 각각의 문자와 대응하는 숫자를 정의하고 있는데 이것을 문자 코드라 한다.

대표적인 문자 코드가 ASCII 코드이며 영문자, 숫자, 기호 등을 ANSI 라는 표준화 기구에서 정의한 7비트 문자 코드(0~127) 이다. 그래서 ASCII코드를 ANSI코드라고도 한다. 정확히 말하면 ANSI코드는 ASCII 코드에 1비트를 더확장(128~255)하여 정의한 8비트 문자 코드이다. C언어에서는 ASCII코드를 사용하여 문자를 표현한다.

ASCII코드 외에도 많은 문자 코드가 존재하며 문화, 지역, 나라, 운영체제, 성능 등 여러 가지 요소들에 따라 문자 코드의 정의와 사용이 다랄져야 하기 때문에 문자 코드는 상당히 복잡하다. 이러한 문제점을 해결하고자 국제 표준화 기구에서 전 세계적으로 유일하게 정의된 문자 집합을 정의했는데 이것을 유니코드(UNICODE)라 한다. UTF-8 방식의 유니코드의 인코딩 원리는 다음과 같다.

UTF-8, UTF-16, UTF-32에서 UTF 다음의 숫자는 비트를 의미한다. UTF-32는 1글자당 32비트를 활용하는 것이다. 1글자당 32비트를 사용하게 되면 문자열 전송에 있어 비효율이 발생한다. 이에따라 UTF-16이 개발되었다. 하지만 UTF-16은 ASCII 문자와의 호환 문제와 엔디안 문제가 발생한다. 엔디안은 쉽게 말해, 한국에서는 책을 왼쪽에서 오른쪽으로 진행하여 읽지만 일본에서는 책을 오른쪽에서 왼쪽으로 진행하며 읽는것과 유사하다. 즉 순서의 문제가 발생한다. 큰 것을 천번째에 두느냐 마지막에 두느냐에 따라 빅엔디안 리틀엔디안으로 구분된다.

위와 같은 문제점을 보완하여 UTF-8이 개발되었다. 이는 1~4바이트로 인코딩될 수 있으며 ASCII 문자와 호환된다. 즉, ASCII 문자의 조합으로 영어 이외의 문자를 출력하는 것이다. 이 조합은 다음과 같이 나타낸다.

U+0000~U+007F : UTF-8 Encoding 0xxx xxxx 8bit (1byte)

U+0080~U+07FF : UTF-8 Encoding 110x xxxx 10xx xxxx 16bit (2byte)

U+0800~U+7FFF : UTF-8 Encoding 1110 xxxx 110x xxxx 10xx xxxx 24bit (3byte)

U+10000~U+10FFFF : UTF-8 Encoding 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx 32bit (4byte)

유니코드 중 📢 를 출력하고자 한다. 📢 는 0xF0 | 0x9F | 0x93 | 0xA2 (UTF-8 Encoding)의 유니코드를 갖고 있다. 이를 이진수로 변환한다면 1111 0000 | 1001 1111 | 1001 0011 | 1010 0010이 된다. 본 프로젝트에서는 이것을 총 4개의 char로 하나씩 받아서 끊기지 않게 출력해야 한다. 결론적으로 유니코드 또한 바이트 단위로 구성되어 있기 때문에, 1바이트(8비트) 단위로 문자정보를 송수신하여 이를 버퍼에 저장하고 문자열 단위로 한번에 출력하는것으로 유니코드 출력을 구현할 수 있다.

// how to print 📢 in C

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

int main(void)
{
	char *arr;

	arr = (char *)malloc(sizeof(char) * 4);
	arr[0] = 0b11110000;
	arr[1] = 0b10011111;
	arr[2] = 0b10010011;
	arr[3] = 0b10100010;

	write(1, &arr[0], 1);
	write(1, &arr[1], 1);
	write(1, &arr[2], 1);
	write(1, &arr[3], 1);
}

Source Code, Flow Chart


42SEOUL-42cursus/02-minitalk at main · 24siefil/42SEOUL-42cursus

// Pseudo Code
// server

1. Server PID를 출력한다.

2. 시그널 함수를 통해 SIGUSR1, SIGUSR2 시그널을 수신하는 경우 handler 함수 호출을 설정한다.
handler 함수는 수신한 시그널 저장하여 출력한다.
	1) g_msg.buf에 1비트씩 시그널에 따라 1 또는 0을 할당한다.
		SIGUSR1인 경우 비트를 1로
		SIGUSR2인 경우 비트를 0으로
	2) 수신한 시그널을 client에 같은 방법으로 echo(송신)한다.(ACK)
	3) 8비트를 수신할때까지 위의 과정을 반복한다.
	4) 8비트를 수신한 경우 연결리스트에 1개의 char(1바이트, 8비트)으로 저장한다.
	5) 버퍼를 초기화한다. 8비트가 모두 0'\0'문자를 수신하기 전까지 이를 반복한다.

	6) 8비트가 모두 0'\0'문자를 수신하는 경우 저장한 문자정보를 출력한다.
		연결리스트를 순회하며 1바이트씩 문자정보를 읽고 출력한다.
			이때 8비트 단위로 문자정보를 저장하였지만 출력은 문자열 단위로 1바이트씩 이루어지 때문에 Uni-Code에 대해서도 작동한다.	
	7) 연결리스트를 해제한다.
	8) msg 구조체를 초기화한다.	
			msg 구조체의 모든 멤버를 초기화한다.
			"Enter client PID" 메세지를 출력한다.
			get_next_line 함수를 통해 *표준입력으로 client PID를 입력받고 할당한다.
			*해당 Client에 수신 준비가 되었음을 SIGUSR1을 송신하여 알린다.

3. msg구조체를 초기화한다.
			msg 구조체의 모든 멤버를 초기화한다.
			"Enter client PID" 메세지를 출력한다.
			get_next_line 함수를 통해 *표준입력으로 client PID를 입력받고 할당한다.
			*해당 Client에 수신 준비가 되었음을 SIGUSR1을 송신하여 알린다.
		
4. 무한루프를 돌려 시그널이 수신되는 경우 계속해서 handler 함수를 호출한다.
// Pseudo Code
// client

1. Server PID와 송신할 메세지가 적절히 입력되지 않은 경우 에러처리.
	argc가 3이 아닌 경우
	argv[2][0]'\0'인 경우

2. Client PID를 출력한다.

3. signal 함수를 통해 SIGUSR1, SIGUSR2을 수신하는 경우 handler 함수 호출을 설정한다.
	handler 함수는 수신하는 시그널을 전역변수에 할당한다.
		if (signo == SIGUSR1)
			g_received_signal = 1
		else if (signo == SIGUSR2)
			g_received_signal = 0
		else
			g_received_signal = -1 (에러)

4. 메세지를 송신한다.
	*서버가 준비될때까지 대기한다. 서버로부터 시그널(SIGUSR1)을 수신하면 이후 과정을 진행한다.
	메세지를 한글자씩 buf에 담는다.
	j = 0;
	while (j < 8)	
	{
		buf를 첫비트부터 1비트씩 서버로 송신한다.
			비트가 1인 경우 SIGUSR1
			비트가 0인 경우 SIGUSR2
		서버로 부터 확인 시그널을 받을때까지 대기한다.(ACK)
		서버로 부터 받은 확인 시그널(비트)이 전송한 시그널과 일치하면 이후 과정을 진행한다.
		++j;
	}
	모든 글자에 대해 위의 과정을 반복한다.
	모든 글자를 송신했으면 끝을 알리기 위해 8비트가 모두 0'\0'문자를 송신한다.

Reference


프로젝트 회고 : minitalk

[minitalk] 이해 및 실행

Minitalk(1)

[42서울, Minitalk] 0. Minitalk는 무엇일까?

리눅스 시스템 프로그래밍 6장 - Signal

유닉스 신호 - 위키백과, 우리 모두의 백과사전

POSIX - 위키백과, 우리 모두의 백과사전

sigaction 함수의 활용

what is the value range of thread and process id?

pid의 최대값

profile
FE 엔지니어가 되기 위해 공부한 것들을 정리하고 공유합니다.

0개의 댓글