0199

학교

[컴퓨터 네트워크] Socket Programming (Part 1) - (1)

1. Introduction

[1] Socket Programming

(1) 목적

: 소켓을 사용하여 통신하는 클라이언트/서버 애플리케이션을 구축하는 방법을 배운다.

(2) 소켓이란?

: 애플리케이션 프로세으와 end-end 전송 프로토콜 사이의 문과 같다.

[2] Socket

(1) Socket Interface

: 애플리케이션과 TCP/UDP 사이에 위치한다.

(2) 소켓과 TCP/UDP의 관계도

  • Socket Description

  • Port number

  • IP Address

(3) 특징

  1. 각 소켓은 5가지 컴포넌트와 연관되어 있다. (is associated with)

    • Protocol (Protocol family and protocol)

    • Source address

    • Source port

    • Destination address

    • Destination port

  2. 컴포넌트를 지정하는 위치

    • Protocol : socket();

    • Source address and port : bind()

    • Destination address and port : connect(), sendto()

2. TCP Socket Programming #1

[1] TCP Server / Client의 Fnction Call

(1) Server

  1. socket() : 소켓 생성하기

  2. bind() : 할당하기

  3. listen() : 요청 기다리기

  4. Client의 Connection Request

  5. accept() : 수락하기

  6. read() / write() : 읽고 쓰기

  7. Client와 Server 간의 Send / Receive

  8. close() : 소켓 닫기

(2) Client

  1. socket() : 소켓 생성하기

  2. connect() : 요청하기

  3. Client의 Connection Request

  4. read() / write() : 읽고 쓰기

  5. Client와 Server 간의 Send / Receive

  6. close() : 소켓 닫기

[2-1] Example : tcp_server.c

(1) 코드

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

// 에러 메시지를 출력하고 프로그램을 종료하는 함수
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock; // 서버 소켓
    int clnt_sock; // 클라이언트 소켓

    struct sockaddr_in serv_addr; // 서버 주소 구조체
    struct sockaddr_in clnt_addr; // 클라이언트 주소 구조체
    socklen_t clnt_addr_size;

    char message[] = "Hello World!";

    // 포트 번호 입력 확인
    if (argc != 2){
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    // 1. 서버 소켓 생성
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        error_handling("socket() error");

    // 2. 서버 주소 초기화 및 설정
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    // 3. 소켓과 주소 바인딩
    if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error"); 

    // 4. 클라이언트 연결 대기
    if (listen(serv_sock, 5) == -1)
        error_handling("listen() error");

    // 5. 클라이언트 연결 수락
    clnt_addr_size = sizeof(clnt_addr);  
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
    if (clnt_sock == -1)
        error_handling("accept() error");  

    // 6. 클라이언트에 메시지 전송
    write(clnt_sock, message, sizeof(message));

    // 7. 소켓 종료
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

// 에러 메시지 출력 함수
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2) 특징

  • 주요 흐름

    • socket()으로 서버 소켓을 생성하고, bind()로 서버 주소를 설정하여 연결 요청을 받을 준비를 한다.

    • listen()으로 클라이언트 연결 요청을 대기한 후, accept() 함수로 연결을 수락하여 클라이언트와 통신한다.

    • 마지막으로, write() 함수로 클라이언트에 메시지를 전송하고 소켓을 닫아 연결을 종료한다.

  • 주요 함수:

    • socket(): 서버 소켓 생성.

    • bind(): 소켓과 서버 주소를 연결.

    • listen(): 클라이언트 연결 요청 대기.

    • accept(): 클라이언트 연결 요청 수락.

    • write(): 클라이언트에 메시지 전송.

(3) 코드 실행 및 실행 결과

code
$ gcc -o tcp_server tcp_server.c
$ ./tcp_server 12345

(따로 없고, 잘 전송하면 끝남)

[2-2] Example : tcp_client.c

(1) 코드

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

// 에러 메시지를 출력하고 프로그램을 종료하는 함수
void error_handling(char *message);

int main(int argc, char* argv[])
{
    int sock; // 클라이언트 소켓
    struct sockaddr_in serv_addr; // 서버 주소 구조체
    char message[30];
    int str_len = 0;
    int idx = 0, read_len = 0;

    // IP와 포트 번호 입력 확인
    if (argc != 3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    // 1. 클라이언트 소켓 생성
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        error_handling("socket() error");

    // 2. 서버 주소 설정
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    // 3. 서버에 연결 요청
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) 
        error_handling("connect() error!");

    // 4. 서버로부터 데이터 수신
    while ((read_len = read(sock, &message[idx++], 1)) > 0)
    {
        if (read_len == -1)
        {
            error_handling("read() error!");
            break;
        }
        str_len += read_len;
    }

    // 5. 수신한 메시지 출력 및 함수 호출 횟수 출력
    printf("Message from server: %s \n", message);
    printf("Function read call count: %d \n", str_len);

    // 6. 소켓 종료
    close(sock);
    return 0;
}

// 에러 메시지 출력 함수
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

(2) 특징

  • 주요 흐름

    • socket()으로 클라이언트 소켓을 생성하고 connect()를 통해 서버와 연결을 시도한다.

    • 연결 성공 후 read()를 통해 서버로부터 메시지를 읽어와 message 버퍼에 저장하고, 모든 데이터를 수신하면 메시지를 출력한다.

  • 주요 함수:

    • socket(): 클라이언트 소켓 생성.

    • connect(): 서버와 연결 시도.

    • read(): 서버로부터 메시지 수신.

(3) 코드 실행 및 실행 결과

YAML
$ gcc -o tcp_client tcp_client.c
$ ./tcp_client 127.0.0.1 12345

Message from server: Hello World!
Function read call count: 12

[3-1] socket()

code
#include <sys/socket.h>
int socket(int domain, int type, int protocol)

1) 역할

통신을 위한 소켓을 생성합니다.

