소켓프로그래밍기초 IT CookBook, 유닉스시스템프로그래밍
학습목표 TCP/IP 프로토콜의기본개념을이해한다. IP 주소와포트번호의개념을이해한다. 소켓관련구조체와함수를이해한다. 소켓을이용한통신프로그램을작성할수있다. 2/42
목차 TCP/IP 개요 IP 주소와호스트명 포트번호 소켓프로그래밍기초 소켓인터페이스함수 유닉스도메인소켓예제 인터넷소켓예제 3/42
TCP/IP 개요 TCP/IP 인터넷의표준프로토콜 5 계층 (4 계층 ) 으로구성 TCP 와 UDP 의차이 4/42
IP 주소와호스트명 [1] IP 주소와호스트명 IP주소 : 인터넷을이용할때사용하는주소로점 (.) 으로구분된 32비트숫자 호스트명 :. 시스템에부여된이름 호스트명 ( 도메인명 ) 과 IP주소를관리하는서비스 -> DNS 호스트명과 IP 주소변환 /etc/hosts 파일또는 DNS, NIS 등 /etc/nsswitch.conf 파일에주소변환을누가할것인지지정 hosts: files dns 호스트명과주소읽어오기 : gethostent(3), sethostent(3), endhostent(3) #include <netdb.h> struct hostent *gethostent(void); int sethostent(int stayopen); int endhostent(void); 5/42
IP 주소와호스트명 [1] gethostent : 호스트명과 IP 주소를읽오 hostent 구조체에저장 sethostent : 데이터베이스의읽기위치를시작위치로재설정 endhostent : 데이터베이스를닫는다 hostent 구조체 struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; } 6/42
[ 예제 11-1] 호스트명읽어오기 01 #include <netdb.h> 02 #include <stdio.h> 03 04 int main(void) { 05 struct hostent *hent; 06 07 sethostent(0); 08 09 while ((hent = gethostent())!= NULL) 10 printf("name=%s\n", hent->h_name); 11 12 endhostent(); 13 14 return 0; 15 } 처음위치로이동 DB 닫기 ex11_1.c DB 의내용을차례로읽어오기 # gcc ex11_1.c 정의되지않음 첫번째참조된 왜실행파일이생성되지않을까? 기호 파일 : endhostent /var/tmp//ccwqu9hn.o gethostent /var/tmp//ccwqu9hn.o sethostent /var/tmp//ccwqu9hn.o ld: 치명적 : 기호참조오류. a.out에출력이기록되지않음 collect2: ld returned 1 exit status 7/42
[ 예제 11-1] 실행결과 표준 C 라이브러리에없는함수들이기때문에 endhostent, gethostent, sethostent libnsl.so 라이브러리를링크해야 -> /usr/lib 디렉토리에위치 # gcc -o ex11_1.out ex11_1.c -lnsl /etc/host 파일의내용이다음과같을때 # cat /etc/hosts # # Internet host table # 127.0.0.1 localhost 218.237.65.4 www.hanb.co.kr 192.168.162.133 hanbit 실행결과 # ex11_1.out Name=localhost Name=www.hanb.co.kr Name=hanbit 8/42
IP 주소와호스트명 [2] 호스트명으로정보검색 : gethostbyname(3) #include <netdb.h> struct hostent *gethostbyname(const char *name); IP 주소로정보검색 : gethostbyaddr(3) #include <netdb.h> struct hostent *gethostbyaddr(const char *addr, int len, int type); type 에지정할수있는값 AF_UNSPEC 0 /* 미지정 */ AF_UNIX 1 /* 호스트내부통신 */ AF_INET 2 /* 인터네트워크통신 : UDP, TCP 등 */ AF_IMPLINK 3 /* Arpanet의 IMP 주소 */ AF_PUP 4 /* PUP 프로토콜 : BSP 등 */ AF_CHAOS 5 /* MIT의 CHAOS 프로토콜 */ AF_NS 6 /* XEROX의 NS 프로토콜 */ AF_NBS 7 /* NBS 프로토콜 */ 9/42
포트번호 [1] 포트번호 호스트에서동작하고있는서비스를구분하는번호 2바이트정수로 0~65535까지사용가능 잘알려진포트 : 이미정해져있고자주사용하는포트 텔넷 (23), HTTP(80), FTP(21) 관련파일 : /etc/services 포트정보읽어오기 : getservent(3), setservent(3), endservent(3) #include <netdb.h> struct servent *getservent(void); int setservent(int stayopen); int endservent(void); getservent : 포트정보를읽어 servent 구조체로리턴 setservent : 읽기위치를시작으로재설정 endservent : 데이터베이스닫기 struct servent { char *s_name; char **s_aliases; int s_port; char **s_proto; } 10/42
[ 예제 11-2] getservent 함수로포트정보읽어오기 ex11_2.c 01 #include <netdb.h> 02 #include <stdio.h> 03 04 int main(void) { 05 struct servent *port; 06 int n; 07 08 setservent(0); 09 10 for (n = 0; n < 5; n++) { 11 port = getservent(); 12 printf("name=%s, Port=%d\n", port->s_name, port->s_port); 13 } 14 15 endservent(); 16 17 return 0; 18 } 처음위치로이동 DB 닫기 socket 라이브러리를지정해서컴파일해야한다. # gcc o ex11_2.out ex11_2.c -lsocket DB 의내용을차례로 5 개만읽어오기 # ex11_2.out Name=tcpmux, Port=256 Name=echo, Port=1792 Name=echo, Port=1792 Name=discard, Port=2304 Name=discard, Port=2304 11/42
포트번호 [2] 서비스명으로정보검색 : getservbyname(3) #include <netdb.h> struct servent *getservbyname(const char *name, const char *proto); name : 검색할포트명 proto : tcp 또는 udp 또는 NULL 포트번호로정보검색 : getservbyport(3) #include <netdb.h> struct servent *getservbyport(int port, const char *proto); proto : tcp 또는 udp 또는 NULL 12/42
소켓프로그래밍기초 [1] 소켓의종류 AF_UNIX : 유닉스도메인소켓 ( 시스템내부프로세스간통신 ) AF_INET : 인터넷소켓 ( 네트워크를이용한통신 ) 소켓의통신방식 SOCK_STREAM : TCP 사용 SOCK_DGRAM : UDP 사용 AF_UNIX SOCK_STREAM AF_UNIX SOCK_DGRAM AF_INET SOCK_STREAM AF_INET SOCK_DGRAM 13/42
소켓프로그래밍기초 [2] 소켓주소구조체 유닉스도메인소켓의주소구조체 struct sockaddr_un { sa_family_t sun_famyly; char sun_path[108] }; sun_family : AF_UNIX sun_path : 통신에사용할파일의경로명 인터넷소켓의주소구조체 struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; }; struct in_addr { in_addr_t s_addr; }; 14/42
소켓프로그래밍기초 [3] 바이트순서함수 정수를저장하는방식 : 빅엔디안, 리틀엔디안 빅엔디안 : 메모리의낮은주소에정수의첫바이트를위치 -> 모토로라, 썬 리틀엔디안 : 메모리의높은주소에정수의첫바이트를위치 -> 인텔 TCP/IP 네트워크에서바이트순서표준 : 빅엔디안 호스트바이트순서 (HBO) : 시스템에서사용하는바이트순서 네트워크바이트순서 (NBO) : 네트워크에서사용하는바이트순서 #include <sys/types.h> #include <netinet/in.h> #include <inttypes.h> uint32_t htonl(unit32_t hostlong); uint16_t htons(unit16_t hostshort); uint32_t ntohl(unit32_t netlong); uint16_t ntohs(unit16_t netshort); htonl :32비트 HBO를 32비트 NBO로변환 htons : 16비트 HBO를 16비트 NBO로변환 ntohl : 32비트 NBO를 32비트 HBO로변환 ntohs : 16비트 NBO를 16비트 HBO로변환 15/42
[ 예제 11-3] NBO 를 HBO 로변환하기 ex11_3.c 01 #include <netdb.h> 02 #include <stdio.h> 03 04 int main(void) { 05 struct servent *port; 06 int n; 07 08 setservent(0); 09 10 for (n = 0; n < 5; n++) { 11 port = getservent(); 12 printf("name=%s, Port=%d\n", port->s_name, ntohs(port->s_port)); 13 } 14 15 endservent(); 16 17 return 0; 18 } NBO 를 HBO 로변환하기위한함수호출 # ex11_3.out Name=tcpmux, Port=1 Name=echo, Port=7 Name=echo, Port=7 Name=discard, Port=9 Name=discard, Port=9 16/42
[ 예제 11-4] HBO 를 NBO 로변환하기 ex11_4.c 01 #include <netdb.h> 02 #include <stdio.h> 03 04 int main(void) { 05 struct servent *port; 06 07 port = getservbyname("telnet", "tcp"); 08 printf("name=%s, Port=%d\n", port->s_name, ntohs(port->s_port)); 09 10 port = getservbyport(htons(21), "tcp"); 11 printf("name=%s, Port=%d\n", port->s_name, ntohs(port->s_port)); 12 13 return 0; 14 } 이름으로서비스포트번호검색 HBO 를 NBO 로변환하여포트번호검색 # ex11_4.out Name=telnet, Port=23 Name=ftp, Port=21 17/42
IP 주소변환함수 IP 주소의형태 192.168.10.1과같이점 (.) 으로구분된형태 시스템내부저장방법 : 이진값으로바꿔서저장 외부적사용형태 : 문자열로사용 문자열행태의 IP 주소를숫자형태로변환 : inet_addr(3) #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> in_addr_t inet_addr(const char *cp); 구조체형태의 IP 주소를문자열형태로변환 : inet_ntoa(3) #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> char *inet_ntoa(const struct in_addr in); 18/42
[ 예제 11-5] IP 주소변환하기 ex11_5.c... 09 int main(void) { 10 in_addr_t addr; 11 struct hostent *hp; 12 struct in_addr in; 13 14 if ((addr = inet_addr("218.237.65.4")) == (in_addr_t)-1) { 15 printf("error : inet_addr(218.237.65.4\n"); 16 exit(1); 17 } 18 주소로호스트명검색 19 hp = gethostbyaddr((char *)&addr, 4, AF_INET); 20 if (hp == NULL) { 21 (void) printf("host information not found\n"); 22 exit(2); 23 } 24 25 printf("name=%s\n", hp->h_name); 26 27 (void) memcpy(&in.s_addr, *hp->h_addr_list, sizeof (in.s_addr)); 0; 28 printf("ip=%s\n", inet_ntoa(in)); 29 30 return 0; 31 } 문자열행태를이진형태로변환 구조체형태에서문자열로변환하여출력 # gcc -o ex11_5.out ex11_5.c -lsocket -lnsl # ex11_5.out Name=www.hanb.co.kr IP=218.237.65.4 19/42
소켓인터페이스함수 [1] 소켓인터페이스함수 socket : 소켓파일기술자생성 bind : 소켓파일기술자를지정된 IP 주소 / 포트번호와결합 (bind) listen : 클라이언트의접속요청대기 connect : 클라이언트가서버에접속요청 accept : 클라이언트의접속허용 recv : 데이터수신 (SOCK_STREAM) send : 데이터송신 (SOCK_STREAM) recvfrom : 데이터수신 (SOCK_DGRAM) sendto : 데이터송신 (SOCK_DGRAM) close : 소켓파일기술자종료 20/42
소켓인터페이스함수 [2] 소켓생성하기 : socket(2) #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); domain : 소켓종류 (AF_UNIX, AF_INET) type : 통신방식 (TCP, UDP) protocol : 소켓에이용할프로토콜 int sd; sd = socket(af_inet, SOCK_STREAM, 0); 21/42
소켓인터페이스함수 [3] 소켓에이름지정하기 : bind(3) #include <sys/types.h> #include <sys/socket.h> int bind(int s, const struct sockaddr *name, int namelen); name : 소켓의이름을표현하는구조체 int sd; struct sockaddr_in sin; memset((char *)&sin, '\0', sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(9000); sin.sin_addr.s_addr = inet_addr("192.168.100.1"); memset(&(sin.sin_zero), 0, 8); bind(sd, (struct sockaddr *)&sin, sizeof(struct sockaddr)); 22/42
소켓인터페이스함수 [4] 클라이언트연결기다리기 : listen(3) #include <sys/types.h> #include <sys/socket.h> int listen(int s, int backlog); backlog : 최대허용클라이언트수 listen(sd, 10); 연결요청수락하기 : accept(3) #include <sys/types.h> #include <sys/socket.h> int accept(int s, struct sockaddr *addr, socklen_t *addrlen); addr: 접속을요청한클라이언트의 IP 정보 int sd, new_sd; struct sockaddr_in sin, clisin; new_sd = accept(sd, &clisin, &sizeof(struct sockaddr_in)); 23/42
소켓인터페이스함수 [5] 서버와연결하기 : connect(3) #include <sys/types.h> #include <sys/socket.h> int connect(int s, const struct sockaddr *name, int namelen); name : 접속하려는서버의 IP 정보 int sd; struct sockaddr_in sin; memset((char *)&sin, '\0', sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(9000); sin.sin_addr.s_addr = inet_addr("192.168.100.1"); memset(&(sin.sin_zero), 0, 8); connect(sd, (struct sockaddr *)&sin, sizeof(struct sockaddr)); 24/42
소켓인터페이스함수 [6] 데이터보내기 : send(3) #include <sys/types.h> #include <sys/socket.h> ssize_t send(int s, const void *msg, size_t len, int flags); char *msg = "Send Test\n"; int len = strlen(msg) + 1; if (send(sd, msg, len, 0) == -1) { perror("send"); exit(1); } 데이터받기 : recv(3) #include <sys/types.h> #include <sys/socket.h> ssize_t recv(int s, void *buf, size_t len, int flags); char buf[80]; int len, rlen; if ((rlen = recv(sd, buf, len, 0)) == -1) { perror("recv"); exit(1); } 25/42
소켓인터페이스함수 [7] UDP 데이터보내기 : sendto(3) #include <sys/types.h> #include <sys/socket.h> ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen); to : 메시지를받을호스트의주소 char *msg = "Send Test\n"; int len = strlen(msg) + 1; struct sockaddr_in sin; int size = sizeof(struct sockaddr_in); memset((char *)&sin, '\0', sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(9000); sin.sin_addr.s_addr = inet_addr("192.168.10.1"); memset(&(sin.sin_zero), 0, 8); if (sendto(sd, msg, len, 0, (struct sockaddr *)&sin, size) == -1) { perror("sendto"); exit(1); } 26/42
소켓인터페이스함수 [3] UDP 데이터받기 : recvfrom(3) #include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen); from : 메시지를보내는호스트의주소 char buf[80]; int len, size; struct sockaddr_in sin; if (recvfrom(sd, buf, len, 0, (struct sockaddr *)&sin, &size) == -1) { perror("recvfrom"); exit(1); } 27/42
소켓함수의호출순서 28/42
[ 예제 11-6] (1) 유닉스도메인소켓 ( 서버 ) ex11_6s.c... 08 #define SOCKET_NAME "hbsocket" 소켓이름 09 10 int main(void) { 11 char buf[256]; 12 struct sockaddr_un ser, cli; 13 int sd, nsd, len, clen; 유닉스도메인소켓생성 14 15 if ((sd = socket(af_unix, SOCK_STREAM, 0)) == -1) { 16 perror("socket"); 17 exit(1); 18 } 19 20 memset((char *)&ser, 0, sizeof(struct sockaddr_un)); 21 ser.sun_family = AF_UNIX; 22 strcpy(ser.sun_path, SOCKET_NAME); 23 len = sizeof(ser.sun_family) + strlen(ser.sun_path); 24 소켓구조체에값지정 29/42
[ 예제 11-6] (1) 유닉스도메인소켓 ( 서버 ) ex11_6s.c 25 if (bind(sd, (struct sockaddr *)&ser, len)) { 26 perror("bind"); 27 exit(1); 28 } 29 30 if (listen(sd, 5) < 0) { 31 perror("listen"); 32 exit(1); 33 } 34 35 printf("waiting...\n"); 36 if ((nsd = accept(sd, (struct sockaddr *)&cli, &clen)) == -1) { 37 perror("accept"); 38 exit(1); 39 } 클라이언트접속수용 40 41 if (recv(nsd, buf, sizeof(buf), 0) == -1) { 42 perror("recv"); 43 exit(1); 44 } 46 printf("received Message: %s\n", buf); 47 close(nsd); 48 close(sd); 49 50 return 0; 51 } 소켓기술자와소켓주소구조체연결 클라이언트접속대기 클라이언트가보낸메시지읽기 30/42
[ 예제 11-6] (2) 유닉스도메인소켓 ( 클라이언트 ) ex11_6c.c... 08 #define SOCKET_NAME "hbsocket" 09 10 int main(void) { 11 int sd, len; 12 char buf[256]; 13 struct sockaddr_un ser; 14 15 if ((sd = socket(af_unix, SOCK_STREAM, 0)) == -1) { 16 perror("socket"); 17 exit(1); 18 } 19 20 memset((char *)&ser, '\0', sizeof(ser)); 21 ser.sun_family = AF_UNIX; 22 strcpy(ser.sun_path, SOCKET_NAME); 23 len = sizeof(ser.sun_family) + strlen(ser.sun_path); 24 25 if (connect(sd, (struct sockaddr *)&ser, len) < 0) { 26 perror("bind"); 27 exit(1); 28 } 소켓생성 서버에연결요청 소켓주소구조체에값지정 31/42
[ 예제 11-6] (2) 유닉스도메인소켓 ( 클라이언트 ) ex11_6c.c 30 strcpy(buf, "Unix Domain Socket Test Message"); 31 if (send(sd, buf, sizeof(buf), 0) == -1) { 32 perror("send"); 33 exit(1); 34 } 서버에데이터전송 35 close(sd); 36 37 return 0; 38 } # ex11_6s.out Waiting... Received Message: Unix Domain Socket Test Message # ex11_6c.out # 서버 클라이언트 32/42
[ 예제 11-7] (1) 인터넷소켓 ( 서버 ) ex11_7s.c... 09 #define PORTNUM 9000 10 11 int main(void) { 12 char buf[256]; 13 struct sockaddr_in sin, cli; 14 int sd, ns, clientlen = sizeof(cli); 15 16 if ((sd = socket(af_inet, SOCK_STREAM, 0)) == -1) { 17 perror("socket"); 18 exit(1); 19 } 20 21 memset((char *)&sin, '\0', sizeof(sin)); 22 sin.sin_family = AF_INET; 23 sin.sin_port = htons(portnum); 24 sin.sin_addr.s_addr = inet_addr("192.168.162.133"); 25 26 if (bind(sd, (struct sockaddr *)&sin, sizeof(sin))) { 27 perror("bind"); 28 exit(1); 29 } 포트번호 소켓생성 소켓주소구조체생성 소켓기술자와소켓주소구조체연결 33/42
[ 예제 11-7] (1) 인터넷소켓 ( 서버 ) ex11_7s.c 31 if (listen(sd, 5)) { 클라이언트접속요청대기 32 perror("listen"); 33 exit(1); 34 } 35 36 if ((ns = accept(sd, (struct sockaddr *)&cli, &clientlen))==-1){ 37 perror("accept"); 38 exit(1); 39 } 40 41 sprintf(buf, "Your IP address is %s", inet_ntoa(cli.sin_addr)); 42 if (send(ns, buf, strlen(buf) + 1, 0) == -1) { 43 perror("send"); 44 exit(1); 45 } 46 close(ns); 47 close(sd); 48 49 return 0; 50 } 클라이언트와연결 클라이언트로데이터보내기 34/42
[ 예제 11-7] (2) 인터넷소켓 ( 클라이언트 ) ex11_7c.c... 09 #define PORTNUM 9000 10 11 int main(void) { 12 int sd; 13 char buf[256]; 14 struct sockaddr_in sin; 15 포트번호 소켓생성 16 if ((sd = socket(af_inet, SOCK_STREAM, 0)) == -1) { 17 perror("socket"); 18 exit(1); 19 } 20 21 memset((char *)&sin, '\0', sizeof(sin)); 22 sin.sin_family = AF_INET; 23 sin.sin_port = htons(portnum); 24 sin.sin_addr.s_addr = inet_addr("192.168.162.133"); 25 소켓주소구조체생성 35/42
[ 예제 11-7] (2) 인터넷소켓 ( 클라이언트 ) ex11_7c.c 26 if (connect(sd, (struct sockaddr *)&sin, sizeof(sin))) { 27 perror("connect"); 28 exit(1); 서버에접속요청 29 } 30 31 if (recv(sd, buf, sizeof(buf), 0) == -1) { 32 perror("recv"); 33 exit(1); 서버가보낸데이터읽기 34 } 35 close(sd); 36 printf("from Server : %s\n", buf); 37 38 return 0; 39 } # gcc -o ex11_7s ex11_7-inet-s.c -lsocket -lnsl # gcc -o ex11_7c ex11_7-inet-c.c -lsocket -lnsl 서버 # ex11_7s.out # ex11_7c.out From Server : Your IP address is 192.168.162.131 클라이언트 36/42
IT CookBook, 유닉스시스템프로그래밍