Post Lists

2018년 7월 14일 토요일

6. Client-Server Background

http://beej.us/guide/bgnet/html/multi/clientserver.html

6. Client-Server Background
client-server의 세계이다. 네트워크에 대한 모든 것은 server process에게 말하는 클라이언트 프로세스를 다루고. 그 역도 해당한다. telnet을 예를들어 보자. 너가 포트번호 23에 telnet으로 원격 호스트에 connect할 때, 그 호스트에 대한 한 프로그램은 (telnetd라고 불려지고, 서버이다) 태어나게 되는 것이다. 그것은 들어오는 telnet connection을 다루고, 너가 로그인 프롬프트를 설정하게 한다. 등등.

클라이언트와 서버사이의 정보 교환은 위의 다이어그램에서 요약된다.

클라이언트-서버 쌍이 SOCK_STREAM, SOCK_DGRAM 또는 어떤 다른것을 말할 수 있다는 것에 주목해라. (그것들이 같은 것을 말하는 한) 클라이언트-서버 쌍의 좋은 예시는 telnet.telnetd, ftp/ftpd, 또는 Firefox/Apache이다. 너가 ftp를사용할 때 마다, 너를 위해 일을 하는 ftpd라는 원격 프로그램이 있따.

종종, 한 기계에 한 서버만이 있을것이고, 그 서버는 fork()를 사용하여 다양한 클라이언트들을 다룰 것이다. 기본 루틴은: 서버는 커넥션을 기다리고, 그것을 accept()하고, 그것을 다룰 자식 프로세스를 fork() 한다. 이것은 우리의 샘플 서버가 다음 섹션에서 하는 것이다.

6.1. A Simple Stream Server
이 서버가 하는 모든 것은 stream connection에 대해 "Hello, world!"라는 문자열을 보내는 것이다. 너가 이 서버를 테스트하기 위해 할 필요가 있는 것은 한 윈도우에 그것을 작동시키고, 다른 것으로부터 그것에 telnet하는 것이다:

  $ telnet remotehostname 3490

여기에서 remotehostname은 너가 작동하고 있는 machine의 이름이다.

서버 코드:


  1 /*
  2 server.c - a stream socket server demo
  3
  4    */
  5
  6 #include <stdio.h>
  7 #include <stdlib.h>
  8 #include <unistd.h>
  9 #include <errno.h>
 10 #include <string.h>
 11 #include <sys/types.h>
 12 #include <sys/socket.h>
 13 #include <netinet/in.h>
 14 #include <netdb.h>
 15 #include <arpa/inet.h>
 16 #include <sys/wait.h>
 17 #include <signal.h>
 18
 19 #define PORT "3490" // The port users will be connecting to
 20 #define BACKLOG 10 // how many pending connecctions queue will hold
 21
 22 void sigchild_handler(int s)
 23 {
 24         // waitpid() might overwirte errno, so we wave and restore it:
 25         int saved_errno = errno;
 26
 27         while(waitpid(-1, NULL, WNOHANG) > 0);
 28
 29         errno = saved_errno;
 30 }
 31
 32 // get sockaddr, IPv4 or IPv6:
 33 void* get_in_addr(struct sockaddr* sa)
 34 {
 35         if(sa->sa_family == AF_INET)
 36                 return &(((struct sockaddr_in*)sa)->sin_addr);
 37
 38         return &(((struct sockaddr_in6*)sa)->sin6_addr);
 39 }
 40
 41 int main(void)
 42 {
 43         int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
 44         struct addrinfo hints, *servinfo, *p;
 45         struct sockaddr_storage their_addr; // connector's address information
 46         socklen_t sin_size;
 47         struct sigaction sa;
 48         int yes = 1;
 49         char s[INET6_ADDRSTRLEN];
 50         int rv;
 51
 52         memset(&hints, 0, sizeof(hints));
 53         hints.ai_family = AF_UNSPEC;
 54         hints.ai_socktype = SOCK_STREAM;
 55         hints.ai_flags = AI_PASSIVE; // use my IP
 56
 57         if((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0)
 58         {
 59                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
 60                 return 1;
 61         }
 62
                                                    
 63         // loop through all the results and bind to the first we can
 64         for(p = servinfo; p != NULL; p = p->ai_next)
 65         {
 66                 if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
 67                 {
 68                         perror("server: socket");
 69                         continue;
 70                 }
 71
 72                 if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
 73                 {
 74                         perror("setsockopt");
 75                         exit(1);
 76                 }
 77
 78                 if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
 79                 {
 80                         close(sockfd);
 81                         perror("server: bind");
 82                         continue;
 83                 }
 84
 85                 break;
 86         }
 87
 88         freeaddrinfo(servinfo); // all don with this structure
 89
 90         if(p == NULL)
 91         {
 92                 perror("listen");
 93                 exit(1);
 94         }
 95
 96         if(listen(sockfd, BACKLOG) == -1)
 97         {
 98                 perror("listen");
 99                 exit(1);
100         }
101
102         sa.sa_handler = sigchild_handler; // reap all dead processes
103         sigemptyset(&sa.sa_mask);
104         sa.sa_flags = SA_RESTART;
105         if(sigaction(SIGCHLD, &sa, NULL) == -1)
106         {
107                 perror("sigaction");
108                 exit(1);
109         }
110
111         printf("server: waiting for connections...\n");
112
113         while(1)
114         {
115                 sin_size = sizeof(their_addr);
116                 new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size);
117                 if(new_fd == -1)
118                 {
119                         perror("accept");
120                         continue;
121                 }
122
123                 inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr*)&their_addr), s, sizeof(s));
124
125                 if(!fork())
126                         // this is the child process
127                 {
128                         close(sockfd); // child doesn't need the listener
129                         if(send(new_fd, "Hello, world!", 13, 0) == -1)
130                                 perror("send");
131                         close(new_fd);
132                         exit(0);
133                 }
134                 close(new_fd);
135         }
136
137         return 0;
138 }