2) 파라미터 설명

  1. domain: 사용할 프로토콜 패밀리

    • PF_INET: IPv4

    • PF_INET6: IPv6

  2. type: 소켓의 서비스 유형

    • SOCK_STREAM: TCP

    • SOCK_DGRAM: UDP

    • SOCK_RAW: Raw IP

  3. protocol: 특정 프로토콜 지정

    • 보통 0으로 설정 (기본값)

    • IPPROTO_TCP, IPPROTO_UDP와 같은 값으로 설정 가능

  4. Return value:

    • 성공 시: 새 소켓의 파일 디스크립터 반환

    • 실패 시: -1 반환

  5. Example

    • TCP 소켓

      code
      // TCP 소켓 생성
      sock = socket(PF_INET, SOCK_STREAM, 0);
      if(sock == -1)
          error_handling("socket() error");
    • UDP 소켓

      code
      // UDP 소켓 생성
      sock = socket(PF_INET, SOCK_DGRAM, 0);
      if(sock == -1)
          error_handling("socket() error");

[3-2] bind()

C++
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

1) 역할

소켓에 IP 주소와 포트를 할당합니다.

2) 파라미터 설명

  1. sockfd: 소켓의 파일 디스크립터

  2. addr: 소켓에 할당할 IP 주소와 포트 번호를 포함한 주소 구조체

  3. addrlen: addr이 가리키는 주소 구조체의 크기 (바이트 단위)

  4. Return value:

    • 성공 시: 0 반환

    • 실패 시: -1 반환

  5. Example

  6. memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind() error");

[3-3] listen()

C++
#include <sys/socket.h>
int listen(int sockfd, int backlog);

1) 역할

소켓을 연결 요청을 수신할 수 있는 상태로 만듭니다. (주로 TCP 서버에서 사용)

2) 파라미터 설명

  1. sockfd: 연결 요청을 대기할 소켓의 파일 디스크립터 (SOCK_STREAM 타입 필요)

  2. backlog: 연결 대기열의 최대 크기

  3. Return value:

    • 성공 시: 0 반환

    • 실패 시: -1 반환

  4. Example

    code
     if(listen(serv_sock, 5) == -1)
         error_handling("listen() error");

[3-4] accept()

C++
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

1) 역할

클라이언트의 연결 요청을 수락합니다. (주로 TCP 서버에서 사용)

2) 파라미터 설명

  1. sockfd: 연결 요청을 수락할 소켓의 파일 디스크립터 (SOCK_STREAM 타입 필요)

  2. addr: 클라이언트 소켓의 주소 정보를 저장할 sockaddr 구조체 포인터

  3. addrlen: 클라이언트 주소의 실제 크기를 저장할 포인터

  4. Return value:

    • 성공 시: 연결된 클라이언트 소켓의 파일 디스크립터 반환

    • 실패 시: -1 반환

  5. Example

    code
     clnt_addr_size = sizeof(clnt_addr);
     clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
     if(clnt_sock == -1)
         error_handling("accept() error");

[3-5] connect()

C++
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

1) 역할

소켓을 서버와 연결합니다.

2) 파라미터 설명

  1. sockfd: 연결할 소켓의 파일 디스크립터

  2. addr: 연결할 서버의 주소 정보가 저장된 sockaddr 구조체 포인터

  3. addrlen: addr이 가리키는 주소 구조체의 크기

  4. Return value:

    • 성공 시: 0 반환

    • 실패 시: -1 반환

  5. Example

    code
     memset(&serv_addr, 0, sizeof(serv_addr));
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
     serv_addr.sin_port = htons(atoi(argv[2]));
     if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
         error_handling("connect() error!");

[3-6] read()

code
#include <sys/socket.h>
ssize_t read(int fd, void *buf, size_t count);

1) 역할

파일 디스크립터에서 데이터를 읽습니다.

2) 파라미터 설명

  1. Return value:

    • 성공 시: 읽은 바이트 수 반환, 파일 위치가 읽은 바이트 수만큼 이동

    • 끝에 도달하면 0 반환

    • 실패 시: -1 반환

  2. Example

    code
     while(read_len = read(sock, &message[idx++], 1))
     {
         if(read_len == -1)
         {
             error_handling("read() error!");
             break;
         }
         str_len += read_len;
     }

[3-7] write()

code
#include <sys/socket.h>
ssize_t write(int fd, const void *buf, size_t count);

1) 역할

파일 디스크립터에 데이터를 씁니다.

2) 파라미터 설명

  1. Return value:

    • 성공 시: 기록한 바이트 수 반환

    • 실패 시: -1 반환

  2. Example

    code
     char message[] = "Hello World!";
     write(clnt_sock, message, sizeof(message));

[3-8] recv()

code
#include <sys/socket.h>
ssize_t recv(int socket, void * buffer, size_t length, int flags);

1) 역할

연결된 소켓에서 메시지를 수신합니다.

2) 파라미터 설명

  1. socket: 소켓의 파일 디스크립터

  2. buffer: 수신된 메시지를 저장할 버퍼의 포인터

  3. length: 버퍼의 최대 길이 (바이트 단위)

  4. flags: 메시지 수신 방식 (0은 일반 데이터)

  5. Return value:

    • 성공 시: 수신된 바이트 수 반환, 0은 연결 종료 의미

    • 실패 시: -1 반환

  6. Example

    code
     while((str_len = recv(recv_sock, buf, sizeof(buf), 0)) != 0)
     {
         if(str_len == -1)
             continue;
         buf[str_len] = 0;
         puts(buf);
     }

[3-9] send()

code
#include <sys/socket.h>
ssize_t send(int socket, const void * buffer, size_t length, int flags);

1) 역할

소켓을 통해 메시지를 전송합니다.

2) 파라미터 설명

  1. socket: 소켓의 파일 디스크립터

  2. buffer: 전송할 메시지를 담은 버퍼

  3. length: 메시지의 길이 (바이트 단위)

  4. flags: 메시지 전송 방식 (0은 일반 데이터)

  5. Return value:

    • 성공 시: 전송된 바이트 수 반환

    • 실패 시: -1 반환

  6. Example

    code
     write(sock, "123", strlen("123"));
     send(sock, "4", strlen("4"), MSG_OOB);  // 긴급 메시지 전송

