프로세스 기반 다중접속 서버 모델
● 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, 0, sizeof(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, 0, sizeof(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, 0, sizeof(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, 0, sizeof(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>
'딥러닝 기반 영상인식 개발 전문가 과정 > 리눅스' 카테고리의 다른 글
6월13일 실습3, 표준 입출력 함수, send,recv (0) | 2019.06.13 |
---|---|
6월13일 실습, IO Multiplexing, select (0) | 2019.06.13 |
6월 11일 echo,op server/client 소스, UDP (0) | 2019.06.11 |
6월11일 Socket, DNS (0) | 2019.06.11 |
6월10일 소켓, 서버/클라이언트 (0) | 2019.06.10 |