Toy_Project/C multi process TCP server

[C] C언어 멀티 프로세스 채팅 서버 만들기

Joo-Topia 2019. 12. 10. 16:53

 

지난번에 멀티 스레드 채팅 서버를 만든 적이 있다. (궁금하면 여기를 참고)

이번 포스팅은 멀티 프로세스 채팅 서버를 C로 구현해봤다.
(사실 친구의 수업에서 진행하는 프로젝트인데 재미있어 보여서 따라 했다.)

C언어 + fork의 조합 때문에 전체적인 코드가 길어지기 때문에 주요 로직에 대해서만 정리를 해야겠다.

 

서버 코드

서버의 코드에서 사용한 공유 메모리 종류이다.

전체적인 채팅 알고리즘을 아래와 같이 설계했다.
- 메세지 헤더, 메세지 유저, 메세지 데이터에 해당하는 공유메모리 영역을 선언한다.
- 메세지 헤더에는 현재 메세지의 번호가 담기며, 코드를 실행하면 0으로 초기화된다.
- 메인 코드 : 클라이언트 들의 접속을 허가하고 fork를 통해 채팅 알고리즘을 수행하는 프로세스를 생성한다.
- 채팅 프로세스 : 내부에서 또 한 번의 fork를 수행한다.
- 수신 프로세스 : 클라이언트에서 메세지를 받으면 메세지 번호가 올라가고 메세지 유저와 메세지 데이터를 갱신한다.
- 발신 프로세스 : 메세지 헤더를 항상 참조하며(바쁜 대기) 메세지 헤더가 변경될 시 새로운 메세지를 각 클라이언트에게 전송한다.


mutex 변수를 통해 세 개의 공유 메모리에 접근하는 임계 영역에 대해 상호 배제를 구현하였다. 
(파이썬은 스레드 GIL이 있기 때문에 이런 작업이 필요 없었지만, C언어에서는 해주는 게 좋다!)

메모리 해제는 필수!

 

- 채팅을 담당하는 프로세스 코드
*실제 채팅에 관여하는 프로세스가 실행하는 함수이기 때문에 프로세스 코드라고 설명.

void chat_func(char *id, char *pw, char *msg, char *buf, int new_fd){
    char *exit_flag;
    int temp, r_byte;
    exit_flag = mmap(NULL, sizeof(char), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 
    *exit_flag = 0;
    if(fork() == 0){
        //child -> only read shared mem
        temp = *chat_header;
        while(*exit_flag == 0){
            if(temp != *chat_header){
                //read
                temp = *chat_header;
                if (strcmp(id, chat_user) != 0){
                    if(strcmp("Log-out", chat_data) == 0){
                        while(*mutex){}
                        *mutex = 1;
                        sprintf(msg, "##### %s %s #####", chat_user, chat_data);
                        send(new_fd, msg, strlen(msg) + 1, 0);
                        *mutex = 0;
                    } else {
                        while(*mutex){}
                        *mutex = 1;
                        sprintf(msg, "%s : %s", chat_user, chat_data);
                        send(new_fd, msg, strlen(msg) + 1, 0);
                        *mutex = 0;
                    }
                }
            }
        }
    } else {
        //parent -> only recieve client's data
        while(*exit_flag == 0){
            memset(buf, 0, 512);
            r_byte = recv(new_fd, buf, MAX_LEN, 0);
            if(r_byte != -1){
                if(strcmp(buf, "/exit") == 0){
                    *exit_flag = 1;
                    while(*mutex){}
                    *mutex = 1;
                    memset(chat_data, 0, MAX_LEN);
                    strncpy(chat_data, "Log-out", strlen("Log-out") + 1);
                    strncpy(chat_user, id, strlen(id) + 1);
                    printf("##### %s %s #####\n", chat_user, chat_data);
                    *chat_header += 1;
                    *mutex = 0;
                } else {
                    while(*mutex){} // A
                    *mutex = 1; // ---------------------- B --------------
                    memset(chat_data, 0, MAX_LEN);
                    strncpy(chat_data, buf, strlen(buf) + 1);
                    strncpy(chat_user, id, strlen(id) + 1);
                    printf("%s : %s\n", chat_user, chat_data);// ---------
                    *chat_header += 1;// C
                    *mutex = 0;
                }
            }
        }
    }
    munmap(exit_flag, sizeof(char));
}

 

- 부모 프로세스
1. 클라이언트의 입력을 대기한다.
2. 클라이언트에서 데이터가 수신되면 공유 메모리 진입을 대기한다. - (A)
3. 공유 메모리에 접근하기 전 mutex의 값을 1로 해주고 메세지 유저와 메세지 데이터를 공유메모리에 복사한다. - (B)
4. 메세지 헤더에 1을 더해주고 mutex의 값을 0으로 바꿔준다. - (C)
*예를 들면, 직전의 메세지가 43번째 메세지였다면 이제 44번째 메세지가 된다는 뜻이다.
5. 만약 수신된 메세지가 "/exit"라면 프로세스를 종료한다.

- 자식 프로세스
1. 자식 프로세스가 실행되면서 현재 메세지 헤더의 값을 저장시킨다. (채팅 순서 동기화)
2. 반복 문 안에서 메세지 헤더의 값과 현재 프로세스의 채팅 번호(변수 temp)를 비교한다.
3. 새로운 채팅이 입력된 것이 확인되면 클라이언트에게 메세지 유저와 메세지 데이터를 전송한다.
4. 이때 자신이 보낸 메세지는 송신되지 않는다.

 

결과

client1, 2, 3 순서
server

잘 동작하는군..

전체 코드는 너무 길어서 여기에 따로 올려두었다.

유저에 대한 아이디, 비밀번호 정보와 그 정보를 확인하는 알고리즘을 구축하는 게 주요 목표가 아니기 때문에 하드코딩으로 진행하였다.