너가 궁금한 경우에, 나는 하나의 큰 main() 함수에 (내가 느끼기에) 문장이 명료한 코드가 있다. 자유롭게 그것을 더 작은 함수로 나눌려고 해보아라. 만약 그것이 너를 더 기분좋게 만들면.

(또한, 이 전체 sigaction() 것들은 너에게 새로울 지도 모른다. - 그래, 이것은 fork()된 자식 프로세스가 종료할 때 나타나는 zombie processes들을 거두는데 책임이 있는 코드이다. 만약 너가 많은 좀비를 만들고 그것들을 처리하지 않으면, 너의 시스템 관리자는 기분이 안좋아 질것이다.)

너는 다음 섹션에 있는 클라이언트를 사용하여 이 서버로 부터 데이터를 얻을 수 있다.

6.2. A Simple Stream Client
이 놈은 서버보다 훨씬 더 쉽다. 이 클라이언트가 하는 모든 것은 너가 command line에 명시한 호스트의 포트 번호 3490에 연결하는 것이다. 그것은 서버가 보내는 문자열을 얻는다.

client source:


  1 /*
  2    client.c -- a stream socket client demo
  3    */
  4
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <errno.h>
  8 #include <string.h>
  9 #include <netdb.h>
 10 #include <sys/types.h>
 11 #include <netinet/in.h>
 12 #include <sys/socket.h>
 13
 14 #include <arpa/inet.h>
 15
 16 #define PORT "3490" // the port client will be connecting to
 17
 18 #define MAXDATASIZE 100 // max number of bytes we can get at once:
 19
 20 // get sockaddr, IPv4 or IPv6:
 21 void* get_in_addr(struct sockaddr* sa)
 22 {
 23         if(sa->sa_family == AF_INET)
 24                 return &(((struct sockaddr_in*)sa)->sin_addr);
 25
 26         return &(((struct sockaddr_in6*)sa)->sin6_addr);
 27 }
 28
 29 int main(int argc, char* argv[])
 30 {
 31         int sockfd, numbytes;
 32         char buf[MAXDATASIZE];
 33         struct addrinfo hints, *servinfo, *p;
 34         int rv;
 35         char s[INET6_ADDRSTRLEN];
 36
 37         if(argc != 2)
 38         {
 39                 fprintf(stderr, "usage: client hostname\n");
 40                 exit(1);
 41         }
 42
 43         memset(&hints,0, sizeof(hints));
 44         hints.ai_family = AF_UNSPEC;
 45         hints.ai_socktype = SOCK_STREAM;
 46
 47         if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0)
 48         {
 49                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
 50                 return 1;
 51         }
 52
 53         // loop through all the reulsts and connec to the first we can
 54         for(p = servinfo; p != NULL; p = p->ai_next)
 55         {
 56                 if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
 57                 {
 58                         perror("client: socket");
 59                         continue;
 60                 }
 61
 62                 if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1)
 63                 {
 64                         close(sockfd);
 65                         perror("client : connect");
 66                         continue;
 67                 }
 68
 69                 break;
 70         }
 71
 72         if(p == NULL)
 73         {
 74                 fprintf(stderr, "client : failed to connect\n");
 75                 return 2;
 76         }
 77
 78         inet_ntop(p->ai_family, get_in_addr((struct sockaddr*)p->ai_addr), s, sizeof(s));
 79         printf("client: connecting to %s\n", s);
 80
 81         freeaddrinfo(servinfo); // all done with this structure
 82
 83         if ((numbytes = recv(sockfd, buf, MAXDATASIZE - 1, 0)) == -1)
 84         {
 85                 perror("recv");
 86                 exit(1);
 87         }
 88
 89         buf[numbytes] = '\0';
 90
 91         printf("client: received '%s'\n", buf);
 92
 93         close(sockfd);
 94
 95         return 0;
 96 }

