프로세스 기반 다중접속 서버 모델

 

핵심은 연결이 하나 생성될 때마다 프로세스를 생성해서 해당 클라이언트에 대해 서비스를 제공하는 것이다.

 

● 1단계 : 에코 서버(부모 프로세스)는 accept 함수호출을 통해서 연결요처을 수락한다.

● 2단계 : 이때 얻게 되는 소켓의 파일 디스크립터를 자식 프로세스를 생성해서 넘겨준다.

● 3단계 : 자식 프로세스는 전달받은 파일 디스크립터를 바탕으로 서비스를 제공한다.

 

echo.mpserv.c

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
#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 BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);
 
int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
 
    pid_t pid;
    struct sigaction act;
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];
    if(argc!=2){
        printf("Usage : %s <port>\n",argv[0]);
        exit(1);
    }
 
    act.sa_handler=read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    state=sigaction(SIGCHLD,&act,0);
 
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
 
    if(bind(serv_sock,(struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error");
 
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");
 
    while(1)
    {
        adr_sz=sizeof(clnt_adr);
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&adr_sz);
        if(clnt_sock==-1)
            continue;
        else
            puts("new client connected...");
        pid=fork();
        if(pid==-1)
        {
            close(clnt_sock);
            continue;
        }
        if(pid==0)
        {
            close(serv_sock);
            while((str_len=read(clnt_sock,buf,BUF_SIZE))!=0)
                write(clnt_sock,buf,str_len);
 
            close(clnt_sock);
            puts("client disconnected...");
            return 0;
        }
        else
            close(clnt_sock);
 
    }
    close(serv_sock);
    return 0;
}
 
void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid=waitpid(-1,&status,WNOHANG);
    printf("removed proc id: %d\n",pid);
}
 
