관리 메뉴

Value Creator의 IT(프로그래밍 / 전자제품)

Chapter 16 입출력 스트림 분리 본문

1. 프로그래밍/4) Network

Chapter 16 입출력 스트림 분리

valuecreatort 2019. 11. 7. 20:01
반응형

앞서 설명한 입출력 스트림 분리는 2가지이다.

 

2019/10/30 - [1. 프로그래밍/4) Network] - Chapter 10 멀티 프로세스 기반의 서버 구현

 

Chapter 10 멀티 프로세스 기반의 서버 구현

1. 프로세스란? 실행중인 프로그램을 의미한다. 멀티프로세스 운영체제는 다수의 프로세스를 동시에 실행 가능하다. 프로세스마다 프로세스 ID가 할당된다. 2. Fork 함수 : 프로세스를 복사하는 함수. 복사되어서..

valueelectronic.tistory.com

 

--> 멀티 프로세스 기반 입출력 분리

 

2019/11/07 - [1. 프로그래밍/4) Network] - Chapter 15 소켓과 표준 입출력

불러오는 중입니다...

--> FILE 구조체 포인터 기반 입출력 분리

 

 

입력과 출력을 분리해서 구현이 편리해지고, 버퍼링 기능이 향상된다(속도가 빨라진다).

 

멀티프로세스 기반의 입출력 분리에서는 Half-Close를 이용한 EOF 전달이 가능하다.

하지만 FILE 구조체 포인터 기반 입출력 분리에서는 기존의 Half-Close방법을 사용할 수 없다.

아래는 fdopen을 이용한 서버-클라이언트 프로그램에서 Half-Close가 진행되지 않는 예제이다.

 

 

sep_serv.c

 

#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);
	
	readfp=fdopen(clnt_sock, "r"); //read와 write를 각각 구분하였다. 
	writefp=fdopen(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); //문자열 전송을 마무리한다. 
	
	fclose(writefp); //write 쪽만 먼저 닫았다. 상대방쪽으로 EOF를 전달한다. 
	fgets(buf, sizeof(buf), readfp); 
    	fputs(buf, stdout); 
	fclose(readfp); //fgets와 fputs를 진행한 후에 read쪽을 닫았다. 
	return 0;
}

 

 

sep_clnt.c

 

#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 sock;
	char buf[BUF_SIZE];
	struct sockaddr_in serv_addr;

	FILE * readfp;
	FILE * writefp;
	
	sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));
  
	connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	readfp=fdopen(sock, "r");
	writefp=fdopen(sock, "w");
  
	while(1)
	{
		if(fgets(buf, sizeof(buf), readfp)==NULL) 
			break;
		fputs(buf, stdout); //입력 버퍼에 있는 문자열을 출력한다. 
		fflush(stdout);
	 }  

	fputs("FROM CLIENT: Thank you! \n", writefp); //EOF를 수신한 뒤 서버쪽으로 문자열을 전송한다. 
	fflush(writefp); //flush를 명령해서 writefp 버퍼의 문자열 전송을 마무리한다. 
	fclose(writefp); //writefp를 닫는다. 
    	fclose(readfp); //readfp를 닫는다. 
	return 0;
}

 

 

 

[서버]

a@a:~/Desktop/NP/manual/TCPIP_Src/Chapter16$ ./sepserv 9190

 

 

[클라이언트]

a@a:~/Desktop/NP/manual/TCPIP_Src/Chapter16$ ./sepclnt 127.0.0.1 9190
FROM SERVER: Hi~ client? 
I love all of the world 
You are awesome! 

 

서버 쪽으로 FROM CLIENT : Thank you! 라는 명령어가 전송되었어야 하는데, 전송되지 않았다.

fclose(writefp) 때문에 서버 쪽의 소켓이 닫혔기 때문이다.

 

기존 : 하나의 파일 디스크립터를 이용해 읽기모드와 쓰기모드의 FILE 포인터를 만든다.

개선 : 파일디스크립터를 복사해서 두개로 만든 후 읽기모드용 파일 디스크립터와 쓰기모드용 파일 디스크립터로 분리한다.

 

 

 

dup.c

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

	cfd1=dup(1); //파일 디스크립터 1(표준 출력)을 cfd1로 복사한다. 
	cfd2=dup2(cfd1, 7); //cfd1의 파일디스크립터 속성을 cfd2로 복사한다. cfd2의 파일디스크립터 번호는 7로 지정한다.
	
	printf("fd1=%d, fd2=%d \n", cfd1, cfd2); //파일 디스크립터 번호는 각각 3과 7이다.
	write(cfd1, str1, sizeof(str1)); //str1을 cfd1에 전송한다. cfd1은 표준 출력이므로 str1이 화면에 표시된다. 
	write(cfd2, str2, sizeof(str2)); //str2를 cfd2에 전송한다. cfd2도 표준 출력이므로 str2가 화면에 표시된다. 
	
	close(cfd1); 
	close(cfd2);
	write(1, str1, sizeof(str1));  //복사된 cfd1과 cfd2를 닫아도 기존의 파일디스크립터 1은 남아있다. 따라서 str1이 화면에 표시된다. 
	close(1);
	write(1, str2, sizeof(str2)); //표준출력이 닫혔으므로 str2는 표시되지 않는다. 
	return 0;
}

 

 

이제 dup과 dup2 함수를 이용하여 서버의 소스코드를 수정하면 된다.

서버의 writefp를 닫아도, readfp는 살아있는 형태이다(전송은 닫히고, 수신만 가능한 상태)

 

sep_serv2.c

 

#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);
	
	readfp=fdopen(clnt_sock, "r");
	writefp=fdopen(dup(clnt_sock), "w"); //dup 기능을 이용해서 clnt_sock 파일디스크립터를 복사했다.
	
	fputs("FROM SERVER: Hi~ client? \n", writefp);
	fputs("I love all of the world \n", writefp);
	fputs("You are awesome! \n", writefp);
	fflush(writefp);
	
	shutdown(fileno(writefp), SHUT_WR); //shutdown을 이용해서 writefp로 EOF 신호를 보냈다. 
	fclose(writefp);
	
	fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); //writefp는 닫혔어도, readfp는 살아있음을 알 수 있다.
	fclose(readfp);
	return 0;
}


 

 

 

[서버]

a@a:~/Desktop/NP/manual/TCPIP_Src/Chapter16$ ./sepserv2 9190
FROM CLIENT: Thank you! 

 

 

[클라이언트]

a@a:~/Desktop/NP/manual/TCPIP_Src/Chapter16$ ./sepclnt 127.0.0.1 9190
FROM SERVER: Hi~ client? 
I love all of the world 
You are awesome! 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
Comments