만약 너가 클라이언트를 작동시키기 전에 server작동시키지 않는다면, connect()는 "Connetion refused"를 반환한다는 것을 주목해라. 매우 유용하다.

6.3. Datagram Sockets
우리는 이미 위에서 sendto()와 recvfrom()의 토론으로 UDP datagram sockets의 기본을 다루었다. 그래서 나는 두 개의 샘플 프로그램을 보여줄 것이다: talker.c and listener.c.

listener는 포트 4950에서 들어오는 패킷을 기다리는 한 기계에 있다. talker는 특정한 머신에서 그 포트에 한 패킷을 보낸다. 그 패킷은 사용자가 커맨드 라인에 무엇을 치든 다 포함한다.

여기에서 listener.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
92
93
94
  1 /*
  2  listener.c -- a datagram sockets "server" demo
  3  */
  4
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <unistd.h>
  8 #include <errno.h>
  9 #include <string.h>
 10 #include <sys/types.h>
 11 #include <sys/socket.h>
 12 #include <netinet/in.h>
 13 #include <arpa/inet.h>
 14 #include <netdb.h>
 15
 16 #define MYPORT "4950" // the port users will be connecting to
 17
 18 #define MAXBUFLEN 100
 19
 20 // get sockaddr, IPv4 or IPv6:
 21 void* get_in_addr(struct sockaddr* sa)
 22 {
 23         if(sa->sa_family == AF_INET)
 24                 return &(((struct sockaddr_in*) sa)->sin_addr);
 25
 26         return &(((struct sockaddr_in6*) sa)->sin6_addr);
 27 }
 28
 29 int main(void)
 30 {
 31         int sockfd;
 32         struct addrinfo hints, *servinfo, *p;
 33         int rv;
 34         int numbytes;
 35         struct sockaddr_storage their_addr;
 36         char buf[MAXBUFLEN];
 37         socklen_t addr_len;
 38         char s[INET6_ADDRSTRLEN];
 39
 40         memset(&hints, 0, sizeof(hints));
 41         hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
 42         hints.ai_socktype = SOCK_DGRAM;
 43         hints.ai_flags = AI_PASSIVE; // use my IP
 44
 45         if((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0)
 46         {
 47                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
 48                 return 1;
 49         }
 50
 51         // loop through all the results and bind to the first we can
 52         for(p = servinfo; p != NULL; p = p->ai_next)
 53         {
 54                 if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
 55                 {
 56                         perror("listener: socket");
 57                         continue;
 58                 }
 59
 60                 if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1)
 61                 {
 62                         close(sockfd);
                                                                                                                   28,0-1        Top
 63                         perror("listener: bind");
 64                         continue;
 65                 }
 66
 67                 break;
 68         }
 69
 70         if(p == NULL)
 71         {
 72                 fprintf(stderr, "listener: failed to bind socket\n");
 73                 return 2;
 74         }
 75
 76         freeaddrinfo(servinfo);
 77
 78         if((numbytes = recvfrom(sockfd, buf, MAXBUFLEN - 1, 0, (struct sockaddr*)&their_addr, &addr_len)) == -1)
 79         {
 80                 perror("recvfrom");
 81                 exit(1);
 82         }
 83
 84         printf("listener: got packet from %s\n", inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr*) &their_addr),
 85                                 s, sizeof(s)));
 86         printf("listener: packet is %d bytes long\n", numbytes);
 87         buf[numbytes] = '\0';
 88         printf("listener: packet contains \"%s\"\n", buf);
 89
 90         close(sockfd);
 91
 92         return 0;
 93 }

