UNIX 신호를 이용해 데이터를 주고받는 작은 프로그램 작성하기
void (*)(int) signal(int sig, void (*handler)(int));
pid_t getpid(void)
unsigned int sleep(unsigned int seconds);
int usleep(useconds_t microseconds);
int pause(void)
int kill(pid_t pid, int sig);
sa_flags에 SA_SIGINFO 플래그를 지정하면 시그널이 발생한 원인을 알 수 있다.
sigaction 구조체에서 시그널 핸들러를 지정할 때 sa_handler 대신 sa_sigaction을 사용한다.
시그널 핸들러는 다음과 같이 인자를 3개 받는 형태로 정의되어 진다.
void handler(int signo, siginfo_t *siginfo, ucontext_t *context);
// signo: 시그널 핸들러를 호출할 시그널
// siginfo: 시그널이 발생한 원인을 담은 siginfo_t 구조체 포인터
// context: 시그널이 전달될 때 시그널을 받는 프로세스의 내부 상태를 담은 ucontext_t 구조체 포인터
Minitalk에서는 SA_SIGINFO 플래그를 설정한다.
struct sigaction sig;
sig.sa_flags = SA_SIGINFO;
SA_SIGINFO 플래그는 시그널 핸들러가 하나가 아닌 3개의 인자를 취할 경우 sa_handler 대신 sa_sigaction의 siginfo_t를 이용한다.
siginfo_t는 다음과 같이 정의된 구조체이다.
siginfo_t {
int si_signo; // 시그널 넘버
int si_errno; // errno 값
int si_code; // 시그널 코드
pid_t si_pid; // 프로세스 ID 보내기
uid_t si_uid; // 프로세스를 전송하는 실제 사용자 ID
int si_status; // Exit 값 또는 시그널
clock_t si_utime; // 소모된 사용자 시간
clock_t si_stime; // 소모된 시스템 시간
sigval_t si_value; // 시그널 값
int si_int; // POSIX.1b 시그널
void *si_ptr; // POSIX.1b 시그널
void *si_addr; // 실패를 초래한 메모리 위치
int si_band; // 밴드 이벤트
int si_fd // 파일 디스크럽터
}
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// signum: 발생한 시그널 번호
// act: 시그널 발생 시 처리해주기 위한 설정 값들이 존재하는 sigaction 구조체
// oldact: 이전에 등록되었던 sigaction 구조체 (필요하지 않다면 NULL or 0)
void (*sa_handler)(int);
void (*sa_handler)(int, siginfo_t *, void *);
위에서 sa_flags 변수에 SA_SIGINFO 플래그를 지정해주었기 때문에 sa_sigaction 포인터 함수에 클라이언트 신호를 처리하기 위한 3개의 인자를 가지는 핸들러 함수를 지정해준다.
sig.sa_sigaction = &ft_handler;
// 3개의 인자를 가지는 함수 ft_handler()을 sig.sa_sigaction에 연결해준다.
sigemptyset(&sig.sa_mask);
이후 pid를 출력하고 sigaction() 함수를 사용하여 SIGUSR1과 SIGUSR2 시그널을 받아준다. pause()를 사용하여 시그널이 들어올 때 까지 대기한다.
단, 주의할 점은 kill() 함수를 통해 서버에게 일방적으로 반복적인 신호를 전송할 것이기 때문에 약간의 딜레이가 필요하다.
클라이언트가 서버에게 kill 함수를 통해 보내는 신호의 속도보다 서버에서 signal 함수로 받는 속도가 느리기 때문에 적절한 딜레이가 필요하다.
1개의 문자를 전송할 때 8개의 비트를 보내야 한다. 즉, 8번의 kill() 함수를 사용하여 시그널을 전송시켜야 한다. 따라서 8번의 딜레이가 필요한데 usleep() 함수를 사용하면 마이크로 초 단위로 대기상태가 되므로 훨씬 빠르게 처리가 가능하다.
usleep() 함수와 함께 메시지의 구성 문자들을 모두 서버에게 전송완료 했다면, 마지막으로 '\0'에 대한 시그널도 서버에게 보낸다.
메시지가 모두 성공적으로 수신 완료되었다는 시그널을 서버로부터 받기 위해 pause() 함수를 호출하여 대기한다.
핸들러 함수는 시그널이 발생할 때마다 호출되어진다.
즉, 정적 변수를 사용하지 않는다면 시그널이 발생할 때마다 핸들러 함수안의 변수들의 값은 유지되지않고 초기화 된다.
또한 8bit 만큼의 시그널을 받았다면 해당 문자 값을 저장하기 위한 변수도 정적으로 선언한다.
클라이언트로부터 보내온 시그널이 SIGUSR1이라면 1을 해당 비트만큼 shift left 연산을 한 뒤 or 연산을 수행한다. 시그널이 SIGUSR2라면 pass하여 자동적으로 0을 or 연산하는 것과 같은 연산이 된다.
총 8bit가 채워졌다면 bit와 문자를 저장하는 변수 tmp를 초기화 시켜준다.
따로 코드 작성은 필요 없다.
맥에서 UTF-8로 인코딩을 실행하므로 자동으로 유니코드를 변환시켜 준다. 즉, Mandatory를 통해 1비트씩 전송할 수 있도록 했다면 유니코드 관련 코드는 작성할 필요가 없다.
void ft_putchar_fd(char c, int fd)
{
write(fd, &c, 1);
}
void ft_putstr_fd(char *s, int fd)
{
int i;
i = 0;
while (s[i])
write(fd, &s[i++], 1);
}
void rec_putnbr(int nb, int fd)
{
char c;
if (nb == 0)
return ;
c = '0' + nb % 10;
rec_putnbr(nb / 10, fd);
ft_putchar_fd(c, fd);
}
void ft_putnbr_fd(int n, int fd)
{
char c;
if (n < 0)
{
write(fd, "-", 1);
c = '0' - n % 10;
rec_putnbr(-(n / 10), fd);
}
else
{
c = '0' + n % 10;
rec_putnbr(n / 10, fd);
}
ft_putchar_fd(c, fd);
}
int ft_atoi(const char *str)
{
int i;
int num;
int sign;
i = 0;
num = 0;
sign = 1;
while (str[i] == ' ' || (str[i] >= 9 && str[i] <= 13))
i++;
if (str[i] == '+' || str[i] == '-')
{
if (str[i] == '-')
sign *= -1;
i++;
}
while (str[i] >= '0' && str[i] <= '9')
{
num = num * 10 + (str[i] - '0') * sign;
i++;
}
return (num);
}
static void ft_send_bit(pid_t pid, char input)
{
int bit;
bit = 0;
while (bit < 8)
{
// 가장 오른쪽 비트부터 왼쪽으로 가면서 전송함
// 1을 bit만큼 left shift 연산 한 값을 input과 &연산
if ((input & (1 << bit)) != 0)
kill(pid, SIGUSR1); // 해당 값이 1일 경우 SIGUSR1 신호 전송
else
kill(pid, SIGUSR2); // 해당 값이 0일 경우 SIGUSR2 신호 전송
usleep(100); // 딜레이를 위해
bit++;
}
}
static void ft_send_str(pid_t pid, char input[])
{
int i;
i = 0;
while (input[i] != '\0') // 한 문자씩 전송함
{
ft_send_bit(pid, input[i]);
i++;
}
ft_send_bit(pid, '\n');
ft_send_bit(pid, '\0'); // 문자열 전송을 끝냈다는 것을 알리기 위한 널문자 전송
}
int main(int argc, char **argv)
{
pid_t pid;
if (argc == 3 && argv[2][0] != '\0') // 인자가 정확할 경우
{
pid = ft_atoi(argv[1]); // 입력한 pid 받아오기
if (pid < 100 || pid > 99999) // pid의 범위처리 100 ~ 99999
{
ft_putstr_fd("Error: wrong pid.\n", 1);
return (0);
}
ft_send_str(pid, argv[2]); // 문자열을 한 번에 전달
}
else // 인자가 유효하지 않은 경우
{
ft_putstr_fd("Error: wrong format.\n", 1);
ft_putstr_fd("Try: ./client [PID] [MESSAGE]\n", 1);
}
return (0);
}
static void ft_handler(int signal)
{
static int bit; // 비트를 얼마나 받았는 지 확인하는 정적 변수
static char tmp; // 문자값을 저장하는 정적 변수
if (signal == SIGUSR1) // SIGUSR2 일 때는 어차피 0이므로 pass 가능
tmp |= (1 << bit); // 가장 오른쪽 비트부터 추가해줌
bit++;
if (bit == 8) // 비트가 8이 될 경우 저장된 문자를 출력하고 정적변수 초기화
{
ft_putchar_fd(tmp, 1);
bit = 0;
tmp = 0;
}
}
static void ft_pid_print(pid_t pid) // pid 출력
{
ft_putstr_fd("PID -> ", 1);
ft_putnbr_fd(pid, 1);
ft_putchar_fd('\n', 1);
ft_putstr_fd("Waiting for a message...\n", 1);
}
int main(int argc, char **argv)
{
pid_t pid;
(void)argv;
if (argc != 1) // 인자가 1개일 경우 예외처리
{
ft_putstr_fd("Error: worng format.\n", 1);
ft_putstr_fd("Try: ./server\n", 1);
return (0);
}
pid = getpid(); // pid를 받아오기
ft_pid_print(pid); // pid 출력
// SIGUSR1신호와 SIGUSR2 신호 입력시 ft_handler함수 호출
signal(SIGUSR1, ft_handler);
signal(SIGUSR2, ft_handler);
while (argc == 1)
pause(); // 신호가 입력될 때 까지 대기
return (0);
}
// 문자열 전송이 완료되었을 때 서버에서 SIGUSR1 전송했을 경우 해당 문자열 출력 후 client 종료
static void ft_confirm(int signal)
{
if (signal == SIGUSR1)
ft_putstr_fd("Server received message\n", 1);
exit (0);
}
static void ft_send_bit(pid_t pid, char input)
{
int bit;
bit = 0;
while (bit < 8)
{
if ((input & (1 << bit)) != 0)
kill(pid, SIGUSR1);
else
kill(pid, SIGUSR2);
usleep(100);
bit++;
}
}
static void ft_send_str(pid_t pid, char input[])
{
int i;
i = 0;
while (input[i] != '\0')
{
ft_send_bit(pid, input[i]);
i++;
}
ft_send_bit(pid, '\n');
ft_send_bit(pid, '\0');
}
int main(int argc, char **argv)
{
pid_t pid;
if (argc == 3 && argv[2][0] != '\0')
{
signal(SIGUSR1, ft_confirm); // server에서 SIGUSR1 신호를 전송한다면 ft_confirm() 함수를 호출한다.
pid = ft_atoi(argv[1]);
if (pid <= 100 || pid >= 99999)
{
ft_putstr_fd("Error: wrong pid.\n", 1);
return (0);
}
ft_send_str(pid, argv[2]);
while (1)
pause(); // 신호가 입력될 때 까지 대기
}
else
{
ft_putstr_fd("Error: wrong format.\n", 1);
ft_putstr_fd("Try: ./client [PID] [MESSAGE]\n", 1);
}
return (0);
}
static void ft_handler(int signal, siginfo_t *info, void *s)
{
static int bit;
static char tmp;
pid_t pid;
(void)s;
pid = info->si_pid; // info의 si_pid를 통해서 pid를 가져옴
if (signal == SIGUSR1)
tmp |= (1 << bit);
bit++;
if (bit == 8)
{
if (tmp != '\0') // 널 문자가 아닐 경우 문자 출력
ft_putchar_fd(tmp, 1);
else // 널 문자일 경우 SIGUSR1 신호 전송
kill(pid, SIGUSR1);
bit = 0;
tmp = 0;
}
}
static void ft_pid_print(pid_t pid)
{
ft_putstr_fd("PID -> ", 1);
ft_putnbr_fd(pid, 1);
ft_putchar_fd('\n', 1);
ft_putstr_fd("Waiting for a message...\n", 1);
}
int main(int argc, char **argv)
{
struct sigaction sig;
pid_t pid;
(void)argv;
if (argc != 1)
{
ft_putstr_fd("Error: worng format.\n", 1);
ft_putstr_fd("Try: ./server\n", 1);
return (0);
}
sig.sa_flags = SA_SIGINFO;
sig.sa_sigaction = &ft_handler;
sigemptyset(&sig.sa_mask);
pid = getpid();
ft_pid_print(pid);
// 각각 SIGUSR1, SIGUSR2신호가 입력될 경우 sigaction으로 연결된 ft_handler() 함수를 호출
sigaction(SIGUSR1, &sig, NULL);
sigaction(SIGUSR2, &sig, NULL);
while (1)
pause();
return (0);
}
다음 규칙을 준수하십시오:
평가 과정 내내 예의바르고, 예의바르고, 공손하고, 건설적인 태도를 유지하십시오. 공동체의 안녕이 그것에 달려 있다.
프로젝트에서 발생할 수 있는 기능 장애를 평가한 학생 또는 그룹과 식별합니다. 확인된 문제에 대해 토론하고 토론하는 시간을 갖습니다.
동료가 프로젝트의 지침과 기능 범위를 이해하는 방식에 약간의 차이가 있을 수 있다는 점을 고려해야 합니다. 항상 열린 마음을 유지하고 가능한 한 정직하게 평가하세요. 교육학은 동료 평가가 진지하게 이루어질 때에만 유용하다.
부정행위가 의심될 경우 평가는 여기서 종료됩니다. "부정행위" 플래그를 사용하여 보고합니다. 침착하고 현명하게 이 결정을 내리십시오. 그리고 부디 이 버튼을 주의 깊게 사용하십시오.
이러한 모든 점이 유효한 경우 예를 선택하고 수정을 계속합니다.
체중계 끝에 적절한 플래그를 사용하지 않는 경우!
이 프로젝트는 신호에 대한 소개 신호의 코드와 사용 방법을 확인하여 통신하는 것입니다.
크기에 상관없이 메시지를 전달할 수 있습니다
수신된 메시지는 서버에 의해 표시되어야 하며, 정확해야 합니다!
서버가 막히거나 잘못된 문자를 인쇄하면 안 됩니다.
수신된 메시지는 서버에 의해 표시되어야 하며, 정확해야 합니다!
필수 부품이 완전히 완벽하게 완성되었고 오류 관리가 예상치 못한 사용 또는 잘못된 사용을 처리하는 경우에만 보너스 부품을 평가합니다. 수비 중 필수 포인트가 모두 통과되지 않은 경우 보너스 포인트는 무시해야 한다.
유니코드 문자는 클라이언트 및 서버에서 지원됩니다.
클라이언트는 다음 신호를 보내기 위해 서버의 승인을 기다립니다.