[3-10] close()

C++
#include <unistd.h>
int close(int fd);

1) 역할

파일 디스크립터를 닫습니다.

2) 파라미터 설명

  1. Return value:

    • 성공 시: 0 반환

    • 실패 시: -1 반환

  2. Example

    code
     close(clnt

_sock);
close(serv_sock);

[tip - 1] read / write와 recv / send의 차이점

readwrite일반 파일 입출력 함수이고, recvsend소켓 전용 함수로 사용되는 데 차이가 있습니다.

1. read / write

  • 용도: 파일, 표준 입출력, 소켓 등 다양한 파일 디스크립터에 적용 가능

  • 기능: 파일 디스크립터에서 데이터 읽기(read) 또는 쓰기(write)

  • 플래그 지원: 별도의 플래그 지원이 없어 단순한 데이터 입출력 작업에 적합

2. recv / send

  • 용도: 소켓에만 사용

  • 기능: 소켓을 통한 네트워크 데이터 송수신 작업을 수행

  • 플래그 지원: flags 파라미터로 메시지 송수신 옵션을 지정 가능

    • 예를 들어, send에서 MSG_OOB 플래그를 사용하면 긴급 데이터를 전송할 수 있습니다.

비교 요약

함수

파일/소켓 호환성

플래그 지원

사용 예시

read / write

파일과 소켓 모두 사용 가능

플래그 없음

파일 입출력, 단순 데이터 전송

recv / send

소켓 전용

flags로 다양한 옵션 제공

네트워크 통신에서 메시지 송수신

일반적으로 소켓 통신에서는 recvsend가 소켓 전용 기능을 활용할 수 있어 더 적합하며, 특정 네트워크 통신 설정이 필요 없는 단순 데이터 송수신에서는 readwrite도 사용될 수 있습니다.

[tip - 2] Listen/accept는 주로 TCP에서 쓴다면, UDP에서는?

listenaccept는 TCP에서만 사용하고 UDP에서는 쓰지 않는데, 그 이유는 두 프로토콜의 연결 방식 차이에 있습니다.

1. TCP와 UDP의 연결 방식

  • TCP는 연결 지향 프로토콜로, 서버와 클라이언트가 데이터를 주고받기 전에 연결을 확립합니다. listen 함수는 서버가 클라이언트의 연결 요청을 대기열에 넣고 기다리게 하며, accept 함수는 이 요청을 받아들여 연결을 수립하는 데 사용됩니다.

  • UDP는 비연결형 프로토콜로, 서버와 클라이언트 사이에 사전에 연결을 확립하지 않습니다. 즉, 클라이언트가 데이터를 보내면 서버가 이를 바로 수신하고, 필요 시 클라이언트로 답을 보내는 방식으로 작동합니다.

2. UDP에서의 서버/클라이언트 통신 방식

UDP에서는 서버가 listenaccept를 통해 연결을 기다릴 필요가 없습니다. 클라이언트는 서버의 IP 주소와 포트 번호를 사용해 직접 데이터를 보낼 수 있고, 서버는 recvfrom 같은 함수를 사용해 데이터를 수신합니다.

즉, UDP에서는 '연결'이라는 개념이 없기 때문에 listenaccept가 필요하지 않으며, 이 덕분에 UDP 통신이 더 간단하고 속도가 빠른 장점이 있습니다.

[tip - 3] 10개의 함수를 간단하게 요약한 내용

  1. socket(int domain, int type, int protocol)-> socket(프로토콜 패밀리, 서비스 타입, 특정 프로토콜)예시: sock = socket(PF_INET, SOCK_STREAM, 0);

  2. bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)-> bind(소켓의 파일 디스크립터, 소켓에 할당할 주소, 주소 구조체의 크기)예시: bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

  3. listen(int sockfd, int backlog)-> listen(소켓의 파일 디스크립터, 시스템이 대기할 수 있는 최대 연결 요청 수)예시: listen(serv_sock, 5);

  4. accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)-> accept(소켓의 파일 디스크립터, 클라이언트 소켓의 주소를 채우기 위한 포인터, 클라이언트 주소의 실제 크기)예시: clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

  5. connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)-> connect(소켓의 파일 디스크립터, 연결할 주소, 주소 구조체의 크기)예시: connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

  6. read(int fd, void *buf, size_t count)-> read(파일 디스크립터, 데이터를 저장할 버퍼, 읽을 바이트 수)예시: read_len = read(sock, buf, sizeof(buf));

  7. write(int fd, const void *buf, size_t count)-> write(파일 디스크립터, 전송할 버퍼, 전송할 바이트 수)예시: write(clnt_sock, message, sizeof(message));

  8. recv(int sockfd, void *buf, size_t len, int flags)-> recv(소켓의 파일 디스크립터, 메시지를 저장할 버퍼, 버퍼의 길이, 수신 플래그)예시: recv_len = recv(recv_sock, buf, sizeof(buf), 0);

  9. send(int sockfd, const void *buf, size_t len, int flags)-> send(소켓의 파일 디스크립터, 전송할 메시지가 담긴 버퍼, 메시지의 길이, 전송 플래그)예시: send(sock, "Hello", strlen("Hello"), 0);

  10. close(int fd)-> close(파일 디스크립터)예시: close(sock);

3. Internet Address and Domain Name

: 3. 인터넷 주소와 도메인 이름

[1] 인터넷 주소

1) IP 주소

  • 인터넷에서 컴퓨터를 식별하는 데 사용됨

  • IPv4 (4바이트 주소)와 IPv6 (16바이트 주소)가 있음

  • 소켓을 생성할 때 기본 프로토콜을 지정해야 함

2) Port

  • 컴퓨터에서 소켓을 식별하는 데 사용됨

  • 포트 번호는 16비트로 표현됨-> 총 범위 0 ~ 65535

3) Port의 종류

