Linux Socket Programming - Introducing Sockets – Network Lab. 이 성 현 한남대학교 컴퓨터공학과 컴퓨터 네트워크 실험실
목 차 소켓의 역사 소켓의 본질 리눅스 커널과 응용 프로그램의 소켓 참조 소켓 C 프로그램 예제
간단한 역사 소개 1957. 10. 04 - Sputnik 발사 성공 1958. 01. 07 - ARPA(Advanced Research Projects Agency) 출범 1962 - J.C.R Licklider : 서로 다른 컴퓨터 간의 통신을 위 한 아이디어 창출 1969 - ARPA & UNIX 1982 - 4.1BSD와 4.2BSD에 TCP/IP를 포함한 UCB 배포 1991. 10. 05 – Linux 0.02 발표
그림 1.1: ARPA으로부터 24년 후에 BSD 소켓 발달 간단한 역사 소개(계속) October 4, 1957 Sputnik January 1, 1958 ARPA 1962 J.C.R Licklider ARPANET 1969 UNIX 1979 DARPA funds UCB 1982 4.1BSD and 4.2BSD Sockets October 5, 1991 Linux 0.02 그림 1.1: ARPA으로부터 24년 후에 BSD 소켓 발달
소켓의 이해 소켓의 정의 – 전화 네트워크와의 비교 리눅스의 소켓은 전화와 유사 소켓은 통신라인의 종점을 표시 소켓의 이해 소켓의 정의 – 전화 네트워크와의 비교 리눅스의 소켓은 전화와 유사 소켓은 통신라인의 종점을 표시 전화 : 전화번호 – 소켓 : 네트워크 주소 소켓은 통신의 종점이기 때문에 종료 가능
소켓의 이해(계속) 전화 네트워크 Telephone Network 그림 1.2 전화 네트워크
소켓에 대한 이해(계속) 소켓의 정의 소켓은 소프트웨어로 작성된 통신 접속점 소켓은 서버와 서로 특정한 규약을 사용하여 데이터를 전송하는 방식 네트워크 응용 프로그램은 소켓을 통해 데이터 송수신 소켓 인터페이스 - 응용 프로그램에서 TCP/IP를 이용하는 창구 역할을 하는 인터페이스
소켓의 이해(계속) (OSI 계층) 그림 1.3: 소켓 인터페이스 위치 5-7 소켓 인터페이스 4 3 2 1 네트워크 응용 1 응용 2 응용 3 소켓 인터페이스 소켓 1 소켓 2 소켓 3 4 TCP/IP 3 2 네트워크 드라이버 1 네트워크 그림 1.3: 소켓 인터페이스 위치
소켓의 이해(계속) 소켓 사용 open(2) 함수를 사용하여 파일을 열 때, 파일 기술자를 리턴하면 성공 read(2), write(2), lseek(2), close(2)를 사용하여 파일을 열 수 있다.
소켓의 이해(계속) 소켓과 파이프의 차이점 소켓은 lseek(2)할 수 없다. 소켓은 위치 주소를 가지지만, 파일과 파이프는 네트워크 주소를 가질 수 없다. 소켓은 서로 다른 옵션을 사용할 수 있으며, 소켓에 대한 질문과 ioctl(2)를 사용할 수 있다. 소켓은 입/출력을 위해 올바른 상태를 가져야 하지만, 오픈된 파일은 어떠한 때라도 읽거나 쓸 수 있다.
소켓의 이해(계속) 소켓 참조 open(2) 함수를 호출하여 새로운 파일을 열었을 때, 파일 기술자를 리눅스 커널에서 리턴 파일 기술자를 호출하여, 파일이 열려질 때 0 또는 양의 정수 값을 이용
소켓의 이해(계속) 파일 기술자가 커널에 위치하는 과정 파일을 열기 위해서 open(2) 함수 호출 파일 기술자 3이 열려진 파일을 참조하기위해서 리턴된다. 새로운 소켓이 적당한 함수를 호출하여 생성된다. 파일 기술자 4는 새로운 소켓을 참조하기 위해서 리턴된다. 다른 파일이 open(2)를 호출하여 열린다. 파일 기술자 5는 새로 오픈된 파일을 참조하기 위해서 리턴된다.
소켓의 이해(계속) 서버 클라이언트 연결요청 데이터 데이터 종료 그림 1.4: 소켓 연결 개요 socket() socket() bind() listen() 연결요청 connect() accept() 데이터 write() read() write() 데이터 read() 종료 close() 그림 1.4: 소켓 연결 개요
소켓의 이해(계속) 소켓 함수의 예 socket() : 사용하고자 하는 통신 프로토콜을 지정 int socket(int domain, int type, int protocol); socketpair() : 소켓 연결의 한 쌍을 지정 int socketpair(int domain, int type, int protocol, int sv[2]); bind() : 소켓에 이름을 결합 int bind(int sockfd, sockaddr * myaddr, int addrlen);
소켓의 이해(계속) 소켓 함수의 예 connect() : 클라이언트가 서버에 연결을 요청하기 위해 호출 int connect(int socfd, struct sockaddr *servaddr, int addrlen); listen() : 서버 프로세스가 통신할 연결기반 소켓 설정 int listen(int sockfd, int size); accept() : 서버 프로세스에서 클라이언트 소켓과 연결을 설정 하기 위해 호출 int accept(int sockfd, struct sockaddr *peer, int *addrlen);
소켓의 이해(계속) 소켓 함수의 예 write() : 다른 소켓에 메시지 전송 read() : 다른 소켓에서 메시지 수신 ssize_t write(int fd, const void *buf, size_t count); read() : 다른 소켓에서 메시지 수신 ssize_t read(int fd, void *buf, size_t count); close() : 소켓의 연결을 종료 int close(int fd);
소켓과 파이프의 비교 pipe pipe(2) 함수 호출이 성공이면, 두개의 파일 기술자가 리턴된다. filedes[0] : 파이프의 끝까지 읽기 위해서 파일 기술자를 담는다. filedes[1] : 파이프의 끝까지 쓰기 위해서 파일 기술자를 받는다. 오직 하나의 직접적인 통신 라인 생성
소켓과 파이프의 비교(계속) 소켓 직접적인 통신 프로세스만 허용 프로세스는 소켓을 열기 위해 파일 기술자 3 사용 지역 프로세스가 파일 기술자 3으로부터 정보를 받아 통신이 이루어지는 원격 프로세스에 보낼 수 있다.
소켓의 생성 Socketpair(2) 함수 int socketpair(int domain, int type, int protocol, int sv[2]); 소켓의 도메인 소켓의 형태 소켓이 사용하는 프로토콜 소켓을 생성하고 참조하는 파일 기술자를 받기 위한 배열 포인터
소켓의 생성(계속) type 인수 – 소켓의 타입 선언 SOCK_STREAM 스트림 소켓 SOCK_DGRAM 데이터그램 소켓
소켓의 생성(계속) socketpair(2) 리턴 값 success : 0 error : -1, errno 에 에러 발생 이유 출력
소켓의 생성(계속) socketpair(2) 예제 – Listing 1.1 13: int 14: main(int argc,char **argv) { 15: int z; /* Status return code */ 16: int s[2]; /* Pair of sockets */ 16 라인에서 소켓이 생성되고 참조되는 두 개의 새로운 파일 기술자를 s[2]의 배열로 받음
소켓의 생성(계속) socketpair(2) 예제 – Listing 1.1 socketpair(2) 함수 호출 21: z = socketpair(AF_LOCAL,SOCK_STREAM,0,s); socketpair(2) 함수 호출 AF_LOCAL : 도메인 인수 지정 SOCK_STREAM : 소켓 타입 0 : 프로토콜 0으로 지정 s : 파일 기술자를 받기 위한 배열 포인터 지정
소켓의 생성(계속) socketpair(2) 예제 – Listing 1.1 에러 발생 출력 23: if ( z == -1 ) { 24: fprintf(stderr, 25: "%s: socketpair(AF_LOCAL,SOCK_STREAM,0)\n", 26: strerror(errno)); 27: return 1; /* Failed */ 에러 발생 출력
소켓의 생성(계속) socketpair(2) 예제 – Listing 1.1 소켓 s[0]의 파일 기술자 출력 33: printf("s[0] = %d;\n",s[0]); 34: printf("s[1] = %d;\n",s[1]); 소켓 s[0]의 파일 기술자 출력 소켓 s[1]의 파일 기술자 출력
소켓의 생성(계속) socketpair(2) 예제 – Listing 1.1 36: system("netstat --unix -p"); netstat(1) 명령어는 system(3) 함수를 사용 --unix : AF_LOCAL 도메인을 출력 -p : 프로세스의 정보를 출력
소켓의 생성(계속) 실행 결과 1: $ ./01lst01 2: s[0] = 3; 3: s[1] = 4; 4: (Not all processes could be identified, non-owned process info 5: will not be shown, you would have to be root to see it all.) 6: Active UNIX domain sockets (w/o servers) 7: Proto RefCnt Flags Type State I-Node PID/Program name Path 8: unix 1 [ ] STREAM CONNECTED 112354 - @0000022c 9: unix 1 [ ] STREAM CONNECTED 112220 - @0000020e 10: unix 12 [ ] DGRAM 485 - /dev/log 11: unix 1 [ ] STREAM CONNECTED 112286 - @00000218 ……………………… 21: unix 1 [ ] STREAM CONNECTED 288195 28402/01lst01 22: unix 1 [ ] STREAM CONNECTED 288194 28402/01lst01
소켓의 생성(계속) 실행 결과 분석 2-3 라인 : 파일 기술자 3, 4에 의해 열려진 소켓 2-45 라인 : netstat(1) 명령으로 system(3) 함수를 호 출하여 출력 21-22라인 : 소켓의 정보 출력 프로그램 명 : 01lst01 프로세스 ID : 28402
소켓의 입출력 실행 read(2), write(2), close(2) 함수 호출 #include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); int close(int fd); read(2) : 파일 기술자의 이용 가능한 값을 count 바 이트에 정의된 값 만큼 buf 에 할당 리턴값 : 읽은 바이트 숫자
소켓의 입출력 실행(계속) read(2), write(2), close(2) 함수 호출 write(2) : 버퍼 buf를 제공받은 파일 기술자 fd 로부 터 데이터를 작성 리턴 값 : 작성중인 바이트 숫자, 일반적으로 count 인수 값 과 일치 close(2) : 파일 기술자가 성공적으로 종료되었을 때 0을 리턴
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 16: int s[2]; /* Pair of sockets */ …………………. 23: z = socketpair(AF_LOCAL,SOCK_STREAM,0,s); socketpair(2) 함수가 23 라인에서 호출되고, 성공적으로 이루어 졌다면, s[2]배열의 s[0]와 s[1]에 파일 기술자 리턴
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 에러 발생 출력 25: if ( z == -1 ) { 26: fprintf(stderr, 27: "%s: socketpair(AF_LOCAL,SOCK_STREAM," 28: "0)\n", 29: strerror(errno)); 30: return 1; 에러 발생 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 36: z = write(s[1],cp="Hello?",6); “Hello?” 메시지가 소켓s[1]에 작성, write(2) 함수의 count 인수에 6으로 할당된 버퍼 값 때문에 공백 바이트를 작성하지 않는다.
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 에러 발생 출력 37: if ( z < 0 ) { 38: fprintf(stderr, 39: "%s: write(%d,\"%s\",%d)\n", 40: strerror(errno),s[1],cp,strlen(cp)); 41: return 2; /* Failed write */ 에러 발생 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 s[1]의 메시지 출력 44: printf("Wrote message '%s' on s[1]\n",cp); s[1]의 메시지 출력 49: z = read(s[0],buf,sizeof buf); 소켓 s[0]의 메시지를 읽어 버퍼에 저장
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 에러 발생 출력 50: if ( z < 0 ) { 51: fprintf(stderr, 52: "%s: read(%d,buf,%d)\n", 53: strerror(errno),s[0],sizeof buf); 54: return 3; /* Failed read */ 에러 발생 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 소켓이 성공적으로 메시지를 받았다는 것을 출력 60: buf[z] = 0; /* NUL terminate */ 61: printf("Received message '%s' from socket s[0]\n", 62: buf); 소켓이 성공적으로 메시지를 받았다는 것을 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 소켓 s[0]에 “Go away!”메시지를 저장 에러 발생 출력 67: z = write(s[0],cp="Go away!",8); 68: if ( z < 0 ) { 69: fprintf(stderr, 70: "%s: write(%d,\"%s\",%d)\n", 71: strerror(errno),s[0],cp,strlen(cp)); 72: return 4; /* Failed write */ 소켓 s[0]에 “Go away!”메시지를 저장 에러 발생 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 s[0]의 메시지 출력 75: printf("Wrote message '%s' on s[0]\n",cp); s[0]의 메시지 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 소켓 s[1]의 메시지를 읽어 버퍼에 저장 에러 발생 출력 80: z = read(s[1],buf,sizeof buf); 81: if ( z < 0 ) { 82: fprintf(stderr, 83: "%s: read(%d,buf,%d)\n", 84: strerror(errno),s[1],sizeof buf); 85: return 3; /* Failed read */ 소켓 s[1]의 메시지를 읽어 버퍼에 저장 에러 발생 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 메시지의 수신이 성공 소켓 s[1]으로부터 받은 메시지 출력 91: buf[z] = 0; /* NUL terminate */ 92: printf("Received message '%s' from socket s[1]\n", 93: buf); 메시지의 수신이 성공 소켓 s[1]으로부터 받은 메시지 출력
소켓의 입출력 실행(계속) 예제 프로그램-Listing 1.2 소켓 s[0]와 s[1]을 종료 프로그램을 종료 98: close(s[0]); 99: close(s[1]); 소켓 s[0]와 s[1]을 종료 102: return 0; 프로그램을 종료
소켓의 입출력 실행(계속) 실행 결과 $ ./01lst02 Wrote message 'Hello?' on s[1] Received message 'Hello?' from socket s[0] Wrote message 'Go away!' on s[0] Received message 'Go away!' from socket s[1] Done.
소켓의 종료 close(2) 함수 소켓 종료시에 사용 소켓이 종료되면, 원격 소켓으로부터 데이터 받을 수 없음. 소켓이 종료되면, 원격 소켓으로부터 데이터 받을 수 없음. 한 쌍의 소켓에서 하나만 종료된 것을 의미
소켓의 종료(계속) shutdown(2) 함수 shutdown 함수는 두 개의 인수를 요구 리턴값 #include shutdown(int s, int how); shutdown 함수는 두 개의 인수를 요구 명시한 함수를 부분적으로 shutdown 시키기 위한 소켓 기술자 s 소켓을 shutdown 시키는 방법을 가리키는 how 인수 리턴값 함수 호출 성공 : 0 함수 호출 실패 : -1
테이블 1.1: shutdown(2) 함수의 how 인수 허용 값 소켓의 종료(계속) how 인수가 허용하는 값 값 매크로 기술 SHUT_RD 명시한 소켓에서 읽는 것을 허용하지 않는다. 1 SHUT_WR 명시한 소켓에 쓰는 것을 허용하지 않는다. 2 SHUT_RDWR 명시한 소켓에서 읽거나 쓰는 것을 허용하지 않는다. 테이블 1.1: shutdown(2) 함수의 how 인수 허용 값
소켓의 종료(계속) 소켓 쓰기 shutdown 쓰기를 허용하지 않는 예제 int z; int s; /*Socket */ z = shutdown(s, SHUT_WR); if ( z == -1 ) perror("shutdown()");
소켓의 종료(계속) 소켓 쓰기 종료의 문제 해결 커널 네트워킹 소프트웨어에 의해 데이터가 버퍼에 저장된다. EOF 지시자를 원격 소켓에 보낸다. 이것은 원격 일기 프로세스에게 소켓에 더 이상의 데이터가 없음을 알려준다. EOF 지시자 이후에 보내는 메시지를 받기 위해 부분적으로 소켓을 열어 놓는다. EOF 지시자를 보낸 소켓을 종료한다.
소켓의 종료(계속) 중복 소켓 처리 int s; /* Existion socket */ int d; /* Duplicated socket */ d = dup(s); /* duplicate this socket */ close(s); /* nothing happens yet */ close(d); /* last close, so shutdown socket */
소켓의 종료(계속) shutdown() 함수 사용 shutdown() 함수가 소켓 s를 shutdown int s; /* Existing socket */ int d; /* Duplicated socket */ d = dup(s); /* duplicate this socket */ shutdown(s, SHUT_RDWR); /* immediate shutdown */ shutdown() 함수가 소켓 s를 shutdown
소켓의 종료(계속) 소켓 읽기 shutdown 소켓에서 읽어 들이는 것을 종료 수신되지 않은 데이터 무시 원격 소켓이 데이터를 전송하더라도 데이터를 수신하지 않음 에러 발생
테이블 1.2: shutdown(2) 함수의 리턴 에러 소켓의 종료(계속) shutdown(2) 함수 에러 shutdown(2) 함수의 에러 테이블 에러 기술 EBADF 주어진 소켓에 유효한 파일 기술자가 없다. ENOTSOCK 주어진 파일 기술자에 소켓이 없다. ENOTCONN 명시된 소켓이 연결되지 않았다. 테이블 1.2: shutdown(2) 함수의 리턴 에러
그림 1.5: fork(2)와 socketpair(2)의 클라이언트/서버 예 Client/Server 예제 Socket File Unit 3 Parent Process Socket File Unit 4 Kernel Child Process 그림 1.5: fork(2)와 socketpair(2)의 클라이언트/서버 예
Client/Server 예제(계속) fork(2) 함수 그림 1.6: fork(2) 수행 과정 fork() fork()의 부모 프로세스 PID = 135 fork() fork()의 리턴값 = 136 fork()의 리턴값 = 0 부모 프로세스 PID = 135 자식 프로세스 PID = 136 그림 1.6: fork(2) 수행 과정
Client/Server 예제(계속) Client/Server 프로세스 socketpair(2) 함수 호출 fork(2) 함수 호출 부모 프로세스와 자식 프로세스로 분기 부모 프로세스 : 서버 프로그램의 역할을 수행 자식 프로세스 : 클라이언트 프로그램의 역할을 수행
Client/Server 예제(계속) strftime(3) 함수 데이터와 시간을 사용하기 위한 문자열 형식 서버 요청된 현재 데이터와 시간을 획득 클라이언트가 요청한 문자열 포맷을 사용하여 리턴
Client/Server 예제(계속) strftime(3) 함수 buf : 출력 버퍼 지정 max : 버퍼 사이즈 지정 size_t strftime(char *buf, size_t max, const char *format, const struct tm *tm); buf : 출력 버퍼 지정 max : 버퍼 사이즈 지정 format : 데이터와 시간의 문자열 형식 tm : 출력 문자열을 생성하는 구성 요소 공급
Client/Server 예제(계속) Client/Server 예제-Listing 1.3 매크로 상수 값 정의 19: #ifndef SHUT_WR 20: #define SHUT_RD 0 21: #define SHUT_WR 1 22: #define SHUT_RDWR 2 23: #endif 매크로 상수 값 정의
Client/Server 예제(계속) Client/Server 예제-Listing 1.3 40: z = socketpair(AF_LOCAL,SOCK_STREAM,0,s); socketpair(2) 함수를 호출하여 소켓 생성 42: if ( z == -1 ) { 43: fprintf(stderr,"%s: socketpair(2)\n", 44: strerror(errno)); 45: exit(1); 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3 51: if ( (chpid = fork()) == (pid_t)-1 ) { …………………………… 56: fprintf(stderr,"%s: fork(2)\n", 57: strerror(errno)); 58: exit(1); fork(2) 함수를 호출하여 프로세스 분기 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3 60: } else if ( chpid == 0 ) { 61: 62: /* 63: * This is the child process (client) : ………………………… 125: close(s[1]); /* Close our end now */ 자식 프로세스가 클라이언트 프로그램으로 동작 서버에 요청하고, 응답을 받음
Client/Server 예제(계속) Client/Server 예제-Listing 1.3 127: else { 128: 129: /* 130: * This is the parent process (server) : ………………………………. 189: waitpid(chpid,&status,0); 부모 프로세스가 서버 프로그램으로 동작 클라이언트의 요구 수행
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Server) 136: printf("Child PID is %ld\n",(long)chpid); 137: fflush(stdout); 자식 프로세스의 PID 출력 139: close(s[1]); /* Client uses s[1] */ 소켓 s[1] 종료
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Server) 145: z = read(s[0],buf,sizeof buf); 146: 147: if ( z < 0 ) { 148: fprintf(stderr,"%s: read(2)\n", 149: strerror(errno)); 150: exit(1); read(2) 함수 호출하여 소켓 s[0]를 읽음 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Server) 157: buf[z] = 0; ……………………… 165: strftime(txbuf,sizeof txbuf, /* Buffer */ 166: buf, /* Input format */ 167: localtime(&td)); /* Input time */ 메시지 수신을 종료하면 공백 바이트로 바꿈 버퍼에 저장된 내용을 지정된 포맷으로 변환
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Server) 현재 시간을 확정 163: time(&td); /* Get current time */ 현재 시간을 확정 172: z = write(s[0],txbuf,strlen(txbuf)); 173: 174: if ( z < 0 ) { 175: fprintf(stderr,"%s: write(2)\n", 176: strerror(errno)); 177: exit(1); write(2) 함수를 호출하여 소켓 s[0]에 메시지 작성 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Server) 소켓 s[0] 종료 183: close(s[0]); 소켓 s[0] 종료 189: waitpid(chpid,&status,0); 자식 프로세스가 종료할 때까지 부모 프로세스를 종료하지 않기 위해 waitpid(2) 함수 호출
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Server) 부모 프로세스 종료 192: return 0; 부모 프로세스 종료
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 67: printf("Parent PID is %ld\n",(long)getppid()); 부모 프로세스 PID 출력 69: close(s[0]); /* Server uses s[1] */ 소켓 s[0] 종료
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 75: msgp = "%A %d-%b-%Y %l:%M %p"; 76: mlen = strlen(msgp); 서버에 보내는 문자열 형식 지정 msgp : 문자열 형식 지정 mlen : 문자열의 길이 지정
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 78: printf("Child sending request '%s'\n",msgp); 79: fflush(stdout); 서버에 요청하는 문자열 형식 출력 서버에 문자열 전송
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 84: z = write(s[1],msgp,mlen); 85: 86: if ( z < 0 ) { 87: fprintf(stderr,"%s: write(2)\n", 88: strerror(errno)); 89: exit(1); write(2)함수를 호출하여 소켓 s[1]에 메시지 작성 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 에러 발생 출력 97: if ( shutdown(s[1],SHUT_WR) == -1 ) { 98: fprintf(stderr,"%s: shutdown(2)\n", 99: strerror(errno)); 100: exit(1); 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 106: z = read(s[1],rxbuf,sizeof rxbuf); 107: if ( z < 0 ) { 108: fprintf(stderr,"%s: read(2)\n", 109: strerror(errno)); 110: exit(1); read() 함수를 호출하여 소켓 s[1]의 응답을 기다림 에러 발생 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 117: rxbuf[z] = 0; …………………………… 122: printf("Server returned '%s'\n",rxbuf); 123: fflush(stdout); 메시지 수신이 종료되면 공백 바이트로 바꿈 응답 메시지 출력
Client/Server 예제(계속) Client/Server 예제-Listing 1.3(Client) 소켓 s[1] 종료 125: close(s[1]); /* Close our end now */ 소켓 s[1] 종료 192: return 0; 클라이언트 프로세스 종료
Client/Server 예제(계속) 실행 결과 $ ./01lst03 Child PID is 28447 Parent PID is 28446 Child sending request '%A %d-%b-%Y %l:%M %p' Server returned 'Tuesday 13-Mar-2001 4:45 PM'
Client/Server 예제(계속) 프로그램 실행 결과-msgp 변경 실행 결과 75: msgp = "%A %d-%b-%Y %l:%M %p"; 75: msgp = "%d-%b-%y"; 실행 결과 15-Jul-99