본문 바로가기
열심히 살기/Network

소켓 프로그래밍 - TCP 소켓(입출력 버퍼, Half-close)

by yeni03o_o 2020. 12. 13.

데이터 입출력(read() / write() 호출)

- write()가 호출되는 순간, 데이터는 출력버퍼로 이동

- read()가 호출되는 순간, 입력버퍼에 저장된 데이터를 읽음

 

입출력 버퍼

- TCP 소켓 각각에 대해 별도로 존재

- 소켓 생성 시 자동으로 생성

- 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이뤄짐

  → 데이터 송신 보장

- 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸됨

  → 데이터 수신 보장X

- 슬라이딩 윈도우 프로토콜 적용이 가능해짐

 

 

TCP 내부동작

소켓과의 연결(3-WAY)

    : SYN → SYN+ACK → ACK

데이터 송수신

    : ACK 번호 = SEQ 번호 + 전송된 바이트 크기 +1

연결종료(4-WAY)

    : FIN → ACK. FIN→ ACK

 

 

close() 기능

- 소켓의 완전 소멸 → 더이상 입출력 불가

- 상대 호스트(소켓)에게 EOF 전달 → 모든 데이터의 전송이 끝났다는 신호

- 일방적인 종료 → 상대 호스트의 데이터 송수신이 완료되지 않은 상황이라면, 문제 발생

                     → 대안: Half-close 기법

 

Half-close(양방향 입출력 스트림 생성. 우아한 종료)

: 입력 또는 출력 스트림 중 하나만 종료

#include <sys/socket.h>

// 성공 시 0, 실패 시 -1 반환
int shutdown(int sock, int howto);
// sock: 종료할 소켓의 파일디스크립터
// howto: 종료방법

※ howto(종료방법)

   - SHUT_RD: 입력 스트림 종료 → 더이상 데이터 수신 불가. 입력관련 함수 호출 허용X

   - SHUT_WR: 출력 스트림 종료 → 더이상 데이터 전송 불가. 출력 버퍼에 남아있는 데이터는 목적지로 전송됨

                                              상대 호스트로 EOF가 전송되고, 입력 스트림은 살아있으므로 데이터 수신 가능

   - SHUT_RDWR: 입출력 스트림 종료

 

 

Half-close 기반 파일 전송 프로그램(Server)

//파일 송신 server

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

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sd, clnt_sd;
    FILE *fp;
    char buf[BUF_SIZE];
    int read_cnt;
    
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    
    if (argc != 2)
    {
    	printf("Usage:%s <port>\n", argv[0]);
        exit(1);
    }
    
    //file_server.c를 클라이언트에게 전송하기 위해서 바이너리 읽기
    fp = fopen("file_server.c", "rb");		
    
    serv_sd = socket(PF_INET, SOCK_STREAM, 0);			//소켓 생성
    if (serv_sd == -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_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    	error_handling("bind() error");
    
    //연결요청 대기. 연결요청 대기큐 생성.
    if (listen(serv_sd, 3) == -1)
    	error_handling("listen() error");
    
    clnt_adr_sz = sizeof(clnt_adr);
    
    //연결요청 대기큐에서 대기중인 클라이언트와의 연결 허용
    clnt_sd = accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    if (clnt_sd == -1)
    	error_handling("accept() error");
    
    // accept() 호출 → 클라이언트에게 파일 데이터 전송 
    while (1)
    {
    	//파일로부터 한 요소의 크기(1바이트), 요소의 개수(BUF_SIZE)만큼 데이터를 읽어들여 buf에 저장
        read_cnt = fread((void*)buf, 1, BUF_SIZE, fp);
        if (read_cnt < BUF_SIZE)				//읽은 내용이 마지막 내용이면
        {
        	write(clnt_sd, buf, read_cnt);			//클라이언트로 읽은 파일 내용 전달
            break;
        }
        write(clnt_sd, buf, BUF_SIZE);				//읽은 파일 내용 클라이언트로 송신
    }
    
    //파일 전송 후에 출력스트림 Half-close → 클라이언트에게 EOF 전송됨
    //클라이언트는 파일전송이 완료되었음을 인식
    shutdown(clnt_sd, SHUT_WR);
    
    read(clnt_sd, buf, BUF_SIZE);				//출력 스트림만 닫았기 때문에 입력 스트림을 통한 데이터 수신 가능
    printf("Message from client:%s\n", buf);
    
    fclose(fp);
    close(clnt_sd);
    close(serv_sd);
    return 0;    
}

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

 

Half-close 기반 파일 전송 프로그램(Client)

//파일 수신 client

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

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sd;
    FILE *fp;
    char buf[BUF_SIZE];
    int read_cnt;
    
    struct sockaddr_in serv_adr;
    
    if (argc != 3)
    {
    	printf("Usage:%s <IP> <port>\n", argv[0]);
        exit(1);
    }
    
    fp = fopen("receive.dat", "wb");		//서버가 전송하는 파일 데이터를 담기 위한 파일(receive.dat) 쓰기전용 open
    
    sd = socket(PF_INET, SOCK_STREAM, 0);	//소켓 생성
    if (sd == -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(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    	error_handling("connect() error");
    
    //EOF가 전송될때까지 데이터 수신 후 파일에 입력
    while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
    	fwrite((void*)buf, 1, read_cnt, fp);
    
    puts("Received file data");
    write(sd, "ByeBye~", 10);		 	//서버로 메시지 전송. 서버의 입력 스트림이 닫히지 않았다면 메시지 수신 가능
    
    fclose(fp);
    close(sd);
    
    return 0;
    
}

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

 

코드 실행 결과

- file_serever.c: 입력스트림으로 수신된 문자열 출력

file_server.c 실행 결과

- file_client.c: 서버로부터 수신된 데이터가 저장된 파일 생성

file_client 실행 결과 생성된 파일

 

공부하면서 학습 목적으로 작성한 포스팅이므로 내용이 완전하지 않습니다ㅠ 
계속해서 학습 후 지식이 좀 더 쌓이면 수시로 수정해나갈 예정입니다! 
틀린 내용은 둥글게 댓글 달아주시면 빠른 확인 후 수정하겠습니다. :)


코드 출처

윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'

 

참고

* 윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'

* qteveryday.tistory.com/51?category=921416

 

댓글