[TCP/IP Socket]Chapter 07 - 소켓의 우아한 연결종료

Lee Jeong Min·2021년 2월 15일
0

네트워크

목록 보기
7/17

07-1 TCP 기반의 Half-close

일반적인 연결종료의 문제점

리눅스의 close 함수호출과 윈도우의 closesocket 함수호출은 완전종료를 의미한다. 완전종료라는 것은 데이터를 전송하는 것은 물론이거니와 수신하는 것 조차 더 이상 불가능한 상황을 의미한다.

위와 같은 경우 호스트 A는 호스트 B가 전송하는 데이터를 수신하지 못하며 데이터 수신과 관련된 함수의 호출 자체가 불가능해진다. 이러한 문제의 해결을 막기 위해 스트림의 일부만 종료 하는 방법이 제공되고 있다.

소켓과 스트림(Stream)

소켓을 통해서 두 호스트가 연결되면 그 다음부터는 상호간에 데이터의 송수신이 가능한 상태가 된다. 이러한 상태를 가리켜 '스트림이 형성된 상태'라고 한다.

두 호스트간에 소켓이 연결되면, 각 호스트별로 입력 스트림과 출력 스트림이 형성된다. 소켓의 우아한 종료라는 것은 한번에 이 두 스트림을 모두 끊어버리는 것이 아니라, 이 중 하나의 스트림만 끊는 것이다.

우아한 종료를 위한 shutdown 함수

#include <sys/socket.h>

int shutdown(int sock, int howto);

두 번째 매개변수에 전달될 수 있는 인자의 종류

  • SHUT_RD 입력 스트림 종료
  • SHUT_WR 출력 스트림 종료
  • SHUT_RDWR 입출력 스트림 종료

SHUT_RD의 경우 데이터 수신 불가, SHUT_WR은 데이터 전송 불가, SHUT_RDWR은 모두불가.

Half-close가 필요한 이유

'클라이언트가 서버에 접속하면 서버는 약속된 파일을 클라이언트에게 전송하고, 클라이언트는 파일을 잘 수신했다는 의미로 문자열 "Thank you'를 서버에 전송한다."

이를 구현하기 위해 클라이언트는 EOF의 수신을 함수의 반환 값을 통해서 확인이 가능하게 만들고, 이는 출력 스트림을 종료하여 상대 호스트로 EOF가 전송되게 한다. 이러한 경우 close함수호출을 하게되면 스트림이 종료되어 버리므로 half-close를 이용한다.

file_server.c

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

    fp = fopen("file_server.c", "rb");
    serv_sd = 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_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    listen(serv_sd, 5);

    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sd = accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

    while(1)
    {
        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);
        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);
}

while문 부분에서 파일을 다 보내주고나서 shutdown을 통해 출력스트림을 제한한다. 그 후에 입력 스트림을 남겨두어 클라이언트로 부터 메세지를 받을 준비를 한다.

file_client.c

#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");
    sd = 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 = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

    while((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
        fwrite((void*)buf, 1, read_cnt, fp);

    puts("Received file data");
    write(sd, "Thank you", 10);
    fclose(fp);
    close(sd);
    return 0;
}

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

파일을 다 받은 후에 서버로 인사 메세지를 전송하고 있고, 서버의 입력스트림이 닫히지 않았다면 이 메시지를 수신할 수 있다.

07-2 윈도우 기반으로 구현하기

#include <winsock2.h>

int shutdown(SOCKET sock, int howto);
  • SD_RECEIVE 입력 스트림 종료
  • SD_SEND 출력 스트림 종료
  • SD_BOTH 입출력 스트림 종료

file_server_win.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <WinSock2.h>

#define BUF_SIZE 30
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	FILE* fp;
	char buf[BUF_SIZE];
	int readCnt;

	SOCKADDR_IN servAdr, clntAdr;
	int clntAdrSz;

	if (argc != 2)
	{
		printf("Usage: %s <port> \n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	fp = fopen("file_server_win.c", "rb");
	hServSock = socket(PF_INET, SOCK_STREAM, 0);

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(atoi(argv[1]));

	bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
	listen(hServSock, 5);

	clntAdrSz = sizeof(clntAdr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, clntAdrSz);

	while (1)
	{
		readCnt = fread((void*)buf, 1, BUF_SIZE, fp);
		if (readCnt < BUF_SIZE)
		{
			send(hClntSock, (char*)&buf, readCnt, 0);
			break;
		}
		send(hClntSock, (char*)&buf, BUF_SIZE, 0);
	}
	shutdown(hClntSock, SD_SEND);
	recv(hClntSock, (char*)buf, BUF_SIZE, 0);
	printf("Messsage from client: %s \n", buf);

	fclose(fp);
	closesocket(hClntSock); closesocket(hServSock);
	WSACleanup();
	return 0;
}

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

file_client_win.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#define BUF_SIZE 30
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	FILE* fp;

	char buf[BUF_SIZE];
	int readCnt;
	SOCKADDR_IN servAdr;

	if (argc != 3)
	{
		printf("Usage: %s <IP> <port>", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		ErrorHandling("WSAStartup() error!");

	fp = fopen("receive.dat", "wb");
	hSocket = socket(PF_INET, SOCK_STREAM, 0);

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = inet_addr(argv[1]);
	servAdr.sin_port = htons(atoi(argv[2]));

	connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr));

	while ((readCnt = recv(hSocket, buf, BUF_SIZE, 0)) != 0)
		fwrite((void*)buf, 1, readCnt, fp);

	puts("Received file data");
	send(hSocket, "Thank you", 10, 0);
	fclose(fp);
	closesocket(hSocket);
	WSACleanup();
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글