2014년 12월 26일 금요일

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

파트2가 시작되었다.(그래봐야 아무도 안봐 ㅡㅜ)
파트2는 리눅스 기반을 위주로 설명하고 있다.
파트2 - 15강은 소켓과 표준 입출력에 대해 알아볼 것이다.
표준 입출력을 통해서 네트워크 데이터 송수신 하는 법을 알아보자

15-1 : 표준 입출력 함수의 장점


표준 입출력을 통해서 데이터 송수신을 하면 장점은 두가지가 있다.
-표준 입출력 함수는 이식성이 좋다.
-표준 입출력 함수는 버퍼링을 통한 성능 향상에 도움을 준다.

표준 입출력 함수는 모든 os에서 지원하니 다 쓸수 있다.
소켓을 생성하면 os는 입출력을 위한 버퍼를 생성하는데, 표준 입출력으로 하면 추가로
또 하나의 버퍼를 생성한다.

소켓에서 생성하는 버퍼는 TCP에서 재전송을 위해 임시 저장하는 버퍼인 반면
표준 입출력에서 생성하는 버퍼는 오로지 성능 향상을 위해 만들어지는 버퍼다.
그러므로 속도 향상에 도움이 된다.

표준 입출력 함수 사용에 있어서 몇가지 불편한 사항


-양방향 통신이 쉽지 않다
-상황에 따라서 fflush 함수의 호출이 빈번히 등장할 수 있다.
-파일 디스크립터를 FILE 구조체의 포인터로 변환해야 한다.

기본적으로 소켓은 생성시에 파일 디스크립터를 반환한다.
이 파일 디스크립터를 FILE 포인터로 변환하는 과정을 거쳐야 한다.

15-2 : 표준 입출력 함수 사용하기


fdopen 함수를 이용한 FILE 구조체 포인터로의 변환

#include <stdio.h>
FILE * fdopen(int fildes, const char* mode);
-> 성공시 변환된 FILE 구조체 포인터, 실패시 NULL 반환

-fildes : 변환할 파일 디스키립터를 인자로 전달
-mode : 생성할 FILE 구조체 포인터의 모드 정보 전달

소스를 보자

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
FILE *fp;
//open 함수를 사용해서 파일을 생성했으므로 파일 디스크립터가 반환된다.
int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
if(fd==-1)
{
fputs("file open error", stdout);
return -1;
}
//fdopen 함수를 통해서 파일 디스크립터를 FILE 포인터로 변환하고 있다.
//이 때 두번째 인자로 "w"를 전달해 출력모드의 FILE 포인터가 반환된다.
fp=fdopen(fd, "w");
//fp를 통해서 얻은 포인터를 기반으로 표준 출력함수인 fputs 함수를 호출하고 있다.
fputs("Network C programming \n", fp);
//FILE 포인터를 이용해서 파일을 닫고 있다. 이 경우 파일자체가 완전히 종료되기 때문에 파일 디스크립터를 이용해서 또 다시 종료할 필요는 없다.
fclose(fp);
return 0;
}

fileno  함수를 이용한 파일 디스크립터로의 변환


fdopen 함수의 반대기능을 제공하는 함수다.

#include <stdio.h>
int fileno(FILE * stream);
-> 성공시 변환된 파일 디스크립터, 실패시 -1 반환

소스를 보자.

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
FILE *fp;
int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
if(fd==-1)
{
fputs("file open error", stdout);
return -1;
}
printf("First file descriptor: %d \n", fd); 
//fdopen 함수호출을 통해서 파일 디스크립터를 FILE 포인터로, 
fp=fdopen(fd, "w");
fputs("TCP/IP SOCKET PROGRAMMING \n", fp);
//fileno 함수호출을 통해서 이를 다시 디스크립터로 변환했다.
  printf("Second file descriptor: %d \n", fileno(fp));
fclose(fp);
return 0;
}

15-3 : 소켓 기반에서의 표준 입출력 함수 사용


서버측 소스를 보자.(4강의 에코 서버에서 변경 부분만 볼 것이다)
  
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
clnt_adr_sz=sizeof(clnt_adr);

for(i=0; i<5; i++)
{
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
if(clnt_sock==-1)
error_handling("accept() error");
else
printf("Connected client %d \n", i+1);
//변경 부분이다.
readfp=fdopen(clnt_sock, "r");
writefp=fdopen(clnt_sock, "w");
while(!feof(readfp))
{
fgets(message, BUF_SIZE, readfp);
fputs(message, writefp);
//fflush 함수로 바로 데이터 전송을 보장하고 있다.
fflush(writefp);
}
fclose(readfp);
fclose(writefp);
}
close(serv_sock);
return 0;
}

클라이언트 부분을 보자.

int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
//FILE 포인터를 선언했다.
FILE * readfp;
FILE * writefp;
........................
//fdopen 함수를 사용했다.
readfp=fdopen(sock, "r");
writefp=fdopen(sock, "w");

while(1) 
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;

fputs(message, writefp);
//fflush 함수로 바로 전송을 보장했다.
fflush(writefp);
  fgets(message, BUF_SIZE, readfp);
printf("Message from server: %s", message);
}
fclose(writefp);
fclose(readfp);
return 0;
}
이번강은 별로 어렵지 않은 강이였다.
그저 소켓 기반에서도 표준 입출력 함수를 사용할 수 있다는 것을 알 수 있다.
하지만 적용에 따른 부가적인 코드의 발생 때문에 자주 이용하진 않는다고 한다.

16강은 입출력 스트림의 분리에 대한 나머지 이야기를 알아볼 것이다.

댓글 없음:

댓글 쓰기