void error_handling(char * message)
{
    fputs(message, stderr);
    fputc('\n',stderr);
    exit(1);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

좀비 프로세스의 소멸을 위해서 앞서 보인, 좀비의 해결을 위한 함수의 호출과정은 거쳐야 한다. 이 코드에서는 이 내용을 보이지 않고 있다.

프로세스가 복사되는 경우 해당 프로세스에 의해 만들어진 소켓이 복사되는 게 아니고, 파일 디스크립터가 복사된다.

하나의 소켓에 두 개의 파일 디스크립터가 존재하는 경우, 두 파일 디스크립터 모두 종료되어야 해당 소켓 소멸 그래서 fork 함수 호출 후에는 서로에게 상관 없는 파일 디스크립터를 종료한다.

 

echo_client.c

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 1024
void error_handling(char *message);
 
int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;
 
    if(argc!=3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    sock=socket(PF_INET, SOCK_STREAM, 0);   
    if(sock==-1)
        error_handling("socket() error");
    
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_adr.sin_port=htons(atoi(argv[2]));
    
    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("connect() error!");
    else
        puts("Connected...........");
    
    while(1
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        
        if(!strcmp(message,"q\n"|| !strcmp(message,"Q\n"))
            break;
 
        write(sock, message, strlen(message));
        str_len=read(sock, message, BUF_SIZE-1);
        message[str_len]=0;
        printf("Message from server: %s", message);
    }
    
    close(sock);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

TCP의 입출력 루틴(Roitine) 분할

입출력 루틴 분할의 이점과 의미

 

소켓은 양방향 통신이 가능하다. 따라서 입력을 담당하는 프로세스와 출력을 담당하는 프로세스를 각각 생성하면, 입력과 출력을 각각 별도로 진행시킬 수 있다.

 

입출력 루틴을 분할하면, 보내고 받는 구조가 아니라, 이 둘이 동시에 진행 가능하다.

 

 

 

echo_mpclient.c

입력을 담당하는 함수와 출력을 담당하는 함수를 구분 지어 정의했기 때문에, 구현의 용이성에도 좋다.

물론, 인터랙티브 방식의 데이터 송수신을 진행 하는 경우에는 이러한 분할이 큰 의미를 부여 하지 못한다. 즉, 이러한 형태의 구현이 어울리는 상황이 있고, 또 어울리지 않는 상황도 있다.

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);
 
int main(int argc, char *argv[])
{
    int sock;
    pid_t pid;
    char buf[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;
 
    if(argc!=3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    sock=socket(PF_INET, SOCK_STREAM, 0);   
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_adr.sin_port=htons(atoi(argv[2]));
    
    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
        error_handling("connect() error!");
    
    pid=fork();
    if(pid==0)
    {    write_routine(sock,buf);}
    else
    {    read_routine(sock,buf);}
    
 
    close(sock);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
 
void read_routine(int sock,char* buf)
{
    while(1)
    {
        int str_len=read(sock,buf,BUF_SIZE);
        if(str_len==0)
        {    return; }
 
 
        buf[str_len]=0;
        printf("Message from server: %s",buf);
    }
}
 
void write_routine(int sock, char *buf)
{
    while(1)
    {
        fgets(buf,BUF_SIZE,stdin);
        if(!strcmp(buf,"q\n"|| !strcmp(buf,"Q\n"))
        {
            shutdown(sock,SHUT_WR);
            return;
        }
        write(sock,buf,strlen(buf));
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

TCP의 이론적인 이야기

 

TCP 소켓에 존재하는 입출력 버퍼

● 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재한다

● 입출력 버퍼는 소켓생성시 자동으로 생성된다.

● 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이뤄진다.

● 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸되어버린다.

 

TCP 프로토콜 특징

 

TCP Protocol stack

Internet 기반의 효율적인 데이터 전송을 지원

Link 계층, 물리적 영역

IP 계층, 경로 선택을 통한 데이터 전송

TCP/UDP 계층, 신뢰성있는 데이터 전송 방법을 제시

 

TCP

- 연결 지향: 전용 선로 개설

안정적인 데이터 통신, 패킷의 순서 재 조립, 패킷 누수시 재 요청

- 모든 패킷 전송에 대한 응답 확인, 패킷에 일련번호 : 패킷을 흐름으로 다룸

- 3Way Handshake , 3번의 패킷 교환으로 세션 개설

 

낮은 성능, -패킷에 대한 응답 : 시간 지연, CPU 소모

Streaming 서비스에 불리, 재 전송 요청에 따른 서비스 지연

 

3Way Handshaking

 

 

TCP 기반 서버의 구현

socket() 소켓 생성 -> bind() 소켓에 주소 할당 -> listen() 연결요청 대기상태

accept() 연결 허용 -> read() & write() 데이터 송수신 -> close() 연결 종료

 

 

다양한 TCP Socket 옵션

소켓 옵션

소켓의 특성을 변경시킬 때 사용하는 옵션 정보들. 계층별로 분류

Protocol Level Option Name Get Set
SOL_SOCKET

SO_SNDBUF

SO_RCVBUF

SO_REUSEADDR

SO_KEEPALIVE

SO_DONTROUTE

SO_OOBINLINE

SO_ERROR

SO_TYPE

O

O

O

O

O

O

O

O

O

O

O

O

O

O

X

X

IPPROTO_IP

IP_TOS

IP_TTL

IP_MULTICAST_TTL

IP_MULTICAST_LOOP

IP_MULTICAST_IF

O

O

O

O

O

O

O

O

O

O

IPPROTO_TCP

TCP_KEEPALIVE

TCP_NODELAY

TCP_MAXSEG

O

O

O

O

O

O

 

 

소켓 옵션 가져오기 - getsockopt()

소켓 옵션 지정하기 - setsockopt()

소켓형태 (SO_TYPE) 가져오기 - stream, dgram

 

 

get_buf.c

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
 
void error_handling(char *message);
 
 
int main(int argc,char *argv[])
{
    int sock;
    int snd_buf,rcv_buf,state;
    socklen_t len;
 
    sock=socket(PF_INET,SOCK_STREAM,0);
    len=sizeof(snd_buf);
    state=getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len);
    if(state)
        error_handling("getsockopt() error");
 
    len=sizeof(rcv_buf);
    state=getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf,&len);
    if(state)
        error_handling("getsockopt() error");
 
    printf("Input buff size: %d\n",rcv_buf);
    printf("Output buffer size: %d\n",snd_buf);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

 

 

set_buf.c

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
 
void error_handling(char *message);
 
int main(int argc,char *argv[])
{
    int sock;
    int snd_buf=1024*3, rcv_buf=1024*3;
    int state;
    socklen_t len;
 
    sock=socket(PF_INET, SOCK_STREAM, 0);
    state=setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,sizeof(rcv_buf));
 
    if(state)
        error_handling("setsockopt() error!");
 
    state=setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,sizeof(snd_buf));
    if(state)
        error_handling("setsockopt() error!");
 
    len=sizeof(snd_buf);
    state=getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len);
    if(state)
        error_handling("getsockopt() error!");
 
    len=sizeof(rcv_buf);
    state=getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len);
    if(state)
        error_handling("getsockopt() error!");
 
    printf("Input buffer size: %d\n",rcv_buf);
    printf("Output buffer size: %d\n",snd_buf);
 
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

 

 

SO_REUSEADDR 옵션

주소 할당 시 에러(Binding Error)의 발생

- 프로그램이 강제 종료 되었을 경우 재실행하게 되면 서버측 bind()에러 발생

- 시스템이 열린 소켓을 잡고 있기 때문

 

이 경우 시스템이 열린 소켓을 해제할 때 까지 기다리지 않고 SO_REUSEADD 옵션을 사용하여 bind() 에러 방지

 

소켓의 재사용

- 같은 IP 주소와 port를 가진 소켓이 없어야 한다.

- 같은 IP 주소와 port를 가진 소켓이 있는 경우 SO_REUSEADDR 옵션이 TRUE로 셋팅되야 한다.

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
#define TRUE 1
#define FALSE 0
void error_handling(char *message);
 
int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char message[30];
    int option, str_len;
    socklen_t optlen, clnt_adr_sz;
    struct sockaddr_in serv_adr, clnt_adr;
    
    if(argc!=2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock==-1)
        error_handling("socket() error");
    
    optlen=sizeof(option);
    option=TRUE;    
    setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
    
 
    memset(&serv_adr, 0sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
 
    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)))
        error_handling("bind() error ");
    
    if(listen(serv_sock, 5)==-1)
        error_handling("listen error");
    clnt_adr_sz=sizeof(clnt_adr);    
    clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&clnt_adr_sz);
 
    while((str_len=read(clnt_sock,message, sizeof(message)))!= 0)
    {
        write(clnt_sock, message, str_len);
        write(1, message, str_len);
    }
    close(clnt_sock);
    return 0;
}
 
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4f; text-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

