C/TCPIP 소켓 프로그래밍(윤성우 저)

TCP/IP 소켓 프로그래밍 7장 내용 확인문제

꾸준함. 2017. 6. 3. 21:22

1. TCP에서의 스트림 형성이 의미하는 바가 무엇인지 설명해보자. 그리고 UDP에서도 스트림이 형성되었다고 할 수 있는 요소가 있는지 없는지 말해보고, 그 이유에 대해서도 설명해보자


>TCP에서의 스트림 형성은 두 소켓이 서로 연결되어서 데이터를 송수신할 수 있게 된 상태를 의미한다. 반면, UDP의 경우는 스트림 형성과정이 존재하지 않는다. 두 소켓이 서로 연결되어있는 상태에 놓이지 않기 때문이다.


2. 리눅스에서의 close 함수 또는 윈도우에서의 closesocket 함수 호출은 일방적인 종료로써 상황에 따라서 문제가 되기도 한다. 그렇다면 일반적인 종료가 의미하는 바는 무엇이며, 어떠한 상황에서 문제가 되는지 설명해 보자.


>일반적인 종료는 소켓을 완전히 종료시켜서 데이터의 송수신이 모두 불가능한 상태로 만드는 것을 의미한다. 보통 자신이 전송할 데이터를 전부 전송한 다음에 일반적인 종료를 하게 되는데, 만약에 상대방이 전송할 데이터가 남아있는 경우에 문제가 될 수 있다.


3. Half-close는 무엇인가? 그리고 출력 스트림에 대해서 Half-close를 진행한 호스트는 어떠한 상태에 놓이게 되며, 출력 스트림의 Half-close 결과로 상대 호스트는 어떠한 메시지를 수신하게 되는가?


>Half-close는 입력 스트림, 출력 스트림 둘 중 하나만 종료하는 것을 의미한다. 그리고 출력 스트림에 대해서 Half-close를 진행하면, 상대 호스트로는 EOF(End of File)가 전송되고, Half-close를 진행한 소켓은 데이터의 전송은 불가능하지만 수신은 가능한 상태이게 된다.


[Half-close 예시]


리눅스 서버 예제

#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"); //서버의 소스파일인 file_server.c 클라이언트에게 전송하기 위해서 파일을 열고 있다

        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, 5) == -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");

 

        while (1) //accept 함수호출을 통해서 연결된 클라이언트에게 파일 데이터를 전송하기 위한 반복문

        {

               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); //파일 전송 후에 출력 스트림에 대한 Half-close 진행, 이로써 클라이언트에게 EOF 전송되고, 이를 통해 클라이언트는 파일전송이 완료되었음을 인식할 있다

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

}

 

언제나처럼 서버파일이 정상작동하면 화면이 멈추고 커서만 깜빡인다


리눅스 클라이언트 예제

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

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

 

        while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0) //EOF 전송될때까지 데이터를 수신한다음, 파일에 담고 있다

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

}

 

클라이언트를 실행하면 file data를 받았다는 메시지를 띄운다

그리고 서버쪽에서는 클라이언트로부터 메시지를 받고 종료했다는 것을 알 수 있다


윈도우 서버 예제

#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 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("C:\\file_server_win.c", "rb"); //서버의 소스파일인 file_server_win.c 클라이언트에게 전송하기 위해서 파일을 열고 있다

        if (fp == NULL)

        {

               puts("file error");

               exit(1);

        }

        hServSock = socket(PF_INET, SOCK_STREAM, 0);

        if (hServSock == INVALID_SOCKET)

               ErrorHandling("socket() error");

 

        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]));

 

        if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)

               ErrorHandling("bind() error");

        if (listen(hServSock, 5) == SOCKET_ERROR)

               ErrorHandling("listen() error");

 

        clntAdrSz = sizeof(clntAdr);

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

 

        while (1) //accept 함수호출을 통해서 연결된 클라이언트에게 파일 데이터를 전송하기 위해 반복문이 구성되어 있다

        {

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

        }

        //파일전송 후에 출력 스트림에 대한 Half-close 진행하고 있다. 이로써 클라이언트에게는 EOF 전송되고, 이를 통해서 클라이언트는 파일전송이 완료되었음을 인식할 있다

        shutdown(hClntSock, SD_SEND);

        recv(hClntSock, (char*)buf, BUF_SIZE, 0); //출력 스트림만 닫았기 때문에 입력 스트림을 통한 데이터의 수신은 여전히 가능하다

        printf("Message 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);

}

 

언제나처럼 서버파일이 정상작동하면 화면이 멈추고 커서만 깜빡인다


윈도우 클라이언트 예제

 #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>\n", argv[0]);

               exit(1);

        }

        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)

               ErrorHandling("WSAStartup() error!");

 

        fp = fopen("C:\\receive.txt", "w+"); //서버가 전송하는 파일 데이터를 담기 위해서 파일을 하나 생성하고 있다

        if (fp == NULL)

        {

               puts("file open error");

               exit(1);

        }

        hSocket = socket(PF_INET, SOCK_STREAM, 0);

        if (hSocket == INVALID_SOCKET)

               ErrorHandling("socket() error");

 

        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]));

 

        if (connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)

               ErrorHandling("connect() error");

 

        while ((readCnt = recv(hSocket, buf, BUF_SIZE, 0)) != 0) //EOF 전송될 때까지 데이터를 수신한 다음, 생성한 파일에 담고 있다

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

}

클라이언트를 실행하면 file data를 받았다는 메시지를 띄운다

그리고 서버쪽에서는 클라이언트로부터 메시지를 받고 종료했다는 것을 알 수 있다


[참고] TCP/IP 소켓프로그래밍 윤성우 저

반응형