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()를 사용할 수 있다.
댓글 없음:
댓글 쓰기