Time-wait

서버, 클라이언트에 상관없이 TCP 소켓에서 연결의 종료를 목적으로 Four-way handshaking의 첫번째 메시지를 전달하는 호스트의 소켓은 Time wait 상태를 거친다.

 

Time-wait 상태 동안에는 해당 소켓이 소멸되지 않아서 할당 받은 Port를 다른 소켓이 할당할 수 없다.

 

Time-wait는 길어질 수 있다: 서비스 중인 서버의 경우 Time-wait이 문제가 될 수 있다. 그러한 경우에는 TIme-wiat 상태에 있는 Port의 할당이 가능하도록 코드를 수정해야 한다.

 

TCP_NODELAY

Nagle 알고리즘

기존에 전송한 패킷이 있을경우 그 패킷에 ACK를 받아야 다음 전송을 진행하는 알고리즘

Nagle 알고리즘 미적용시 전송속도 향상효과, 네트워크 트래픽에 부하증가

 

Nagle 알고리즘의 중단요청 : TCP_NODELAY

- 1(TRUE)로 설정 : Nagle 알고리즘 미적용

- 0(FALSE)로 설정 : Nagle 알고리즘 적용

1
2
3
4
5
6
7
8
9
10
11
12
/* 디폴트 nagle알고리즘 설정 상태 확인 */
state=getsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
 &opt_val, &opt_len);
printf(" 디폴트 nagel알고리즘 : %s \n", opt_val ? "비설정" : "설정");
// nagel 알고리즘을 disable 시킨다
 
opt_val=TRUE;
state=setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
 &opt_val,sizeof(opt_val));
/* 변경된 nagle 알고리즘 확인 */
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt_val, &opt_len);
printf("변경된 nagle 알고리즘 : %s \n",opt_val ? "비설정" : "설정");
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none; color:white">cs

 

 

<netinet/tcp.h>

+ Recent posts