반응형
728x90
반응형
 

C기반 I/O Multiprocessing - 8. 드디어 올리는 멀티 프로세싱

진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다.. 포스팅 순서가 사실 멀티 프로세싱이 먼저이지만, 게임 서버 개발에 사용한 것은 멀티 플렉싱

typingdog.tistory.com

 

 

 

C기반 I/O Multiprocessing - 9. 멀티 프로세싱에서 좀비 문제

C기반 I/O Multiprocessing - 8. 드디어 올리는 멀티 프로세싱 진짜 다 까먹어서 다시 예제 쳐보고 다시 올린다... 정말 미리미리 올리고 자주자주 봐야합니다.. 포스팅 순서가 사실 멀티 프로세싱이 먼

typingdog.tistory.com

이전 시리즈(?) 들이다.

이전 내용들을 토대로 멀티 프로세싱 서버를 구현해볼 것이다. 간단하게 특징있는 부분만 짚어본다.

시그널을 등록하는 코드.

파일 디스크립터와 소켓의 관계를 보여준다.

프로세스가 복제가 되면 서버 소켓에 해당하는 파일 디스크립터도 복제가 되고, 클라이언트 소켓에 해당하는 파일 디스크립터도 복제가 된다. 그렇지만 파일 디스크립터는 프로세스의 소유이고, 소켓을 가리키는 숫자에 불과하기 때문에 복제가 되도 상관없지만 소켓 같은 경우는 운영체제 소유이기 때문에 복제가 되지 않고, 복제 되어서도 안된다. 

그렇지만 소켓을 제거하려면 해당 소켓을 동일하게 가리키는 파일 디스크립터를 모두 제거해야한다. 부모든 자식이든. 그렇기 때문에 각 부모, 자식에서는 필요한 디스크립터만 살려두고 나머지를 제거해야 나중에 잊지 않고 완벽하게 소켓을 제거할 수 있다.

전체 코드 및 실행 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUFSIZE 30
 
void ErrorMsg(const char *msg);
void GetChildReturnValue(int sig);
 