getaddrinfo()에 대한 호출에서 우리는 마침내 SOCK_DGRAM을 사용하고 있다는 것에 유의해라. 또한 listen() 또는 accept()를 할 필요가 없다. 이것이 unconnected datagram sockets을 사용하는 것의 특전 중의 하나이다.

다음은 talker.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
  1 /*
  2  * talker.c -- a datagram "client" demo
  3  */
  4
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <unistd.h>
  8 #include <errno.h>
  9 #include <string.h>
 10 #include <sys/types.h>
 11 #include <sys/socket.h>
 12 #include <netinet/in.h>
 13 #include <arpa/inet.h>
 14 #include <netdb.h>
 15
 16 #define SERVERPORT "4950" // the port users will be connencting to
 17
 18 int main(int argc, char* argv[])
 19 {
 20         int sockfd;
 21         struct addrinfo hints, *servinfo, *p;
 22         int rv;
 23         int numbytes;
 24
 25         if(argc != 3)
 26         {
 27                 fprintf(stderr, "usage : talker hostname message\n");
 28                 exit(1);
 29         }
 30
 31         memset(&hints, 0, sizeof(hints));
 32         hints.ai_family = AF_UNSPEC;
 33         hints.ai_socktype = SOCK_DGRAM;
 34
 35         if((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0)
 36         {
 37                 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
 38                 return 1;
 39         }
 40
 41         // loop through all the results and make a socket
 42         for(p = servinfo; p != NULL; p = p->ai_next)
 43         {
 44                 if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
 45                 {
 46                         perror("talker : socket");
 47                         continue;
 48                 }
 49
 50                 break;
 51         }
 52
 53         if(p == NULL)
 54         {
 55                 fprintf(stderr, "talker: failed to create socket\n");
 56                 return 2;
 57         }
 58
 59         if((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0, p->ai_addr, p->ai_addrlen)) == -1)
 60         {
 61                 perror("talker : sendto");
 62                 exit(1);
 63         }
 64
 65         freeaddrinfo(servinfo);
 66
 67         printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);
 68         close(sockfd);
 69
 70
 71         return 0;
 72 }

그리고 이게 다이다. 어떤 머신에서 listener를 작동시키고나서, 다른 것에서 talker를작동시켜라. 그것들이 통신하는 것을 보아라.

너는 심지어 이번에 서버를 작동시킬 필요가 없다! 너는 talker 그 자체를 작동시킬 수 있고, 그것은 그냥 행복하게 패킷을 어딘가로 보내버린다. 그리고 거기에서 만약 어떤 다른 side에서 누구도 recvfrom()할 준비가 되어있지 않다면 그것들은 사라진다. 기억해라 : UDP datagram sockets을 사용하여 보낸 데이터는 도착하는 것이 보장되지 않는다.

이전에 내가 여러 번 언급한 한 가지 더 아주 작은 세부사항을제외하고: connected datagram sockets. 나는 이것에 대해서 여기서 말할 필요가 있다. 우리가 datagram section에 있기 때문이다. talker가 connect()를 호출하고, listener의 주소를 명시한다고 가정하자. 그 시점으로 부터, talker는 connect()에 의해 명시된 그 주소로 보내거나, 그 주소로 부터 받기만할지도 모른다. 이 이유 때문에, 너는 sendto()와 recvfrom()을 사용할 필요가 없다; 너는 간단히 send()와 recv()를 사용할 수 있다.


댓글 없음:

댓글 쓰기