모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!
# 지금까지 우리가 구현해 보았던 입출력 스트림 분리
1) (Ch 10)TCP의 입출력 루틴 분할
: fork함수 호출을 통해 파일 디스크립터 복사 후, 이를 입력용/출력용으로 분리하여 사용
- 입력루틴(코드)과 출력루틴의 독립을 통한 구현의 편의성 증대
- 입력에 상관없이 출력이 가능하게 함으로 인해서 속도의 향상 기대
2) (Ch 15)FILE 포인터를 통한 입출력 분할
: 입력/출력 용도로 파일 구조체 포인터를 만들어 열고, 사용하고, 닫음
- 읽기모드와 쓰기모드의 구분을 통한 구현의 편의성 증대
- 입력버퍼와 출력버퍼를 구분함으로 인한 버퍼링 기능의 향상
# 스트림 분리 이후의 EOF에 대한 문제점
- TCP의 입출력 루틴 분할에서는
Hafl-close을 통한 EOF 전달의 필요성에 대해 언급하면서
e.g. shutdown(sock, SHUT_WR); // 출력스트림 연결을 끊으면서 상대에게 EOF 전달
과 같은 방법을 알아보았다.
- 그렇다면, fdopen함수를 통해 파일 디스크립터를 FILE 구조체 포인터로 변환하고,
이를 이용한 입출력 분할에서는 Half-close를 어떻게 할 수 있을까?
- 이 경우, 읽기모드/쓰기모드 FILE 구조체 포인터 둘중 하나에 대해서만
fclose()를 한다고해서 Half-close가 되지 않으며, 소켓이 완전히 연결 종료됨.
이는 "하나의 파일 디스크립터"를 통해 얻은 FILE 구조체 포인터들이기 때문.
# FILE 구조체 포인터 입출력 분할에서의 Half-close
- 파일 디스크립터를 복사하여, 각각의 파일 디스크립터를 각각 읽기/쓰기모드 파일 구조체 포인터로 만들면 된다.
- 여기서 파일 디스크립터 복사는 fork 함수호출에 의한 것이 아님.
한 프로세스에 원본과 복사본 파일 디스크립터가 모두 있어야 하기 때문.
- "동일한 파일 또는 소켓의 접근을 위한 또 다른 파일 디스크립터의 생성"이 필요함.
단순한 정수값 복사를 의미하는 것이 결코 아님
- "모든 파일 디스크립터가 소멸되어야 소켓도 소멸된다."라는 특징을 이용하여
Half-close를 위한 환경 조성이 가능해짐
- 그러나 이것만으로는 Half-close는 불가능. 하나의 파일 디스크립터를 통해 얻은 파일 구조체 포인터를 닫았다고 해도, 사실상 아직 소켓에 대한 파일 디스크립터가 한 개 남아 있는 것이므로 이 파일 디스크립터를 통해 사실상 입출력 둘다 가능한 상태임.
- 복사된 파일 디스크립터의 수에 상관없이, EOF의 전송을 동반하는 Half-close를 위해서는 "shutdown 함수"를 호출해야 함을 반드시 기억.
#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
// -> 성공시 복사된 파일 디스크립터, 실패시 -1 반환
- fildes : 복사할 파일 디스크립터 전달
- fildes2 : 명시적으로 지정할 파일 디스크립터의 정수값 전달
(0보다 크고, 프로세스당 생성할 수 있는 파일 디스크립터의 수보다 작은 값을 전달)
# 파일 디스크립터 0,1,2(표준입력/출력/에러)는 항상 자동으로 열림을 기억하자!
# FILE 구조체 포인터를 통한 입출력 분할에서의 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];
serv_sock=socket(PF_INET,SOCK_STREAM,0); // TCP 소켓 생성
/* 서버 주소정보 초기화 */
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);
/* 파일 디스크립터를 통해 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);
/* Half-close */
shutdown(fileno(writefp),SHUT_WR); // 출력스트림 종료(클라이언트에 EOF 전달)
fclose(writefp); // 쓰기모드 파일 구조체 포인터가 소멸되면서 복사된 파일 디스크립터도 소멸!
fgets(buf,sizeof(buf),readfp); // 아직 입력스트림은 살아있으므로 클라이언트로부터 데이터 수신
fputs(buf,stdout); // 콘솔로 수신한 데이터를 출력
fflush(stdout);
fclose(readfp); // 이 때 비로소 모든 파일 디스크립터가 소멸되면서 소켓도 종료됨.
return 0;
}
sep_clnt.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.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_adr;
FILE* readfp;
FILE* writefp;
sock=socket(PF_INET,SOCK_STREAM,0); // TCP 소켓 생성
/* 서버 주소정보 초기화 */
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]));
/*
서버 주소정보를 기반으로 연결요청
이때 비로소 진정한 클라이언트 소켓이 됨
서버소켓의 연결요청 대기큐에 들어가게 됨
*/
connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
/* 파일 디스크립터를 통해 FILE 구조체 포인터를 얻음 */
readfp=fdopen(sock,"r");
writefp=fdopen(sock,"w");
while(true)
{
/*
서버로부터 데이터를 수신
서버에서 EOF 전달시 NULL 반환
함수 특성상 자동으로 끝에 널문자 추가
*/
if(fgets(buf,sizeof(buf),readfp)==NULL)
break;
fputs(buf,stdout); // 콘솔로 수신한 데이터를 출력
fflush(stdout);
}
fputs("FROM CLIENT: Thank you! \n",writefp); // 서버에게 데이터 전송
fflush(writefp);
fclose(writefp); fclose(readfp); // 클라이언트 소켓 연결 종료
return 0;
}
[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어
'Programming > 열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)' 카테고리의 다른 글
Ch 17. select보다 나은 epoll (0) | 2020.08.10 |
---|---|
Ch 16. 내용 확인문제 (0) | 2020.08.10 |
Ch 15. 내용 확인문제 (0) | 2020.08.10 |
Ch 15. 소켓과 표준 입출력 (0) | 2020.08.10 |
Ch 14. 내용 확인문제 (0) | 2020.08.09 |