1. 잘 알려진 포트: 0 ~ 1023

  • 이미 사용 용도가 결정됨

  • 예: ssh의 경우 22, 웹의 경우 80 등

2. 임시 포트: 단기적으로 사용되는 전송 프로토콜 포트

  • IP 스택 소프트웨어에 의해 미리 정의된 범위에서 자동으로 할당됨

  • 예: 리눅스: 32768 ~ 60999

[2] 주소 구조

1) <netinet/in.h>에 정의되어 있다.

1. sockaddr

C++
struct sockaddr {
    u_short sa_family; // 주소 패밀리
    char sa_data[14]; // 주소
};

2. sockaddr_in

C++
struct sockaddr_in {
    sa_family_t sin_family; // 주소 패밀리: AF_INET
    uint16_t sin_port; // 포트 번호 (16비트), 네트워크 바이트 오더
    struct in_addr sin_addr; // IP 주소 (32비트), 네트워크 바이트 오더
    char sin_zero[8]; // 사용되지 않음
};

3. in_addr

C++
struct in_addr {
    in_addr_t s_addr; // IPv4 인터넷 주소 (32비트)
};

2) 예시

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

if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
    error_handling("bind() error");

INADDR_ANY: 소켓이 모든 로컬 인터페이스에 바인딩됨.

[3] 바이트 순서 변환

1) 바이트 순서

  • 리틀 엔디안: 가장 낮은 중요도의 바이트가 먼저

  • 빅 엔디안: 가장 높은 중요도의 바이트가 먼저

2) 네트워크 바이트 순서 = 빅 엔디안

  • 가장 중요한 바이트가 단어의 최하위 주소에 위치

  • 호스트 바이트 순서는 호스트의 CPU에 따라 다름

[4] htons(), ntohs(), htonl(), ntohl()

1) 각 함수 별 정의

C++
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort); // 호스트 바이트 순서에서 네트워크 바이트 순서로 변환하여 포트 번호로 변환하여 사용
uint16_t ntohs(uint16_t netshort);   // 네트워크 바이트 순서에서 호스트 바이트 순서로 변환하여 포트 번호로 변환하여 사용
uint32_t htonl(uint32_t hostlong);    // 호스트 바이트 순서에서 네트워크 바이트 순서로 변환하여 IP 주소로 변환하여 사용
uint32_t ntohl(uint32_t netlong);     // 네트워크 바이트 순서에서 호스트 바이트 순서로 변환하여 IP 주소로 변환하여 사용

2) 역할

: 호스트 바이트 순서와 네트워크 바이트 순서 간의 값 변환 (이는 서로 다른 컴퓨터 시스템 간에 데이터를 정확하게 주고받기 위해 필요하다.)

3-1) 예시: endian_conv.c

C++
unsigned short host_port = 0x1234;
unsigned short net_port;
unsigned long host_addr = 0x12345678;
unsigned long net_addr;
net_port = htons(host_port);
net_addr = htonl(host_addr);
printf("호스트 바이트 순서 포트: %#x \n", host_port);
printf("네트워크 바이트 순서 포트: %#x \n", net_port);
printf("호스트 바이트 순서 주소: %#lx \n", host_addr);
printf("네트워크 바이트 순서 주소: %#lx \n", net_addr);

3-2) 결과

code
>> 호스트 바이트 순서 포트: 0x1234
>> 네트워크 바이트 순서 포트: 0x3412
>> 호스트 바이트 순서 주소: 0x12345678
>> 네트워크 바이트 순서 주소: 0x78563412

4) Long은 addr, short는 port에 쓰이는 이유

  • long은 IPv4 주소와 같은 더 큰 값을 표현하기 위해 사용되며, short는 포트 번호와 같이 상대적으로 작은 값을 표현하기 위해 사용된다.

  • IPv4 주소는 32비트로 구성되어 있어 long 타입이 필요하고, 포트 번호는 16비트로 충분하기 때문에 short 타입을 사용한다.

[5] inet_addr(), inet_aton()

code
#include <arpa/inet.h>
in_addr_t inet_addr(const char *string); // 점으로 구분된 10진수 IP 주소를 32비트 빅 엔디안 정수로 변환
int inet_aton(const char *string, struct in_addr *addr); // 점으로 구분된 10진수 IP 주소를 32비트 빅 엔디안 정수로 변환하여 struct in_addr에 저장

1) 역할

: IPv4 주소 조작

  • 점으로 구분된 10진수 IP 주소(문자열)를 32비트 빅 엔디안 정수로 변환

2) 함수 별 특징

  1. inet_addr()

    • 반환 값

      • 성공: 인터넷 주소 (32비트 정수)

      • 오류: -1

  2. inet_aton()

    • inet_addr()와 유사하지만 결과 값을 인자를 통해 반환

    • 반환 값

      • 성공: 1 (참)

      • 오류: 0 (거짓)

3-1) Example (inet_addr.c)

code
char *addr1 = "1.2.3.4";
char *addr2 = "127.232.124.79";

unsigned long conv_addr = inet_addr(addr1);
if (conv_addr == INADDR_NONE)
    printf("Error occured! \n");
else
    printf("Network ordered integer addr: %#lx \n", conv_addr);

conv_addr = inet_addr(addr2);
if(conv_addr == INADDR_NONE)
    printf("Error occureded \n");
else
    printf("Network ordered integer addr: %#lx \n\n", conv_addr);

3-2) 실행 결과

code
>> Network ordered integer addr: 0x4030201
>> Network ordered integer addr: 0x4f7ce87f

[6] inet_ntoa()

code
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr); // IPv4 주소를 점으로 구분된 10진수 IP 주소로 변환하는 함수

1) 역할

: IPv4 주소 조작

  • 32비트 빅 엔디안 정수를 점으로 구분된 10진수 IP 주소(String)로 변환

2) 함수 별 특징

  1. inet_ntoa()

    • 문자열 IP 주소를 점으로 구분된 10진수 IP 주소로 변환

    • 반환 값

      • 성공: 변환된 점으로 구분된 IP 주소(String)

      • 오류: -1