int main(void)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_addr, clnt_addr;
 
    int first_clnt_sock = -1, last_clnt_sock = -1, i = 0;
 
    pid_t pid;
    struct sigaction act;
 
    socklen_t addr_size;
    int strLen, state;
 
    char buf[BUFSIZE];
 
    act.sa_handler = GetChildReturnValue;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0);
 
    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    if(serv_sock == -1)
        ErrorMsg("socket() error");
 
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(7010);
 
    if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
        ErrorMsg("bind() error");
 
    if(listen(serv_sock, 5== -1)
        ErrorMsg("listen() error");
 
    while(1)
    {
        addr_size = sizeof(clnt_addr);
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &addr_size);
        if(clnt_sock == -1)
            continue;
        else
            printf("새로운 클라이언트가 접속 [%d]\n", clnt_sock);
 
        // 서버 측에서 클라이언트 소켓을 일괄 제거 하기 위함.
        // 리눅스에서는 파일 디스크립터가 1씩 증가함을 전제로 작성됨.
        // 아래 로직 영역이 없으면 클라이언트 디스크립터는 항상 같은 값을 나타냄..
        if(first_clnt_sock == -1)
            first_clnt_sock = clnt_sock; // 처음 디스크립터 값만 기록함.
        last_clnt_sock = clnt_sock; // 계속 갱신.
 
        pid = fork();
        /*
            이 때, 프로세스가 복사되면서 파일 디스크립터 값 또한 복사가 된다. 다만 소켓은 복사 되지 않는다.
            그 이유는 소켓은 운영체제 소유, 파일 디스크립터는 프로세스의 소유.
 
            하나의 소켓에 두 개의 파일 디스크립터가 존재하는 경우에는 두 개의 파일 디스크립터가 모두
            소멸되어야 소켓이 비로소 소멸된다.
 
            -> 그렇기 때문에 꼭 서로에게 상관 없는 소켓의 파일 디스크립터는 꼭 닫아줘야 한다.
            -> 부모 프로세스의 영역에서 위 라인에서 얻은 클라이언트 소켓을 꼭 닫아준다.
        */
        if(pid == -1// 프로세스 생성이 제대로 안 됐을 경우에는
        {
            close(clnt_sock); // 연결을 끊어버림. 왜냐하면 프로세스 생성이 제대로 안됐기 때문에.
            continue// 또 다른 접속을 받기 위함.
        }
 
        if(pid == 0// 자식 프로세스 실행 영역
        {
            close(serv_sock);
            // 자식 프로세스에서는 서버 소켓은 필요 없다. 연결된 클라이언트에 대해서만 처리하면 되기 때문.
            // 하나의 소켓, 둘의 파일 디스크립터이므로 꼭 닫아줘야한다.
            while((strLen = read(clnt_sock, buf, BUFSIZE)) != 0)
            {
                printf("[%d]로부터 온 메시지 : [%s]\n", clnt_sock, buf);
                write(clnt_sock, buf, strLen); // 내용을 다시 보냄. 에코.
            }
 
            close(clnt_sock);
            printf("클라이언트 종료 : [%d]\n", clnt_sock);
 
            return 0// 자식 클라이언트에서의 인자 전달이 이루어짐.
        }
        else // 부모 프로세스 실행 영역
        {
            // 부모 실행 영역에서는 실행할 로직이 없다.
        }
    }
    // 안타깝게도 이를 실행할 수 있는 방법은 현재까지 공부한 내용으로는 없다.
    // 부모 프로세스에서 쓰레드 등을 이용해서 위의 while(1)을 탈출할 수 있도록 해야한다.
 
    for( i = first_clnt_sock; i <= last_clnt_sock; i++)
        close(i);
 
    return 0;
}
 
void GetChildReturnValue(int sig)
{
    int status;
    pid_t pid = waitpid(-1&status, WNOHANG);
    if(WIFEXITED(status))
    {
        printf("종료 처리된 자식 프로세스가 소멸되었습니다.[pid:%d][return:%d]\n", pid, WEXITSTATUS(status));
    }
    return;
}
 
void ErrorMsg(const char *msg)
{
    fputs(msg, stderr);
    fputc('\n',stderr);
    exit(1);
}
 
cs

728x90
반응형
728x90
반응형

 

집에다가 Ubuntu Server 20.04를 설치했다. 

git 서버 / svn 서버 / 서비스 서버 용도로 사용하려고 하는데 GUI 환경은 필요없기 때문이다. 설치하고 놀랐던 점은 바로 아래과 같다!!!

역시 서버라 그런지 메모리 사용량이 실화인가 싶을 정도이다. (gitlab 이나 svn 서버 올리면 내나 그 타령이겠지만ㅋ)

뭐 아무튼 용도가 서버 용도인만큼 내가 원할 때 상시 접속을 할 수 있어야 하는데, 전기요금으로 인해 서버를 항시 켜둘 수도 없기 때문에 선택한 것은 WOL이다!

Wake On Lan 기능으로 랜 신호를 이용하여 컴퓨터를 깨우는 방법이다!

위와 같이 웹으로 접속하여 공유기한테 WOL 신호를 컴퓨터에게 주라고 명령하는 것이다 ㅋㅋㅋ 

그렇지만 깨움을 당하는 입장인 OS에서도 (마음의) 준비가 되어 있어야 하기 때문에 어느 정도 설정이 필요하다. 그 설정 때문에 이렇게 또 귀찮게 포스팅을 하게 되었다. 자주 쓰니까 기록할 겸!

 

1. 필요 명령 설치

sudo apt install net-tools ethtool wakeonlan

위 명령어를 통해 필요한 부분을 먼저 설치하자.

2. 이더넷 인터페이스 이름을 알아보자

내가 깨울 컴퓨터의 네트워크 랜 카드에 배정된 이더넷 인터페이스 이름을 알아야 한다. 이를 알기 위해서는 먼저,

ifconfig 명령을 실행하여

이더넷 인터페이스 이름을 알아낸다.

3. /etc/network/interfaces 설정

sudo vim /etc/network/interfaces

위의 명령을 통해 위에서 알아낸 인터페이스 이름을 기입하고 저장한다.

post-up /sbin/ethtool -s 이더넷 인터페이스 이름 wol g
post-down /sbin/ethtool -s 이더넷 인터페이스 이름 wol g

기입하고 저장한 모습

4. /etc/netplan 설정

고정 아이피만을 기준으로 적용한다. (wol 자체가 서버로 이용하려는 경우가 많기 때문에 다른 아이피들도 설정할 일이 많아서 그냥 고정 아이피로 설정해두는게 심신에 평안을 가져다 준다 ㅋㅋ)

/etc/netplan 디렉토리 내에 ~~.yaml 파일이 있는데 이를 다음 명령을 통해 열고 아래와 같이 수정한다.

sudo vim /etc/network/interfaces 

빨간 박스 부분만 추가하면 된다.

그리고 나서 아래의 명령어로 적용을 해준다.

sudo netplan apply

우분투 18.04 부터 /etc/network/interfaces 를 수정하는 것은 적용이 되지 않는다는 정보가 있어서 netplan 부분 설정이 필요해서 넣었다.

5. 시작 스크립팅 작성 ( 4번까지 적용해도 작동이 안될 경우 강제로 wol 기능을 켜도록 작성 )

먼저, 스크립트를 작성할 디렉토리를 생성한다.

sudo mkdir /etc/wol

위 디렉토리 안에 다음과 같은 내용을 작성하고 저장하여 wakeonlan.sh 스크립트를 생성한다.

sudo vim /etc/wol/wakeonlan.sh # 스크립트 생성 및 쓰는 명령어

---- 아래는 안에 들어가야할 내용 ----
#!/bin/sh                           
/sbin/ethtool -s 인터페이스이름 wol g
---- 위에는 안에 들어가야할 내용 ----

실행 퍼미션 설정

sudo chmod u+x /etc/wol/wakeonlan.sh

서비스 정의 파일(wakeonlan.service)을 작성한다. ( 컴퓨터가 켜질 때마다 실행할 서비스로 등록하는 과정 )

vim /etc/systemd/system/wakeonlan.service

---- 아래는 안에 들어가야할 내용 ----
[Unit]                                       
Description=Enable Wake-On-LAN 

[Service] Type=simple                  
ExecStart=/etc/wol/wakeonlan.sh    
Restart=always                           

[Install]                                     
WantedBy=multi-user.target         
---- 위에는 안에 들어가야할 내용 ----

아래의 명령어를 통해 서비스 등록 및 시작을 한다. 

sudo systemctl enable wakeonlan.service
sudo systemctl start wakeonlan.service

아래처럼 재부팅 후 명령어를 쳤을 때(인터페이스 이름은 각자 맡게 ㅋ), g 값이 뜬다면 성공인 것이고, 다시 컴퓨터를 종료한 후 wol 신호를 보내보자!  

728x90
반응형

+ Recent posts