2015년 1월 2일 금요일

TCP/IP 소켓 프로그래밍 16강

2015년이다~ 아아 원래 2014년 안에 다 끝내고 1월은 조직응용기사 실기 공부할려고 했는데........;;

그래도 막 공부할 순 없으니 차근차근 공부하면서 자격증 준비도 병행하려고 한다.
10점차로 떨어졌으니 이번에는 붙겠지.....

하여튼 이번 강은 "입출력 스트림의 분리에 대한 나머지 이야기"다

16-1 : 입쳑 스트림과 출력 스트림의 분리


첫번째 분리 - TCP의 입출력 루틴 분할(fork 함수 호출)
두번째 분리 - FILE 포인터를 이용한 분리(fdopen 함수 호출)

첫번째 분리의 이점
-입력루틴(코드)과 출력루틴의 독립을 통한 구현의 편의성 증대
-입력에 상관없이 출력이 가능하게 함으로 속도 향상

두번째 분리의 이점
-FILE 포인터는 읽기 모드와 쓰기모드를 구분해야 해서 구현의 편의성 증대
-입력 버처와 출력 버퍼를 구분함으로 인한 버퍼링 기능의 향상

fdopen 함수 호출 기반의 Half-close 기반의 EOF 전달 방법은 조금 다르다

소스를 보자.(일부만 볼 것이다.)

bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr));
listen(serv_sock, 5);
clnt_adr_sz=sizeof(clnt_adr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&clnt_adr_sz);
//읽기모드(r), 쓰기모드(w)의 FILE 포인터를 생성한다.
readfp=fdopen(clnt_sock, "r");
writefp=fdopen(clnt_sock, "w");
//클라이언트로 문자열 데이터를 전송하고 fflush 함수호출을 통해 전송을 마무리 한다.
fputs("FROM SERVER: Hi~ client? \n", writefp);
fputs("I love all of the world \n", writefp);
fputs("You are awesome! \n", writefp);
fflush(writefp);
//FILE 포인터를 대상으로 fclose 함수를 호출한다. 소켓을 종료시키면 EOF가 전송된다.
fclose(writefp); //쓰기모드 종료
fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); 
fclose(readfp); //읽기 모드 종료
return 0;

fclose 함수가 호출되면 EOF가 전달은 된다. 하지만 위의 소스를 적용시켜서 실행해보면
마지막 문자열을 수신하지 못하고 종료되어 버린다.

16-2 : 파일 디스크립터의 복사와 Half-close

위의 소스에서 쓰기 모드와 읽기 모드를 따로따로 종료했는데 완전 종료가 된 이유는
파일 디스크립터가 1개였기 때문이다.
파일 디스크립터가 종료되면 소켓도 같이 종료가 된다.

쓰기 모드를 종료하는 시점에 연결되어 있던 파일 디스크립터도 종료되면서
소켓도 종료되어서, 뒤에 남은 읽기 모드의 메세지가 전송되지 않고 종료되어 버린 것이다.

이것을 해결하기 위해서 파일 디스크립터를 복사해서 해결한다.

파일 디스크립터의 복사
단순한 복사가 되면 의미가 없고, "동일한 파일 또는 소켓의 접근을 위한" 또다른 파일 디스크립터의 생성이다.

파일 디스크립터의 복사 함수를 보자.

#include <unistd.h>

int dup(int fildes);
int dup2(int fildes, int fildes2);
-> 성공시 복사된 파일 디스크립터, 실패시 -1 반환
-fildes : 복사할 파일 디스크립터 전달
-fildes2 : 명시적으로 지정할 파일 디스크립터의 정수 값 전달

위 함수를 사용한 소스를 보자.
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
int cfd1, cfd2;
char str1[]="Hi~ \n";
char str2[]="It's nice day~ \n";
//파일 디스크립터 1 복사
cfd1=dup(1);
//복사한 1을 재복사 하고 7할당
cfd2=dup2(cfd1, 7);
//복사 확인
printf("fd1=%d, fd2=%d \n", cfd1, cfd2);
write(cfd1, str1, sizeof(str1));
write(cfd2, str2, sizeof(str2));

close(cfd1); //파일 디스크립터 1 종료(복사본)
close(cfd2); //파일 디스크립터 7 종료
write(1, str1, sizeof(str1));
close(1); //파일 디스크립터 1 종료(원본)
write(1, str2, sizeof(str2)); // 이 출력은 실행 안됨
return 0;
}

소스를 분석 해보면 어찌 사용하는지 알 수 있을 것이다.

그럼 위의 것을 통합해서 소스에 적용해보자.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024

int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
FILE * readfp;
FILE * writefp;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
char buf[BUF_SIZE]={0,};

serv_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr));
listen(serv_sock, 5);
clnt_adr_sz=sizeof(clnt_adr); 
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr,&clnt_adr_sz);
//fdopen 함수호출을 통해 FILE 포인터를 생성
readfp=fdopen(clnt_sock, "r");
writefp=fdopen(dup(clnt_sock), "w");
fputs("FROM SERVER: Hi~ client? \n", writefp);
fputs("I love all of the world \n", writefp);
fputs("You are awesome! \n", writefp);
fflush(writefp);
//fileno 함수 호출시 반환되는 파일 디스크립터를 대상으로 shutdown 함수 호출
//half-close가 진행되어 클라이언트로 EOF가 전달 된다.
//shutdown 함수가 호출되면 복사된 파일 디스크립터의 수에 상관 없이 
//half-close가 진행되고 이 과정에서 EOF가 전달된다.
shutdown(fileno(writefp), SHUT_WR);
fclose(writefp);
fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); 
fclose(readfp);
return 0;
}

-실행 결과-
swyoon@my_linux:~/tcpip$ gcc sep_serv2.c -o serv2
swyoon@my_linux:~/tcpip$ ./serv2 9190
FROM CLIENT: Thank you! 

복사된 파일 디스크립터의 수에 상관 없이 
EOF의 전송을 동반하는 half-close 를 진행하기 위해서는 
shutdown 함수를 호출해야 한다.

이번 강도 끝났다. 역시 직접 소스를 다운 받아서 직접 해보는 것이 도움이 된다.


다음 17강은 select 보다 나은 epoll을 알아볼 것이다.

댓글 없음:

댓글 쓰기