3-1) 예시 (inet_ntoa.c)

code
struct sockaddr_in addr1, addr2; // sockaddr_in 구조체를 위한 변수 선언
char *str_ptr; // IP 주소 문자열을 저장할 포인터
char str_arr[20]; // IP 주소 문자열을 저장할 배열

addr1.sin_addr.s_addr = htonl(0x1020304); // addr1에 IP 주소를 설정
addr2.sin_addr.s_addr = htonl(0x1010101); // addr2에 IP 주소를 설정

str_ptr = inet_ntoa(addr1.sin_addr); // addr1의 sin_addr을 점으로 구분된 문자열로 변환
strcpy(str_arr, str_ptr); // 변환된 문자열을 str_arr에 복사
printf("Dotted-Decimal notation1: %s \n", str_ptr); // 변환된 IP 주소 출력

3-2) 실행 결과

YAML
>> Dotted-Decimal notation1: 1.2.3.4

[7] gethostbyname()

C++
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname); // 호스트 이름으로 호스트 정보를 가져오는 함수

1) 역할

: 호스트 이름으로 호스트 정보를 가져옴

  • 호스트 이름을 통해 호스트의 공식 이름, 별칭 및 IP 주소를 얻을 수 있음

  • hostname: 호스트 이름 또는 표준 점 표기법의 IPv4 주소

2) 반환 값

  • 성공: 주어진 호스트 이름에 대한 hostent 구조체

  • 오류: NULL

3-1) 예시 (gethostbyname.c)

code
struct hostent *host; // hostent 구조체 포인터 선언
…
host = gethostbyname(argv[1]); // 인자로 받은 호스트 이름으로 호스트 정보를 가져옴
printf("Official name: %s \n", host->h_name); // 공식 이름 출력

for (i = 0; host->h_aliases[i]; i++) // 별칭을 출력
    printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);

printf("Address type: %s \n", (host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6"); // 주소 타입 출력

for (i = 0; host->h_addr_list[i]; i++) // IP 주소를 출력
    printf("IP addr %d: %s \n", i+1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));

3-2) 결과

code
# 실행 결과는 호스트 이름에 따라 달라짐

[8] hostent의 구조

1) netdb.h에 정의되어 있다.

C++
#define h_addr h_addr_list[0] // h_addr을 h_addr_list의 첫 번째 주소로 정의

struct hostent {
  char *h_name;        // 호스트의 공식 이름
  char **h_aliases;    // 별칭 목록
  int h_addrtype;     // 호스트 주소 타입
  int h_length;       // 주소 길이
  char **h_addr_list;  // 주소 목록
};

[9] gethostbyaddr()

C++
#include <netdb.h>
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);

1) 역할

: 네트워크 바이트 순서 주소를 통해 호스트 정보를 가져온다.

  • 주어진 주소(addr)를 통해 호스트의 공식 이름, 별칭(aliases), IP 주소를 얻을 수 있다 (네트워크 바이트 주소).

2) 파라미터 설명

  1. addr : 호스트의 주소 (type = struct in_addr)

  2. len : 주소의 길이 (IPv4 = 4, IPv6 = 16)

  3. family : 주소 패밀리 (AF_INET = IPv4, AF_INET6 = IPv6)

3) 반환 값

  • 성공: 주어진 호스트 이름에 대한 hostent 구조체 타입

  • 오류: NULL

4) 예시 (gethostbyaddr.c)

code
struct hostent *host;         // hostent 구조체 포인터
struct sockaddr_in addr;      // sockaddr_in 구조체

memset(&addr, 0, sizeof(addr)); // addr 초기화
addr.sin_addr.s_addr = inet_addr(argv[1]); // 입력된 IP 주소를 addr에 저장
host = gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET); // 주소를 통해 host 정보 조회

if (!host) // 호스트 정보가 없으면 오류 처리
    error_handling("gethost... error");

printf("Official name: %s \n", host->h_name); // 공식 이름 출력

for (i = 0; host->h_aliases[i]; i++) // 별칭 목록 출력
    printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);

printf("Address type: %s \n", 
    (host->h_addrtype==AF_INET)?"AF_INET":"AF_INET6"); // 주소 타입 출력

for (i = 0; host->h_addr_list[i]; i++) // IP 주소 목록 출력
    printf("IP addr %d: %s \n", i+1,
                inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));

4. TCP Socket Programming #2

: 4. TCP 소켓 프로그래밍 #2

[1] TCP: 개요 (RFC: 793, 1122, 2018, 5681, 7323)

1) 특징

  1. 포인트 투 포인트 (Point-to-Point):

    • 송신자와 수신자가 1:1로 연결됨.

  2. ?신뢰성(Reliable) 있고 순서 보장된 바이트 스트림 (Reliable, In-Order Byte Stream):?

    • "메시지 경계(message boundaries)"가 없음.

  3. 전이중 데이터 (Full Duplex Data):

    • 동일한 연결 내에서 양방향 데이터 흐름 가능.

    • MSS (Maximum Segment Size): 최대 세그먼트 크기.

  4. 누적 ACK (Cumulative ACK):

    • 수신한 모든 데이터에 대한 ACK를 전송.

  5. 파이프라이닝 (Pipelining):

    • TCP의 혼잡 제어 및 흐름 제어가 윈도우 크기를 설정함.

  6. ?연결 지향적 (Connection-Oriented): ?

    • 데이터 전송 전 송신자와 수신자의 상태를 초기화하기 위해 핸드셰이킹(제어 메시지 교환) 수행.

  7. 흐름 제어 (Flow Controlled):

    • 송신자가 수신자를 압도하지 않도록 조절됨.

[2] TCP의 메시지 경계 없음

1) 특징

  1. "메시지 경계(Message boundary)"는 프로토콜을 통해 전송되는 두 메시지 간의 구분을 의미한다.

  2. UDP는 메시지 경계를 보존한다.

    • 예를 들어, 서버가 write()를 두 번 호출하면 클라이언트는 read()를 두 번 호출해야 한다.

  3. TCP는 메시지 경계를 보존하지 않는다.

    • 예를 들어, 서버가 write()를 두 번 호출하더라도 클라이언트는 모든 데이터를 한 번에 읽을 수 있다.

