12 장 소켓을이용한통신 (1) 함수 - inet_addr - inet_ntoa - socket - bind - listen - accept - connect - recv -send 1
서론 파이프를사용하여통신을하기위한시스템호출 / 표준라이브러리함수 함수 의미 inet_addr 문자열형태의인터넷주소를바이너리형태로변환한다. inet_ntoa 바이너리형태의인터넷주소를문자열형태로변환한다. socket 통신에사용하기위해소켓을생성한다. bind 호스트의로컬주소를소켓과연결한다. listen 소켓을연결요청대기상태로만든다. accept 연결요청을수락한다. connect 연결을요청한다. recv 소켓을통해데이터를수신한다. send 소켓을통해데이터를전송한다. 2
TCP 를사용한연결형통신모델에서의함수호출 3
예제 12-1 ex 12-01s.c, 서버 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define SIZE sizeof(struct sockaddr_in) main() { char c; int sockfd_listen, sockfd_connect; struct sockaddr_in server = {AF_INET, 5000, INADDR_ANY; sockfd_listen = socket(af_inet, SOCK_STREAM, 0); bind(sockfd_listen, (struct sockaddr *) &server, SIZE); listen(sockfd_listen, 5); sockfd_connect = accept(sockfd_listen, NULL, NULL); recv(sockfd_connect, &c, 1, 0); send(sockfd_connect, &c, 1, 0); close(sockfd_connect); close(sockfd_listen); 4
예제 12-1 ex 12-01c.c, 클라이언트 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define SIZE sizeof(struct sockaddr_in) main() { int sockfd; char send_c = A, recv_c; struct sockaddr_in server = {AF_INET, 5000; server.sin_addr.s_addr = inet_addr("127.0.0.1"); sockfd = socket(af_inet, SOCK_STREAM, 0); connect(sockfd, (struct sockaddr *) &server, SIZE); send(sockfd, &send_c, 1, 0); recv(sockfd, &recv_c, 1, 0); close(sockfd); 5
2. inet_addr, inet_ntoa 인터넷주소를문자열에서바이너리로또는바이너리에서문자열로변환한다 #include <arpa/inet.h> in_addr_t inet_addr(const char *ip_addr); char *inet_ntoa(struct in_addr n_addr); ip_addr 문자열로표현된인터넷호스트주소이다. n_addr 바이너리로표현된인터넷호스트주소이다 반환값 inet_addr 은호출이성공하면바이너리형식으로변환된인터넷주소가반환되고, 실패하면 -1 이반환된다. inet_ntoa 는호출이성공하면문자열형식으로변환된인터넷주소의포인터를반환한다. 문자열형식의인터넷주소 202.31.200.123 과같이문자열로구성된인터넷주소 실제통신을위해서는바이너리로변환해야한다. 6
예제 12-2 ex 12-02.c #include <arpa/inet.h> #include <unistd.h> main() { char *valid = "197.0.0.1"; char *invalid = "300.0.0.1"; in_addr_t ipaddr1; struct in_addr ipaddr2; if ((ipaddr1 = inet_addr(valid)) == -1) printf("invalid: %s n", valid); else printf("valid: %d.%d.%d.%d n", (ipaddr1 >> 0) & 0xFF, (ipaddr1 >> 8) & 0xFF, (ipaddr1 >> 16) & 0xFF, (ipaddr1 >> 24) & 0xFF ); ipaddr2.s_addr = ipaddr1; if ((ipaddr1 = inet_addr(invalid)) == -1) printf("invalid: %s n", invalid); else printf("vaild: %x n", ipaddr1); printf("%s n", inet_ntoa(ipaddr2)); 7
3. socket 프로세스간통신을위한 socket 을생성한다 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); domain 통신도메인 ( 통신에사용되는 protocol family) 을지정한다. type 통신방법을선택한다. protocol 선택된프로토콜군 (family & type) 에서특정프로토콜을선택한다. 대부분의프로토콜군은하나의프로토콜을갖고있다. 반환값 호출이성공하면소켓기술자를반환하고실패하면 -1 을반환한다. 파일을다루기위해파일기술자가필요한것처럼통신에는소켓이필요하다. 소켓을생성할때다음을결정한다. 프로토콜 family 연결형또는비연결형 8
3. socket int domain; // protocol family 지정 이름 ( 매크로상수 ) 의미 AF_UNIX, AF_LOCAL 로컬통신 ( 동일한시스템에있는프로세스간의통신이다.) AF_INET IP version 4 의인터넷통신프로토콜 AF_INET6 IP Version 6 의인터넷통신프로토콜 AF_IPX 노벨네트워크의통신프로토콜 int type; // 통신유형지정 이름 ( 매크로상수 ) 의미 SOCK_STREAM 연결형통신을사용한다. SOCK_DGRAM 비연결형통신을사용한다. int protocol; // 통신프로토콜지정 보통 0 을설정 ( 선택한 protocol family 와통신유형에맞는프로토콜은 하나뿐이다. 연결형이면 TCP, 비연결형이면 UDP 가선택된다.) 9
예제 12-3 ex 12-03.c #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> main() { int sockfd; if ((sockfd = socket(af_inet, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "fail to call socket() n"); exit(1); printf("socket descriptor is %d n", sockfd); /* 소켓을통한통신기능을수행한다. */ close(sockfd); $ ex12-03 socket descriptor is 3 $ 10
4. bind 서버주소를소켓과연결시킨다 #include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); sockfd socket() 으로미리생성된소켓기술자이다. my_addr 서버주소이다. addrlen my_addr 의바이트길이이다. 반환값 호출이성공하면 0 을반환하고, 실패하면 -1 을반환한다. SOCK_STREAM 으로설정되어있고서버쪽소켓일경우 bind 를 사용하여로컬주소를소켓에연결해야한다. my_addr 에대한통신시도를 sockfd 로지정한소켓으로 전달하도록한다. 11
4. bind 서버의주소를나타내는구조체 struct sockaddr 과 struct sockaddr_in struct sockaddr 동일시스템내의프로세스간통신에사용 struct sockaddr_in 서로다른시스템에있는프로세스간통신에사용 bind() 의두번째인자로적용할수있는데이터형식은 struct sockaddr 형뿐이므로 struct sockaddr_in 형을쓸경우 struct sockaddr 형으로변환 12
4. bind struct sockaddr 과 struct sockaddr_in struct sockaddr { sa_family_t sa_family; /* 주소계열, AF_xxx */ char sa_data[14]; /* 프로토콜주소 */ ; #include <netinet/in.h> struct sockaddr_in { sa_family_t sin_family; /* 주소계열, AF_xxx */ unsigned short int sin_port; /* 포트번호 */ struct in_addr sin_addr; /* 인터넷주소 */ char sin_zero[8]; /* struct sockaddr 과크기를 */ /* 맞추기위한부분 */ ; 13
예제 12-4 ex 12-04.c #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #define SIZE sizeof(struct sockaddr_in) main() { int sockfd; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = 1004; addr.sin_addr.s_addr = INADDR_ANY; /* socket() 으로소켓을생성하는코드 */ if (bind(sockfd, (struct sockaddr *) &addr, SIZE) == -1) { fprintf(stderr, "fail to call bind() n"); exit(1); /* 통신을위한나머지코드들 */ close(sockfd); 14
5. listen 지정한소켓을연결요청을받을수있는상태로만든다. #include <sys/socket.h> int listen(int sockfd, int backlog); sockfd socket() 으로미리생성된소켓기술자이다. backlog 큐의사이즈다. 큐는동시에들어오는여러연결요청을넣는데사용됨. 반환값 호출이성공하면 0 을반환하고, 실패하면 -1 을반환한다. SOCK_STREAM 으로연결형통신이설정된서버쪽소켓에지정한다. 이렇게지정된소켓은메시지를주고받는실제통신에사용되는것이아니라클라이언트의연결요청을받아들이는용도로사용된다. 클라이언트는 connect() 를호출하여연결을요청한다. 서버는 accept() 로연결요청을수락한다. 15
5. listen int backlog 동시에받아들일수있는연결요청의수를의미한다. 동시에여러개의연결요청이있을경우 먼저들어온요청을처리하고나머지는 backlog 에서 지정한크기의큐에서대기하게된다. backlog 는 0 이될수없다. 16
예제 12-5 ex 12-05.c #include <...h> #define SIZE sizeof(struct sockaddr_in) main() { char c; int sockfd_listen; struct sockaddr_in server = {AF_INET, 5000, INADDR_ANY; /* socket() 을호출하는부분 */ /* bind() 를호출하는부분 */ if (listen(sockfd_listen, 5) == -1) { fprintf(stderr, "fail to call listen() n"); exit(1); /* 통신을수행하는부분 */ 17
6. accept 연결형소켓으로통신을할때상대의연결요청을수락한다 #include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockfd socket() 으로미리생성된소켓기술자이다. addr 연결요청한상대에대한정보가저장된다. addrlen addr 의바이트길이이다. 반환값 호출이성공하면음수가아닌정수형의새로운소켓기술자가반환되고, 실패하면 -1 이반환된다. 연결형통신모델에서서버쪽프로세스가사용한다. accept 를호출하면 연결요청대기큐의첫번째연결요청을가져온다. 실제통신을위한새로운소켓기술자를할당한다. 18
6. accept listen 의소켓과 accept 가만드는새로운소켓 listen 의소켓 클라이언트의연결요청을받기위한용도의소켓이다. 실제메시지를주고받는용도로사용되지않는다. accept 가만드는새로운소켓 listen 의소켓으로들어온클라이언트의연결요청에응한결과 클라이언트와실제메시지를주고받기위해새롭게만들어진다. struct sockaddr *addr 연결을요청한클라이언트쪽프로세스에대한정보로채워진다. 19
예제 12-6 ex 12-06.c(1/2) #define SIZE sizeof(struct sockaddr_in) int sockfd_connect; main() { char c; int sockfd_listen; struct sockaddr_in server = {AF_INET, 5000, INADDR_ANY; if ((sockfd_listen = socket(af_inet, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "fail to call socket() n"); exit(1); if (bind(sockfd_listen, (struct sockaddr *) &server, SIZE) == -1) { fprintf(stderr, "fail to call bind() n"); exit(1); 20
예제 12-6 ex 12-06.c(2/2) if (listen(sockfd_listen, 5) == -1) { fprintf(stderr, "fail to call listen() n"); exit(1); while (1) { if((sockfd_connect = accept(sockfd_listen, NULL, NULL)) == -1) { fprintf(stderr, "fail to call accept() n"); continue; /* sockfd_connect 를사용하여통신을수행 */ 21
7. connect listen 상태로대기중인서버프로세스에연결을요청한다 #include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); sockfd 소켓기술자이다. serv_addr 통신을연결할상대의주소등에관한정보가저장되어있다. addrlen serv_addr 의크기이다. 반환값 호출이성공하면 0 을반환하고, 실패하면 -1 을반환한다. serv_addr 연결을요청할대상인서버쪽에대한정보가담겨있다. 22
예제 12-7 ex 12-07.c #define SIZE sizeof(struct sockaddr_in) main() { int sockfd; char send_c, recv_c; struct sockaddr_in server = {AF_INET, 5000; server.sin_addr.s_addr = inet_addr("127.0.0.1"); if ((sockfd = socket(af_inet, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "fail to call socket() n"); exit(1); if (connect(sockfd, (struct sockaddr *) &server, SIZE) == -1) { fprintf(stderr, "fail to call connect() n"); exit(1); /* 메시지를주고받는부분 */ 23
8. send, recv 지정한소켓을사용하여메시지를전송하거나수신한다. #include <sys/types.h> #include <sys/socket.h> int send(int sockfd, const void *msg, size_t msg_len, int flags); int recv(int sockfd, void *msg, size_t msg_len, int flags); sockfd 메시지를전송하거나수신할소켓기술자이다. msg 전송하거나수신한메시지를저장하고있다. msg_len msg 의길이이다. flags 메시지를전송하거나수신하는방법을선택한다. 반환값 send 는호출이성공할경우전송한문자수를반환하고, 실패할경우 -1 을반환한다. recv 는호출이성공할경우수신한바이트수를반환하고, 실패할경우 -1 을반환한다. 24
8. send, recv int sockfd 실제메시지를주고받을소켓의기술자 서버쪽은 accept 호출로만들어진소켓이고클라이언트쪽은 connect 를호출할때사용한소켓이다. const void msg 상대프로세스에게전송할또는상대프로세스로부터수신한메시지를담고있는버퍼이다. size_t msg_len 송수신할메시지의길이이다. 실제메시지의길이와상관없이 msg_len 으로지정한크기만큼송수신한다. 25
8. send, recv int flags send 와 recv 의송수신방법을결정한다. 보통 0 을설정한다. 이경우 write, read 와동일하게동작한다. 함수 send recv 심볼형상수 MSG_OOB MSG_DONTROUTE MSG_NOSIGNAL MSG_OOB MSG_PEEK MSG_NOSIGNAL 의미 대역을벗어난데이터를보낸다. 프로토콜이대역을벗어난데이터를지원해야한다. 직접연결된네트워크에존재하는호스트로전송할때게이트웨이를사용하지않고전송한다. 연결형소켓에서상대프로세스가연결을끊었을때발생하는 SIGPIPE 시그널을받지않는다. 일단데이터가아닌대역을벗어난데이터를수신한다. 도착한메시지큐에서첫번째메시지를제거하지않고읽어온다. 일반적인호출이라면큐에서메시지를가져오면서제거한다. 연결형소켓에서상태프로세스가연결을끊었을때발생하는 SIGPIPE 시그널을받지않는다. 26
예제 12-8 ex 12-08.c #define SIZE sizeof(struct sockaddr_in) int sockfd_connect; main() { /* sock_listen 소켓생성 */ /* sock_listen 으로 bind 호출 */ /* sock_listen 으로 listen 호출 */ while(1) { /* accept 를호출하여 sockfd_connet 를생성 */ while (recv(sockfd_connect, &c, 1, 0) > 0) send(sockfd_connect, &c, 1, 0); close(sockfd_connect); 27
9. close 와연결해제 소켓또는소켓기술자 통신을완료하여더이상필요없어진소켓은닫는다. 어느한쪽의프로세스가비정상적으로종료되는경우 비정상적으로끊어진상대방의소켓에대해서메시지송신이나수신을 시도하면 SIGPIPE 시그널이발생한다. 유연한대처를위해 SIGPIPE 시그널을처리하기위한대비가있어야한다. ex12-09.c while(1) { sockfd_connect = accept(sockfd_listen, NULL, NULL); /* send, recv 를호출하는부분 */ close(sockfd_connect); 28
예제프로그램완성 연결형모델, 서버쪽 (1/2) #define SIZE sizeof(struct sockaddr_in) int sockfd_connect; void closesock(int sig) { close(sockfd_connect); exit(0); main() { char c; int sockfd_listen; struct sockaddr_in server = {AF_INET, 5000, INADDR_ANY; signal(sigpipe, closesock); if ((sockfd_listen = socket(af_inet, SOCK_STREAM, 0)) == -1) exit(-1); if (bind(sockfd_listen, (struct sockaddr *) &server, SIZE) == -1) exit(-1); if (listen(sockfd_listen, 5) == -1) exit(-1); 29
예제프로그램완성 연결형모델, 서버쪽 (2/2) while (1) { if ((sockfd_connect = accept(sockfd_listen, NULL, NULL)) == -1) continue; while (recv(sockfd_connect, &c, 1, 0) > 0) send(sockfd_connect, &c, 1, 0); close(sockfd_connect); 30
예제프로그램완성 클라이언트쪽 main() { int sockfd; char send_c, recv_c; struct sockaddr_in server = {AF_INET, 5000; server.sin_addr.s_addr = inet_addr("127.0.0.1"); sockfd = socket(af_inet, SOCK_STREAM, 0)); connect(sockfd, (struct sockaddr *)&server, SIZE); while (1) { send_c = getchar(); send(sockfd, &send_c, 1, 0); if (recv(sockfd, &recv_c, 1, 0) > 0) printf("%c", recv_c); else { close(sockfd); exit(1); 31