Home Minitalk ④ 구현 과정
Post
Cancel

Minitalk ④ 구현 과정

preview

구현한 기능

server

  • server pid 출력
  • 시그널 수신
  • 메시지 출력
  • 발송인의 pid 출력
  • 받은 바이트 출력
  • 메시지 수신 여부 출력

client

  • client pid 출력
  • 연결 여부 출력
  • 시그널 전송
  • 보낸 바이트 출력
  • 메시지 전송 성공 여부 출력

방법 구상

  1. server on
  2. client on
  3. client send message (str → char → bit → signal)
  4. server get message
  5. server print message
  6. server end

방법 수정

  • 3-way handshake를 활용해 연결
  • 서버와 클라이언트가 시그널을 주고받으며 수신여부를 실시간으로 체크
  • 메모리를 일정한 크기만큼 할당한 후 0으로 초기화해가며 사용

첫 구현에서는 client가 연결 성공 여부를 확인하지 않고 바로 시그널을 전송하는 문제가 있었다. 이를 해결하기 위해 3-way handshake 방식을 활용해 연결 여부를 확인하였다.
client가 server에게 시그널을 보내면 server는 받은 시그널에 1을 더해서 반송한다. client는 시그널이 제대로 수신되었는지를 확인한 후에 성공여부를 출력한다.

시그널을 계속해서 보내게 되면 시그널이 잘못 수신되는 경우가 생기는데 이를 해결하기 위해 기존에는 usleep(100)과 같이 일정한 딜레이를 두어 시그널을 전송했다. 하지만 이러한 코드는 긴 메시지를 보낼 때 효율성이 떨어진다는 단점이 존재한다. 따라서 client가 매 시그널을 보낼 때마다 server도 이에 답하는 시그널을 보내 수신 여부를 체크하도록 했다.

헤더 파일

1
2
3
4
5
6
7
8
typedef struct s_data
{
	struct sigaction	action_handshake;
	struct sigaction	action_transmission;
	pid_t			pid;
	char			*message;
	int			byte;
}	t_data;

위와 같은 구조체를 전역으로 선언해서 사용

  • sigaction 구조체
    • action_handshake : 3-way handshake를 통해 연결을 확인한다.
    • action_transmission : 들어오는 시그널을 받아서 메시지로 변환하거나 종료여부를 파악한다.
  • pid

    메시지를 전송하려는 상대방의 Process ID를 저장한다.

    • PID : 운영체제가 프로세스를 식별하기 위해 부여한 번호
    • pid_t 자료형
      • pid를 저장하는 자료형
      • sys/types.h 에 선언되어 있다.
      • 시스템에 따라 프로세스가 int형이 아닌 경우가 있을 수 있기 때문에 사용
  • message
    • Client : 전송하려는 메시지를 저장한다.
    • Server : 수신하는 메시지를 출력하기 전까지 저장한다.
  • byte

    송수신된 메시지의 바이트를 저장한다.

구성

Server

server.c

  • init : 초기 설정 (각종 변수 초기화, sigaction 설정, 메모리 할당, 현재 Process ID 출력)
  • handshake_synack : 3-way handshake 2단계. 받은 시그널에 1을 더한 시그널을 보낸 pid에게 전송

server_transmit.c

  • ft_receive_signal : 수신된 시그널을 처리할 함수를 호출 후 종료 여부 체크
  • signal_to_char : (수신된 시그널에 해당하는) 비트를 배열의 값에 더해준다.
  • print_message : 메시지 출력

utils.c

  • ft_kill : kill 함수를 변형
  • ft_error : 에러 메시지 출력 후 종료

Client

client.c

  • check_argument : 명령행 인자의 유효성 체크
  • init : 초기 설정 (각종 변수 초기화, sigaction 설정, 현재 Process ID 출력)
  • handshake_syn : 3-way handshake 1단계.
  • handshake_ack : 3-way handshake 3단계. 수신된 시그널을 확인해 연결의 성공여부 체크

client_transmit.c

  • ft_send_or_end : server가 보낸 시그널에 따라 종료 여부 결정
  • ft_send_message : 보낼 메시지를 바이트 단위로 쪼개서 함수 호출
  • send_signal : 해당 바이트의 각 비트 값에 따라 시그널 전송