2-1) 예시

  1. 서버

    code
    // Server
    char message1[] = "Hello World!111"; // 첫 번째 메시지
    char message2[] = "Hello World!222"; // 두 번째 메시지
    …
    write(clnt_sock, message1, sizeof(message1)); // 첫 번째 메시지 전송
    write(clnt_sock, message2, sizeof(message2)); // 두 번째 메시지 전송
  2. 클라이언트

    code
    // Client
    sleep(1); // 잠시 대기
    read_len = read(sock, message, sizeof(message)); // 서버로부터 데이터 읽기
    printf("Message from server: "); // 서버로부터의 메시지 출력
    for (i = 0; i < sizeof(message); i++)
     printf("%c", message[i]); // 읽은 메시지 출력
    printf("Read_len: %d \n", read_len ); // 읽은 길이 출력

2-2) 결과

code
$ ./hclient2 127.0.0.1 9191

Message from server: Hello World!111Hello World!222**p^*
Read_len: 32

[3] 서버 종류

1) 반복 서버 (Iterative Server)

  • 단일 서버 프로세스가 "잘 알려진" 포트에서 들어오는 요청을 수신하고 처리한다.

  • 대부분의 UDP 서버는 반복적이다.

    • UDP 서버는 간단하고 효율적인 처리를 위해 하나의 프로세스와 하나의 스레드로 운영된다.

2) 동시 서버 (Concurrent Server)

  • 각 클라이언트를 처리하기 위해 별도의 프로세스를 생성한다.

  • 주요 서버 프로세스가 각 클라이언트를 처리하기 위해 새로운 서비스 프로세스를 생성한다.

  • 대부분의 TCP 서버는 연결 관리의 용이성을 위해 동시적이다.

[4] Iterative vs Concurrent servers

1) Iterative server skeleton

code
int sockfd, newsockfd;
// 소켓 생성
if ((sockfd = socket(…)) < 0)
    err_sys(“socket error”);
// 소켓에 주소 바인딩
if ((bind(sockfd, …) < 0)
    err_sys(“bind error”);
// 클라이언트 요청을 대기
if (listen(sockfd, 5))
    err_sys(“listen error”);
for (;;) {
  // 클라이언트 연결 수락
  newsockfd = accept(sockfd, …);
  if (newsockfd < 0)
      err_sys(“accept error”);
  // 요청 처리 함수 호출
  doit(newsockfd);
  // 연결 종료
  close(newsockfd);
}

설명:

  • 단일 프로세스: 하나의 프로세스가 클라이언트 요청을 순차적으로 처리한다.

  • 간단한 구조: 요청을 하나씩 처리하므로 코드가 간단하다. 하지만 동시에 여러 클라이언트를 처리할 수 없다.

2) Concurrent server skeleton

code
int sockfd, newsockfd;
// 소켓 생성
if ((sockfd = socket(…)) < 0)
    err_sys(“socket error”);
// 소켓에 주소 바인딩
if ((bind(sockfd, …) < 0)
    err_sys(“bind error”);
// 클라이언트 요청을 대기
if (listen(sockfd, 5))
    err_sys(“listen error”);
for (;;) {
  // 클라이언트 연결 수락
  newsockfd = accept(sockfd, …);
  if (newsockfd < 0)
      err_sys(“accept error”);
  // 자식 프로세스 생성하여 클라이언트 요청 처리
  if (fork() == 0) {
    close(sockfd); // 자식 프로세스에서 기존 소켓 닫기
    doit(newsockfd); // 요청 처리 함수 호출
    exit(0); // 자식 프로세스 종료
  }
  else {
      close(newsockfd); // 부모 프로세스에서 연결 소켓 닫기
  }
}

설명:

  • 다중 프로세스: 각 클라이언트 연결에 대해 새로운 프로세스를 생성하여 요청을 병렬로 처리한다.

  • 효율적인 동시 처리: 여러 클라이언트의 요청을 동시에 처리할 수 있어 성능이 향상된다. 하지만, 프로세스를 관리하는 오버헤드가 증가할 수 있다.

[5] TCP 소켓의 입력 및 출력 버퍼

TCP 소켓의 입력 및 출력 버퍼
  • 입력 버퍼와 출력 버퍼: TCP 소켓에는 입력 버퍼와 출력 버퍼가 존재한다.

  • 버퍼 생성 시점: 소켓이 생성될 때 입력 버퍼와 출력 버퍼가 생성된다.

  • 소켓 종료 시 데이터 전송: 소켓이 닫혀도 출력 버퍼에 남아 있는 데이터는 계속 전송된다.

  • 입력 버퍼의 데이터 소멸: 소켓이 닫히면 입력 버퍼에 남아 있는 데이터는 파괴된다.

1) 설명

  1. 입력 버퍼(Input Buffer)

  • 클라이언트가 보낸 데이터를 수신하기 위한 공간이다.

  • 클라이언트가 데이터를 보낼 때, 이 버퍼에 데이터가 쌓이며, 이후 애플리케이션이 이 데이터를 읽어 처리한다.

  • 클라이언트 소켓이 닫히면 이 버퍼의 데이터는 사라지기 때문에 중요한 데이터가 소실되지 않도록 주의해야 한다.

  1. 출력 버퍼(Output Buffer)

  • 서버가 클라이언트에게 보내기 위한 데이터가 저장되는 공간이다.

  • 애플리케이션이 데이터를 소켓을 통해 전송할 때, 이 버퍼에 데이터를 쌓고, 소켓이 실제로 데이터를 전송할 때 사용된다.

  • 서버 소켓이 닫혀도 이 버퍼에 있는 데이터는 계속해서 전송되므로, 소켓의 종료가 데이터 전송에 영향을 미치지 않는다.

[6] Half-close

