카테고리 없음

TCP&IP 제15장 다양한 입출력 함수들

Barbarian developer 2024. 9. 7.

표준 입출력 함수의 장점

표준 입출력 함수의 두 가지 장점

  • 표준 입출력 함수는 이식성(Portability)이 좋다.
  • 표준 입출력 함수는 버퍼링을 통한 성능의 향상에 도움이 된다.

표준 입출력 함수를 이용해서 데이터를 정송할 경우 왼쪽의 그림과 같이 소켓의 입출력 버퍼 이외의 버퍼를 통해서 버퍼링이 된다.

 

표준 입출력 함수와 시스템 함수의 성능비교

#include <stdio.h>
#include <fcntl.h>
#define BUF_SIZE 3 //배열의 길이를 최소한으로 구성

int main(int argc, char *argv[])
{
	int fd1, fd2, len; //fd1, fd2에 저장되는 것은 파일 디스크립터
	char buf[BUF_SIZE];

	fd1=open("news.txt", O_RDONLY);
	fd2=open("cpy.txt", O_WRONLY|O_CREAT|O_TRUNC);

	while((len=read(fd1, buf, sizeof(buf)))>0)
		write(fd2, buf, len);

	close(fd1);
	close(fd2);
	return 0;
}

해석

시스템 함수를 이용해서 버퍼링 없는 파일 복사를 진행하고 있다.

 

#include <stdio.h>
#define BUF_SIZE 3 //배열의 길이를 최소한으로 구성

int main(int argc, char *argv[])
{
	FILE * fp1; //fp1에 저장되는 것은 FILE 구조체의 포인터
	FILE * fp2; //fp2에 저장되는 것은 FILE 구조체의 포인터
	char buf[BUF_SIZE];
	
	fp1=fopen("news.txt", "r");
	fp2=fopen("cpy.txt", "w");

	while(fgets(buf, BUF_SIZE, fp1)!=NULL)
		fputs(buf, fp2); 

	fclose(fp1);
	fclose(fp2);
	return 0;
}

해석

표준 입출력 함수를 이용해서 버퍼링 기반의 파일 복사를 진행하고 있다.

 

 

300메가 바이트 이상의 파일을 대상으로 테스트 시 속도의 차가 매우 극명하게 나타난다.

 

표준 입출력 함수의 사용에 있어서 불편사항

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

fopen  함수 호출 시 반환되는 File 구조체의 포인터를 대상으로 입출력을 진행할 경우, 입력과 출력이 동시에 진행되게 하는 것은 간단하지 않다. 데이터가 버퍼링 되기 때문이다.

 

소켓 생성시 반환되는 것은 파일 디스크립터이다. 그런데 표준 C함수에서 요구하는 것은 FILE 구조체의 포인터이다. 따라서 파일 다스크립터를 FILE구조체의 포인터로 변환해야 한다.

 

표준 입출력 함수 사용하기

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

#include<stdio.h>

FILE *fdopen(int fildes, const char*mode);
	성공시 변환된 FILE 구조체 포인터, 실패시 NULL반환

fildes : 변환할 파일 디스크립터를 인자로 전달.

mode : 생성할 FILE 구조체 포인터의 모드(mode)정보 전달.

#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;
	}

	fp=fdopen(fd, "w");
	fputs("Network C programming \n", fp);
	fclose(fp);
	return 0;
}

해석

7행 : open 함수를 사용해서 파일을 생성했으므로 파일 디스크립터 반환

14행 : fodopen  함수호출을 통해서 파일 디스크립터를 FILE포인터로 변환. 두번째 인자로 "w"가 전달되었으니, 출력모드의 FILE 포인터가 반환된다.

15행 : 14행을 통해서 얻은 포인터를 기반으로 표준출력 함수인 fputs 함수를 호출하고 있다.

16행 : FILE포인터를 파일을 닫음. 파일 자체가 완전히 종료되기 때문에 다시 종료할 필요 없음. fclose 함수 호출 이후 부터 파일 디스크립터도 의미 없는 정수.

 

실행결과

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

#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); 
	fp=fdopen(fd, "w");
	fputs("TCP/IP SOCKET PROGRAMMING \n", fp);
  	printf("Second file descriptor: %d \n", fileno(fp));
	fclose(fp);
	return 0;
}

 

14행 : 7행에서 반환된 파일 디스크립터의 정수 값을 출력하고 있다.

15, 17행 : 15행에서는 fdopen함수호출을 통해서 파일 디스크립터를 FILE포인터로, 17행에서는 fileno함수호출을 통해서 이를 다시 파일 디스크립터로 변환하였다. 그리고 이 정수 값을 출력하고 있다.

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

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

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	char message[BUF_SIZE];
	int str_len, i;
	
	struct sockaddr_in serv_adr;
	struct sockaddr_in clnt_adr;
	socklen_t clnt_adr_sz;
	FILE * readfp;
	FILE * writefp;
	
	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(serv_sock==-1)
		error_handling("socket() error");
	
	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]));

	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(writefp);
		}
		fclose(readfp);
		fclose(writefp);
	}
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
		while(!feof(readfp))
		{
			fgets(message, BUF_SIZE, readfp);
			fputs(message, writefp);
			fflush(writefp);
		}

fgets, fputs함수 호출을 통해서 에코 서비스가 제공되고 있음.  fflush 함수가 호출되고 있는데, 표준 입출력 함수는 성능 향상을 목적으로 버퍼링을 하기 때문에, fflush 함수를 호출하지 않으면 당장에 클라이언트로 데이터가 전송된다고 보장 할 수가 없다.

 

fflush() 함수는 시스템이 지정된 출력 stream과 연관된 버퍼를 비우게 함(가능한 경우).

 

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

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int sock;
	char message[BUF_SIZE];
	int str_len;
	struct sockaddr_in serv_adr;
	FILE * readfp;
	FILE * writefp;

	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);   
	if(sock==-1)
		error_handling("socket() error");
	
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");
	else
		puts("Connected...........");

	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(writefp);
 		fgets(message, BUF_SIZE, readfp);
		printf("Message from server: %s", message);
	}	
	fclose(writefp);
	fclose(readfp);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

 

댓글