본문 바로가기
Programming/열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)

Ch 01. 내용 확인문제

by minjunkim.dev 2020. 7. 28.

    모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!



01. 네트워크 프로그래밍에서 소켓이 담당하는 역할은 무엇인가?
그리고 소켓이라는 이름이 붙인 이유는 어디에 있는가?

   

    소켓은 물리적으로 연결(e.g. 인터넷)되어 있는 호스트간의 데이터 송수신을 위해 필요한 소프트웨어적 장치이다. 소켓은 네트워크 망으로의 연결을 위한 도구로 사용되기 때문에 소켓이라는 이름이 붙게 되었다.
* 소켓, socket : 전구(電球) 따위를 끼워 넣어 전선과 접속되게 하는 기구.


02. 서버 프로그램에서는 소켓 생성 이후에 listen 함수와 accept 함수를 차례대로 호출한다.
그렇다면 이들의 역할은 각각 무엇인지 비교해서 설명해보자.

 

listen 함수의 호출 - 서버소켓 : "이제부터 나에게 연결요청(connect 함수 호출)을 해도 돼!"
accept 함수의 호출 - 서버소켓 : "너 아까 연결요청 했으니까 지금 연결할게!"


03. 리눅스의 경우 파일 입출력 함수를 소켓 기반의 데이터 입출력에 사용할 수 있다. 반면 윈도우에서는 이것이 불가능하다. 그렇다면 리눅스에서는 가능하고, 윈도우에서는 불가능한 이유가 무엇인가?

 

    계속 강조했지만, 리눅스에서는 파일과 소켓을 동일시하고 윈도우에서는 파일과 소켓을 구분해서 바라본다. 그래서 위와 같은 차이점이 발생한다.


04. 소켓을 생성한 다음에는 주소할당의 과정을 거친다. 그렇다면 주소할당이 필요한 이유는 무엇이며,
이를 목적으로 호출하는 함수는 또 무엇인가?

    인터넷상에서 소켓을 구분하기 위해서는 주소정보가 필요하다. 따라서 서버소켓에서는 bind 함수를 호출하여 주소할당의 과정을 거치며, 클라이언트소켓에서는 connect 함수를 호출할 때 자동으로(IP는 호스트IP, Port 번호는 임의로) 주소할당이 된다.


05. 리눅스의 파일 디스크립터와 윈도우의 핸들이 의미하는 바는 사실상 같다. 그렇다면 이들이 의미하는 바가 무엇인지 소켓을 대상으로 설명해보자.

 

    리눅스의 파일 디스크립터는 파일/소켓을 구분하고 가리킬 목적으로 파일/소켓에 부여된 정수 값이다. 윈도우의 핸들 역시 이와 동일한 목적이나 윈도우에서는 파일/소켓 핸들이 구분되어 있다.


06. 저 수준 파일 입출력 함수와 ANSI 표준에서 정의하는 파일 입출력 함수는 어떠한 차이가 있는가?

 

    저수준 파일 입출력 함수는 표준에 상관없이 운영체제가 독립적으로 제공하는 파일 입출력 함수이다. 따라서 운영체제 별로 다른 입출력 함수이다. 반면, ANSI 표준에서 정의한 파일 입출력 함수는 운영체제에 상관없이 C 표준으로 동일하게 제공되는 함수로, 모든 운영체제에서 사용 가능하다.


07. 본문에서 보인 예제 low_open.c와 low_read.c를 참조하여 파일 복사 프로그램을 작성하되, 저 수준 파일 입출력 함수를 기반으로, 그리고 ANSI 표준 입출력 함수를 기반으로 각각 작성해보자. 그리고 복사 프로그램의 사용방법은 여러분이 임의로 결정하기 바란다.

    저 수준 파일 입출력 함수를 기반으로 한 파일 복사 프로그램은 아래 예제 2개로 대신하겠습니다.

# low_open.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

void error_handling(char* message);

int main(int argc, char *argv[])
{
    int fd;
    int bytes;
    char buf[]="Let's go!\n"; // "널문자" 포함하여 11바이트 임을 주의!

    fd=open("data.txt",O_CREAT|O_WRONLY|O_TRUNC); // data.txt 파일에 경로에 없으면 생성, 쓰기전용, 기존 데이터 삭제
    if(fd==-1)
        error_handling("open() error!");

    printf("file descriptor: %d \n",fd);

    if((bytes=write(fd,buf,sizeof(buf)))==-1)
        error_handling("write() error!");
    printf("%d\n",bytes); // 실제로 11바이트가 전달되었음을 확인 가능
    close(fd);

    return 0;
}

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

# 결과


# low_read.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define BUF_SIZE 100
void error_handling(char* message);

int main(int argc, char *argv[])
{
    int fd;
    int bytes;
    char buf[BUF_SIZE];

    fd=open("data.txt",O_RDONLY); // 읽기전용(data.txt 파일이 해당 경로에 있어야 열립니다!)
                                  // low_read.c를 먼저 컴파일 후 실행해주세요.
    if(fd==-1)
        error_handling("open() error!");

    printf("file descriptor: %d \n",fd);

    if((bytes=read(fd,buf,sizeof(buf)))==-1) // low_read.c에서 널문자를 포함하여 전송했으므로,
                                             // read할 때도 최대 buf 배열 전체만큼 수신받아도 무방합니다.
                                             // 만약 널문자를 포함하여 전송하지 않는다면,
                                             // sizeof(buf)-1를 최대 수신바이트 크기로 잡고
                                             // 수신한 데이터 끝에 널문자를 반드시 삽입해야 합니다!
        error_handling("read() error!");

    printf("%d\n", bytes); // 실제로 11바이트가 수신되었음을 확인 가능
    printf("file data: %s",buf);
    close(fd);

    return 0;
}

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

# 결과


    ANSI 표준 입출력 함수를 기반으로 각각 작성 해보겠습니다.

# ANSI_open.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

void error_handling(char* message);

int main(int argc, char *argv[])
{
    FILE *fp; // 고수준 파일 입출력을 위한 FILE 구조체 포인터
    int bytes;
    char buf[]="Let's go!\n"; // "널문자" 포함하여 11바이트 임을 주의!

    fp=fopen("data.txt","w"); // data.txt 파일에 경로에 없으면 생성, 쓰기전용, 기존 데이터 삭제
    if(fp==NULL)
        error_handling("open() error!");

    bytes=fwrite((void*)buf,1,sizeof(buf),fp);
      
    printf("%d\n",bytes); // 실제로 11바이트가 전달되었음을 확인 가능
    fclose(fp);

    return 0;
}

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

# 결과


# ANSI_read.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define BUF_SIZE 100
void error_handling(char* message);

int main(int argc, char *argv[])
{
    FILE *fp; // 고수준 파일 입출력을 위한 FILE 구조체 포인터
    int bytes;
    char buf[BUF_SIZE];

    fp=fopen("data.txt","r"); // 읽기전용(data.txt 파일이 해당 경로에 있어야 열립니다!)
                            // ANSI_read.c를 먼저 컴파일 후 실행해주세요.
    if(fp==NULL)
        error_handling("open() error!");

    bytes=fread((void*)buf,1,sizeof(buf),fp);

    printf("%d\n", bytes); // 실제로 11바이트가 수신되었음을 확인 가능
    printf("file data: %s",buf);
    fclose(fp);

    return 0;
}

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

# 결과


    동일하게 파일 입출력이 이루어졌음을 알 수 있습니다. 파일 입출력 단계에서 "문자열"과 "널문자" 개념 때문에 혼동이 올 수 있으므로, 이번 기회에 정리해보는 것도 좋을 것 같습니다.


[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어