: 반닫힘 (Half-close)

  1. close()의 의미

    • 소켓의 완전한 파괴를 의미한다.

    • 더 이상 I/O 작업이 불가능하다.

    • 데이터 송수신이 완료되지 않은 경우 문제를 일으킬 수 있다.

  2. 반닫힘 (Half-close)

    • 입력 스트림 또는 출력 스트림 중 하나만 닫는다.

[7] shutdown()

code
#include <unistd.h>
int shutdown(int socket, int how);

1) 역할

: 소켓의 송신 및 수신 작업을 종료한다.

  • 이 함수는 소켓의 전이중(full-duplex) 연결에서 모든 또는 일부를 종료한다.

  • shutdown()은 실제로 소켓 설명자(socket descriptor)를 해제하지 않으며, 설명자를 해제하려면 close()를 사용해야 한다.

2) 파라미터 설명

  1. socket: 소켓 파일 설명자

  2. how: 종료 유형

    • SHUT_RD (0): 더 이상의 수신 작업을 비활성화한다.

    • SHUT_WR (1): 더 이상의 송신 작업을 비활성화한다.

    • SHUT_RDWR (2): 더 이상의 송신 및 수신 작업을 비활성화한다.

3) 반환 값

  • 성공: 0

  • 오류: -1

4) 예시

code
shutdown(clnt_sd, SHUT_WR); // 클라이언트 소켓의 송신 작업 종료
read(clnt_sd, buf, BUF_SIZE); // 클라이언트 소켓에서 메시지를 읽는다
printf("Message from client : %s \n", buf); // 클라이언트로부터 받은 메시지를 출력한다

close(clnt_sd); // 클라이언트 소켓을 닫는다
close(serv_sd); // 서버 소켓을 닫는다

섹션별 정리 및 추가 설명 (심화)

1. Introduction (소개)

  • 소켓 프로그래밍의 개념: 네트워크를 통해 애플리케이션 간에 데이터를 주고받는 방법을 다루는 프로그래밍 기법입니다. 소켓은 이러한 통신을 위한 추상적인 끝점(endpoint) 역할을 합니다.

  • 소켓의 역할: 애플리케이션과 운영체제 사이에서 네트워크 통신을 위한 인터페이스를 제공합니다.

  • TCP/UDP 프로토콜: TCP는 연결 지향, 신뢰성 있는 프로토콜이고, UDP는 비연결형, 비신뢰성 프로토콜입니다. 각 프로토콜의 특징에 따라 적절한 소켓을 사용해야 합니다.

  • 소켓의 구성 요소: 소켓은 프로토콜, 소스 주소, 소스 포트, 목적지 주소, 목적지 포트 등 다섯 가지 컴포넌트로 구성됩니다.

추가 설명:

  • 소켓 종류: 스트림 소켓(TCP), 데이터그램 소켓(UDP) 외에도 RAW 소켓 등 다양한 종류의 소켓이 있습니다.

  • 소켓 주소: IP 주소와 포트 번호로 구성되며, 이를 통해 다른 컴퓨터의 특정 애플리케이션과 통신할 수 있습니다.

  • 소켓 함수: 소켓을 생성하고, 연결하고, 데이터를 주고받는 등 다양한 작업을 수행하기 위한 함수들이 있습니다.

2. TCP Socket Programming #1 (TCP 소켓 프로그래밍 1)

  • TCP 서버/클라이언트의 기능 호출: TCP 서버는 listen을 통해 연결 요청을 대기하고, accept로 연결을 수락하며, readwrite로 데이터를 주고받습니다. TCP 클라이언트는 connect를 통해 서버에 연결하고, readwrite로 데이터를 주고받습니다.

  • 예제 코드 분석: 제공된 코드를 통해 TCP 서버와 클라이언트의 구현 방법을 자세히 살펴볼 수 있습니다. 각 함수의 역할과 코드의 흐름을 이해하는 것이 중요합니다.

  • TCP의 특징: TCP는 연결 지향, 신뢰성, 순서 보장 등의 특징을 가지고 있어 안정적인 데이터 전송에 적합합니다.

추가 설명:

  • TCP 3-way handshake: TCP 연결을 설정하기 위한 과정입니다. SYN, SYN-ACK, ACK 패킷을 주고받으며 연결을 확립합니다.

  • TCP 4-way handshake: TCP 연결을 종료하기 위한 과정입니다. FIN 패킷을 주고받으며 연결을 해제합니다.

  • TCP 윈도우 크기: 송신자가 수신자에게 한 번에 보낼 수 있는 데이터의 양을 제한하는 값입니다. 흐름 제어에 사용됩니다.

3. Internet Address and Domain Name (인터넷 주소와 도메인 이름)

  • IP 주소: 컴퓨터를 식별하기 위한 숫자 형태의 주소입니다. IPv4와 IPv6가 있으며, IPv4는 32비트, IPv6는 128비트로 구성됩니다.

  • 포트 번호: 컴퓨터 내에서 실행되는 프로세스를 식별하기 위한 번호입니다.

  • 도메인 이름: 사람이 기억하기 쉬운 형태로 표현된 컴퓨터의 이름입니다. DNS 서버를 통해 IP 주소로 변환됩니다.

  • 바이트 순서: 빅 엔디안과 리틀 엔디안이 있으며, 네트워크 통신에서는 빅 엔디안을 사용합니다.

  • 주소 변환 함수: htons, htonl, inet_addr, inet_ntoa 등의 함수를 사용하여 호스트 바이트 순서와 네트워크 바이트 순서를 변환하거나, IP 주소를 문자열로 변환하고, 그 반대의 작업을 수행할 수 있습니다.

추가 설명:

  • DNS (Domain Name System): 도메인 이름을 IP 주소로 변환해주는 시스템입니다.

  • IP 주소 클래스: A, B, C, D, E 클래스로 나뉘며, 각 클래스마다 네트워크 부분과 호스트 부분의 비율이 다릅니다.

  • 서브네팅: 하나의 네트워크를 여러 개의 작은 네트워크로 나누는 기술입니다.