utils.c

  • 위와 동일

동작 과정

Server

  • 입력된 인자를 체크
  • init함수를 통해 각 변수를 초기화
  • sigaction 함수의 구조체를 설정(핸들러, 플래그, 마스크 등)
  • message 포인터에 100만큼 메모리 할당한 후 0으로 채움
  • 무한루프를 돌며 신호를 기다림
  • 신호가 들어오면 핸들러인 handshake_synack가 실행되어 들어온 값에 1을 더해 client에 전송한다.
  • sigaction의 구조체를 변경해 메시지를 수신할 준비를 한다.
  • 메시지가 들어오면 pid를 체크한 후에 이상이 있으면 들어온 시그널을 그대로 전송하거나 핸들러를 종료시킴
  • 이상이 없으면 0번 인덱스부터 비트시프트를 통해 값을 채워나감. (이때, 시그널 하나를 받을때마다 client에 종료여부를 시그널로 보냄)
  • 널문자를 만나면 문자를 출력한 후에 세부사항을 출력하고 sigaction의 구조체를 변경한 후 종료신호를 보냄
  • 100칸을 다 채웠다면 문자를 출력하고 다시 입력을 받는다.

Client

  • 입력한 인자를 체크한 후 pid에 저장한다.
  • init함수로 변수들을 초기화해준 후 sigaction함수에 handshake 구조체를 설정한다.
  • pid에 SIGUSR1을 송신한다.
  • 보내준 시그널에 1이 더해진 값이 수신되면 handshake 성공
  • 들어온 값이 다르거나 수신된 pid가 다르면 일정횟수동안 다시 연결을 시도한 후 종료
  • send_message 함수를 통해 message를 비트단위로 쪼개어 시그널을 전송 (해당 자리의 비트가 1이면 USR1을, 0이면 USR2를 전송한다.)
  • 보낸 pid로부터 메시지를 받았다는 시그널을 받으면 종료여부에 따라 이어서 전송하거나(SIGUSR1) 전송을 종료한다.(SIGUSR2)
  • 전송을 마치면 전송한 바이트와 성공 메시지를 출력한다.

예외 처리

Server

  • 명령행 인자가 들어옴
  • 시그널을 보낸 발송인의 pid가 기존에 메시지를 보내던 client가 아닌 경우

Client

  • 명령행 인자가 ./client [PID] [Message] 의 형태가 아님
  • 명령행 인자의 pid가 유효하지 않다.(정수가 아닌 경우)
  • 명령행 인자의 pid가 유효하지 않다.(server의 pid가 아닌 경우)

테스트

  • 일반 메시지 (메시지에 공백이 있으면 큰따옴표로 묶어서 입력해야한다.)

    1
    
      hello world
    

  • 100자 메시지

    1
    
      LoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumd
    
  • 500자

    1
    
      LoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdoLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdoLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdoLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdoLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumdolorsitametLoremipsumd
    

  • 유니코드 1

    1
    
      😀🐶🍎🏀🚗💡
    

  • 유니코드 2

    1
    
      메시지테스트
    

  • 유니코드가 출력되는 이유?

    • 1바이트 char형의 최대값은 127.
      • 오른쪽부터 7개의 비트만 사용한다. (01111111)
    • 반면 4바이트 유니코드는 11110xxx 형태를 가짐.
      • 제일 왼쪽 비트를 사용한다.
    • UTF-8 인코딩 방식을 사용하는 터미널은 첫 번째 바이트의 형태를 보고 4바이트의 유니코드라고 인식하는 것.

총평

부족한 부분

  • sigaction 함수와 시그널 핸들러 (세 번째 매개변수의 역할)
  • sigemptyset, sigaddset의 사용 방법
  • makefile (헤더파일 include, addprefix, patsubst 등)

개선 방향

  • 첫글자가 다르게 출력되는 케이스가 존재
    • kill 함수를 통해 시그널을 주고받을 때 딜레이를 전혀 주지 않아서 생기는 문제로 보인다.
    • ft_kill 함수를 정의하여 시그널을 전송하기 전에 usleep으로 10마이크로초만큼의 딜레이를 발생시킴

Ref.

This post is licensed under CC BY 4.0 by the author.

Minitalk ③ 배경 지식 : 기타

운영 체제 - 00. 학습 계획