Using Standard I/O on Sockets VLSI 석사 3학기 윤 창 열 한남대학교 컴퓨터공학과 컴퓨터 네트워크 실험실
목차 FILE stream과 소켓 연결하기 읽기 쓰기 FIEL stream의 생성과 적용 소켓과 관련된 stream 닫기 인터럽트 시스템 호출 문제
표준 입출력 필요성 인터페이스의 표준화는 많은 플렛폼에 이식할 수 있도록 한다. stdio(3) package 어플리케이션에서 필요로 하는 기능 제공 read(2), write(2) 등 제공 버퍼링 능력 제공 : 입출력 기능 향상
Stream과 소켓 연결하기 파일 열기 일반적 방법 FILE *in; in = fopen(pathname, "r"); if ( in == NULL ) { fprintf(stderr, "%s : opening %s for read.\n”, strerror(errno), pathename); exit(1); }
소켓과 Stream의 연결을 위한 fdopen 사용 int fildes : 파일 기술자 const char *mode 사용하는 표준 입출력 모드 fopen(3)과 비슷 : “r” – 읽기 “w” – 쓰기 FILE 구조체의 포인터 반환 에러 발생시 외부변수 errno에 원인 포함 #include <stdio.h> FILE *fdopen(int fildes, const char *mode);
소켓과 Stream의 연결을 위한 fdopen 사용 int s; /* 소켓 */ FILE *io; /* 스트림 */ s = socket(PF_INET, SOCK_STREAM, 0); --- io = fdopen(s, "r+"); if ( io == NULL ) { fprintf(stderr, "%s: fdopen(s)", strerror(errno)); exit(1); }
소켓 Stream 닫기 close(s) 사용 안함 fclose(io) stream 변수 io가 사용하는 파일 기술자를 닫음 버퍼링된 데이터가 있다면, write(2) 호출 파일 기술자 닫음 : close(2) 자원 해제 : free(3)
분리된 읽기, 쓰기 Stream의 사용 두개의 분리된 stream을 사용하는 이유 쓰기모드와 읽기 모드의 변환 : fgetpos(3) 적은 오버헤드와 전체적인 버퍼링 기능 향상
분리된 읽기, 쓰기 Stream의 사용 읽기 쓰기 stream의 생성 int s; /* 소켓 */ FILE *rx; /* 읽기 스트림 */ FILE *tx; /* 쓰기 스트림 */ s = socket(PF_INET, SOCK_STREAM, 0); ---
분리된 읽기, 쓰기 Stream의 사용 rx = fdopen(s, "r"); if ( rx == NULL ) { fprintf(stderr, "%s: fdopen(s,'r')", strerror(errno)); exit(1); } tx = fdopen(dup(s), "w"); if ( tx == NULL) { fprintf(stderr, "%s: fdopen(s,'w')", strerror(errno));
소켓의 중첩(duplicating) int dup(int oldfd); 하나의 소켓에 여러 개의 파일 기술자 지정 가능 새로 생성된 파일 기술자도 원래의 소켓을 나타낸다. 생성된 파일 기술자 중 마지막 파일 기술자가 닫혀질 때, 소켓이 닫혀지게 된다.
이중 stream 닫기 버퍼의 상호작용 없앰, fgetpos(3) 필요없음 stream 닫기 rx stream fgets(3), fgetc(3)등 사용 tx stream fputs(3), fputx(3)등 사용 stream 닫기 fclose(rx) : 입력 stream 닫기 fclose(tx) : 출력 stream 닫기
이중 stream 닫기 선행조건 쓰기 stream에 버퍼링된것 버리기 내재하는 파일 기술자 닫기 버퍼 해제 FILE 객체에 의해 제어되는 stream 해제
통신의 종료 shutdown int shutdown(int s, int how) 양방향 통신 연결의 일부를 종료한다. 소켓에 영향을 미치나, 파일 기술자에는 영향을 미치지 않는다.
통신의 종료 쓰는 쪽에서 작업 종료하는 경우 호출하는 프로세스가 더 이상 쓸 데이터가 없음을 커널에 알리기 위해 shutdown(2) 호출 종료 과정 fflush(3)을 이용하여 버퍼안의 데이터 청소 shutdown(2)를 호출하여, 쓰는 측면의 작업 종료 fclose(3)을 이용하여, stream 닫기
통신의 종료 쓰는 쪽의 작업 종료 과정 fflush(tx); shutdown(fileno(tx), SHUT_WR); 쓰기 위한 데이터가 남아있으면 강제적으로 출력 fileno(tx) : tx의 파일 기술자를 반환하는 함수 shutdown( fileno(tx), SHUT_WR) 쓰는 쪽의 작업 중단 fflush(tx); shutdown(fileno(tx), SHUT_WR); fclose(tx);
통신의 종료 읽는 쪽에서 작업 종료하는 경우 더 이상 받을 데이터가 없음을 표시하기 위해 shutdown(2) 호출 fclose 함수 이용해 stream 닫기 Fflush(3) 호출의 불필요 shutdown(fileno(rx), SHUT_RD); fclose(rx);
통신의 종료 읽기, 쓰기 모두에서 작업 종료 종료 과정 fclose 호출을 통해 tx, rx 모두 종료 rx를 닫을 때 stream 닫는다. 종료 과정 fclose(3) 을 이용하여 쓰기 stream 닫기 쓰여지지 않은 데이터 강제적 출력 자원 해제 읽기 쓰기 종료를 위한 shutdown(2) 호출 fclose(3) 을 이용하여 읽기 stream 닫기 stream 버퍼와 FILE 구조체 해제
통신의 종료 fclose(tx); shutdown(fileno(rx), SHUT_RDWR); fclose(rx); 다른 프로세스의 영향에도 안정적 수행
인터럽트 처리 EINTR 에러 메시지 EINTR 메시지를 숨기는 경향 인터럽트 시스템 호출이 발생했음을 나타냄 함수 호출이 오랫동안 대기 상태일 때 메시지 출력 EINTR 메시지를 숨기는 경향 다른 플렛폼(unix 등)으로 이식이 필요할 경우 이 메시지 제어 필요
인터럽트 처리 EINTR 메시지의 허용을 위한 코드 int ch; do clearerr(rx); Clearerr(rx) : rx stream의 에러 지우기 ferror(rx) : rx stream에 에러가 있는지 테스트 int ch; do clearerr(rx); ch = fgets(rx); /* 입출력 동작 수행 */ while ( ferror(rx) && errno == EINTR);
인터럽트 처리 EINTR을 제어할 필요가 있을 때 Gnu C 라이브러리는 EINTR 에러를 숨기는 방향으로 변함 리눅스에서 다른 유닉스 플렛폼에 이식해야 할 경우 어플리케이션에서 EINTR 에러가 가끔씩 발생하는 경우 Gnu C 라이브러리는 EINTR 에러를 숨기는 방향으로 변함
인터럽트 처리 Connect(2) writev(2) Accept(2) recvfrom(2) Read(2) sendto(2) 다른 기능을 위한 EINTR 제어 인터럽트에 의해서 영향 받는 함수 Connect(2) writev(2) Accept(2) recvfrom(2) Read(2) sendto(2) Write(2) select(2) Readv(2) poll(2)
버퍼 동작 정의 stdio(3)을 사용하면 내부 버퍼 사용 버퍼의 사용은 시스템 효율성 증가 읽기, 쓰기 모두 적용 버퍼링의 세가지 모델 Fully buffered( or “block” buffered) Line buffered Unbuffered
버퍼 동작 정의 Unbuffered Line buffered Fully buffered 버퍼링의 효율성이 없을때 버퍼를 사용하지 않을 수 있다. Line buffered 텍스트 라인 기반의 동작에 유용 Fully buffered 물리적으로 쓰기 원하는 지점에서 fflush(3) 적용 데이터가 출력되지 않기 때문에, 어플리케이션이 데이터를 기다리는 경우가 발생
버퍼 동작 정의 버퍼 함수의 개략적 정의 #include <stdio.h> int setbuf(FILE *stream, char *buf); 열린 stream의 버퍼 모드를 변경한다. int setbuffer(FILE *stream, char *buf, size_t size); int setlinebuf(FILE *stream); int setvbuf(FILE *stream, char *buf, int mode, size_t size);
버퍼 동작 정의 전달 인수 설명 버퍼링 모드 *stream은 영향을 받는 FILE의 포인터이다. *buf 포인터는 제공되어지는 버퍼의 포인터이다. size 인수는 버퍼의 바이트 사이즈 의미 mode 인수는 버퍼링 모드를 나타낸다. 버퍼링 모드 _IOFBF : fully buffered _IOLBF : line buffered _IONBF : 버퍼링 하지 않음
소켓에 FILE stream 적용하기 계산기 Mkaddr() 함수 임의의 긴 정수를 수용 스택에 저장 후 계산 Gnu Multi-Precision(GMP) 라이브러리 이용 Mkaddr() 함수 Int mkaddr(void *addr, int *addr_lin, char *str_addr, char *protocol); Return value 0 : 성공적 수행 -1 : 문자열의 호스트 부분의 이상, unknown host name -2 : 포트 넘버가 틀렸거나, 알려지지 않은 서비스 이름
소켓에 FILE stream 적용하기 mkaddr() 함수의 인수 addr : 소켓 구조를 받는 주소 addr_len : 주소의 길이를 나타내는 정수의 포인트 str_addr : 상징적인 호스트네임과 선택적인 포트 넘버 host_name : service 의 구조 Host_name => 127.0.0.1 or sunsite.unc.edu Service => 8080 or telnet protocol : 서버가 사용하는 프로토콜, 널 포인터는 문자열 “tcp”를 나타낸다.
Mkaddr 함수 flow 호스트 부분과 포트 부분의 default value 설정 Address 구조의 초기화 호스트 주소 채우기 포트 넘버 채우기 종 료
Mkaddr.c char *inp_addr = strdup(str_addr); Strdup 함수를 이용한 string 중첩하기 char *host_part = strtok(inp_addr,":"); Strtok 함수를 이용하여 호스트 스트링 추출하기 char *port_part = strtok(NULL,""); Strtok 함수를 이용하여 포트 스트링 추출하기 memset(ap, 0, *addrlen) addreln 크기의 바이트에 ap 주소에서 부터 0으로 채우는 함수
RPN 계산기 엔진 코드 rpn_dump(tx) rpn_process(FILE *tx, char *buf) Stack에 있는 내용 출력 rpn_process(FILE *tx, char *buf) buf의 내용이 dump이면 rpn_dump(tx) 실행 buf의 내용이 ‘=‘이면, 결과 출력 buf의 내용이 ‘#’이면, 스택에 넣기 그 밖의 내용이면 버퍼 내용에 따른 동작 rpn_opr(operation) 실행
rpnsrv.c : 계산기 동작을 위한 메인 함수 Stream 생성 서버의 주소가 인수로 주어 졌는지 파악 생성된 두개의 stream을 Buffered mode로 설정 서버의 주소를 구성하기 위해 mkaddr() 호출 클라이언트 요구 처리 사용할 TCP/IP 소켓 생성 클라이언트 연결 해제 서버 주소 결합 종 료 서버 시작
len_inet = sizeof adr_srvr; z = mkaddr(&adr_srvr,&len_inet, 서버의 주소가 인수로 주어 졌는지 파악 if ( argc >= 2 ) srvr_addr = argv[1]; 서버의 주소를 구성하기 위해 mkaddr() 호출 len_inet = sizeof adr_srvr; z = mkaddr(&adr_srvr,&len_inet, srvr_addr,"tcp"); if ( z < 0 || !adr_srvr.sin_port ) fprintf(stderr,"Invalid server " "address, or no port number " "was specified."); exit(1); }
z = listen(s,10); s = socket(PF_INET,SOCK_STREAM,0); if ( s == -1 ) 사용할 TCP/IP 소켓 생성 s = socket(PF_INET,SOCK_STREAM,0); if ( s == -1 ) bail("socket(2)"); 서버 주소 결합 z = bind(s,(struct sockaddr *)&adr_srvr, len_inet); if ( z == -1 ) bail("bind(2)"); 서버 시작 z = listen(s,10); if ( z == -1 ) bail("listen(2)");
for (;;) /* * Wait for a connect : */ len_inet = sizeof adr_clnt; 서버 시작 - 연결 대기 for (;;) /* * Wait for a connect : */ len_inet = sizeof adr_clnt; c = accept(s, (struct sockaddr * )&adr_clnt, &len_inet); if ( c == -1 ) bail("accept(2)"); Stream 생성 rx = fdopen(c,"r"); if ( !rx ) /* Failed */ close(c); continue; } tx = fdopen( dup(c),“w"); if ( !tx ) fclose(rx); continue; }
while ( fgets(buf,sizeof buf,rx) ) rpn_process(tx,buf); 생성된 두개의 stream을 Buffered mode로 설정 setlinebuf(rx); setlinebuf(tx); 클라이언트 요구 처리 while ( fgets(buf,sizeof buf,rx) ) rpn_process(tx,buf); 클라이언트 연결 해제 fclose(tx); shutdown(fileno(rx),SHUT_RDWR); fclose(rx); 종 료
실행 결과 $ ./rpnsrv & #:970976453 0: [1] 13321 $ telnet localhost 9090 trying 127.0.0.1... Connected to localhost. Escape character is '^]' #:970976453 0: #:2636364 1: dump 1:2636364 0:970976453 E:end of stack dump
실행 결과 : 덧셈예제 $telnet localhost 9090 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. #:970976453 0: #:2636364 1: + 0: = 0:973612817 ^] telnet> c Connection closed. $
요약 FILE stream과 소켓 연결하기 읽기 쓰기 FIEL stream의 생성과 적용 소켓과 관련된 stream 닫기 io = fdopen(s, "r+"); 읽기 쓰기 FIEL stream의 생성과 적용 읽기, 쓰기 stream의 따로 생성 소켓과 관련된 stream 닫기 버퍼링 방식 선택 인터럽트 시스템 호출 문제 다른 플렛폼의 이식성을 위해서 필요 : EINTR 에러 메시지