4. TCP Socket Programming #2 (TCP 소켓 프로그래밍 2)

  • TCP의 특징: TCP의 신뢰성, 순서 보장, 연결 지향 등의 특징을 심층적으로 다룹니다.

  • 서버 종류: 반복 서버와 동시 서버의 차이점을 비교하고, 각각의 장단점을 설명합니다.

  • 소켓 버퍼: 소켓의 입력 버퍼와 출력 버퍼의 역할과 중요성을 설명합니다.

  • 반닫힘: TCP 연결을 부분적으로 종료하는 방법으로, shutdown 함수를 사용하여 구현합니다.

추가 설명:

  • TCP 윈도우 흐름 제어: 송수신 버퍼의 크기를 이용하여 흐름을 제어하는 방식입니다.

  • TCP 혼잡 제어: 네트워크 혼잡을 해결하기 위한 다양한 알고리즘(Slow Start, Congestion Avoidance 등)이 존재합니다.

  • TCP Keep-Alive: 연결 유지를 위한 메커니즘으로, 주기적으로 데이터를 주고받지 않아도 연결이 유지될 수 있도록 합니다.


목차
  1. 1. Introduction
  2. [1] Socket Programming
  3. (1) 목적
  4. (2) 소켓이란?
  5. [2] Socket
  6. (1) Socket Interface
  7. (2) 소켓과 TCP/UDP의 관계도
  8. (3) 특징
  9. 2. TCP Socket Programming #1
  10. [1] TCP Server / Client의 Fnction Call
  11. (1) Server
  12. (2) Client
  13. [2-1] Example : tcp_server.c
  14. (1) 코드
  15. (2) 특징
  16. (3) 코드 실행 및 실행 결과
  17. [2-2] Example : tcp_client.c
  18. (1) 코드
  19. (2) 특징
  20. (3) 코드 실행 및 실행 결과
  21. [3-1] socket()
  22. 1) 역할
  23. 2) 파라미터 설명
  24. [3-2] bind()
  25. 1) 역할
  26. 2) 파라미터 설명
  27. [3-3] listen()
  28. 1) 역할
  29. 2) 파라미터 설명
  30. [3-4] accept()
  31. 1) 역할
  32. 2) 파라미터 설명
  33. [3-5] connect()
  34. 1) 역할
  35. 2) 파라미터 설명
  36. [3-6] read()
  37. 1) 역할
  38. 2) 파라미터 설명
  39. [3-7] write()
  40. 1) 역할
  41. 2) 파라미터 설명
  42. [3-8] recv()
  43. 1) 역할
  44. 2) 파라미터 설명
  45. [3-9] send()
  46. 1) 역할
  47. 2) 파라미터 설명
  48. [3-10] close()
  49. 1) 역할
  50. 2) 파라미터 설명
  51. [tip - 1] read / write와 recv / send의 차이점
  52. 1. read / write
  53. 2. recv / send
  54. 비교 요약
  55. [tip - 2] Listen/accept는 주로 TCP에서 쓴다면, UDP에서는?
  56. 1. TCP와 UDP의 연결 방식
  57. 2. UDP에서의 서버/클라이언트 통신 방식
  58. [tip - 3] 10개의 함수를 간단하게 요약한 내용
  59. 3. Internet Address and Domain Name
  60. [1] 인터넷 주소
  61. 1) IP 주소
  62. 2) Port
  63. 3) Port의 종류
  64. [2] 주소 구조
  65. 1) <netinet/in.h>에 정의되어 있다.
  66. 2) 예시
  67. [3] 바이트 순서 변환
  68. 1) 바이트 순서
  69. 2) 네트워크 바이트 순서 = 빅 엔디안
  70. [4] htons(), ntohs(), htonl(), ntohl()
  71. 1) 각 함수 별 정의
  72. 2) 역할
  73. 3-1) 예시: endian_conv.c
  74. 3-2) 결과
  75. 4) Long은 addr, short는 port에 쓰이는 이유
  76. [5] inet_addr(), inet_aton()
  77. 1) 역할
  78. 2) 함수 별 특징
  79. 3-1) Example (inet_addr.c)
  80. 3-2) 실행 결과
  81. [6] inet_ntoa()
  82. 1) 역할
  83. 2) 함수 별 특징
  84. 3-1) 예시 (inet_ntoa.c)
  85. 3-2) 실행 결과
  86. [7] gethostbyname()
  87. 1) 역할
  88. 2) 반환 값
  89. 3-1) 예시 (gethostbyname.c)
  90. 3-2) 결과
  91. [8] hostent의 구조
  92. 1) netdb.h에 정의되어 있다.
  93. [9] gethostbyaddr()
  94. 1) 역할
  95. 2) 파라미터 설명
  96. 3) 반환 값
  97. 4) 예시 (gethostbyaddr.c)
  98. 4. TCP Socket Programming #2
  99. [1] TCP: 개요 (RFC: 793, 1122, 2018, 5681, 7323)
  100. 1) 특징
  101. [2] TCP의 메시지 경계 없음
  102. 1) 특징
  103. 2-1) 예시
  104. 2-2) 결과
  105. [3] 서버 종류
  106. 1) 반복 서버 (Iterative Server)
  107. 2) 동시 서버 (Concurrent Server)
  108. [4] Iterative vs Concurrent servers
  109. 1) Iterative server skeleton
  110. 2) Concurrent server skeleton
  111. [5] TCP 소켓의 입력 및 출력 버퍼
  112. 1) 설명
  113. [6] Half-close
  114. [7] shutdown()
  115. 1) 역할
  116. 2) 파라미터 설명
  117. 3) 반환 값
  118. 4) 예시
  119. 섹션별 정리 및 추가 설명 (심화)
  120. 1. Introduction (소개)
  121. 2. TCP Socket Programming #1 (TCP 소켓 프로그래밍 1)
  122. 3. Internet Address and Domain Name (인터넷 주소와 도메인 이름)
  123. 4. TCP Socket Programming #2 (TCP 소켓 프로그래밍 2)