CSAPP/11장

[CSAPP] 11장 네트워크 프로그래밍(Network Programming) 11.4

넌뭐가그렇게중요해 2025. 5. 3. 16:34

11.4 소켓 인터페이스(Socket Interface)

1. 소켓(socket)

네트워크 소켓은 프로세스 간 또는 호스트 간 데이터 송수신을 위한 소프트웨어 구조로,

  • 커널 관점에서는 패킷의 입·출력을 중개하는 통신의 종단점(endpoint)
  • 애플리케이션 관점에서는 read()/write() 가능한 파일 디스크립터(fd)

라고 볼 수 있습니다.

도메인(domain): AF_INET(IPv4), AF_INET6(IPv6), AF_UNIX(로컬) 등을 지정
타입(type): SOCK_STREAM(연결 지향/TCP), SOCK_DGRAM(비연결 지향/UDP) 등
프로토콜(protocol): 보통 0으로 두면 타입에 따라 기본 프로토콜(TCP, UDP)을 자동 선택합니다.

비유: 소켓은 ‘전화기’, 커널은 ‘전화 교환국’에 비유할 수 있습니다.
전화기(소켓)를 들고 상대방 번호(IP:포트)로 전화를 걸면(connect),
교환국(커널)이 해당 전화를 걸어 연결해 주는 방식입니다.

2. 소켓 인터페이스(Socket Interface) 핵심 함수 상세

Unix 계열부터 Windows, macOS까지 대부분의 운영체제는 BSD 소켓 API라는 함수 집합을 제공하여, 애플리케이션이 네트워크 통신을 쉽게 구현하도록 돕습니다. 

2.1 socket()

int sockfd = socket(int domain, int type, int protocol);

 

  • 동작: 통신 종단점 생성, 새로운 파일 디스크립터 반환
  • 주요 인자
    • domain: 주소 체계(AF_INET 등)
    • type: 통신 방식(SOCK_STREAM 등)
    • protocol: 세부 프로토콜 번호(대개 0)
  • 반환값: 성공 시 소켓 디스크립터 ≥0, 실패 시 -1
  • 비유: 전화기를 꺼내고(디바이스 준비) 전화를 걸 준비를 하는 단계

2.2 bind()

int ret = bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 

  • 목적: 서버 측에서 로컬 IP·포트와 소켓 연결
  • 인자
    • sockfd: socket()으로 얻은 디스크립터
    • addr: sockaddr_in 구조체(주소, 포트 포함) 포인터
    • addrlen: 구조체 크기(sizeof(struct sockaddr_in))
  • 반환값: 성공 0, 실패 -1
  • 설명: 아직 전화를 걸지 않고 수화기를 벽(네트워크 인터페이스)에 연결해 번호를 지정하는 과정과 유사

 

2.3 connect()

int ret = connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

 

  • 목적: 클라이언트가 원격 서버(IP·포트)에 연결 요청
  • 인자
    • sockfd: socket() 디스크립터
    • serv_addr: 서버 주소·포트 정보
    • addrlen: 구조체 크기
  • 반환값: 성공 0, 실패 -1
  • 비유: 전화 걸기 버튼을 누르고 상대방과 연결되길 기다리는 단계

 

2.4 listen()

int ret = listen(int sockfd, int backlog);

 

 

  • 목적: 서버 소켓을 수동 모드로 전환, 클라이언트 연결 요청 대기열 생성
  • 인자
    • sockfd: bind()된 소켓 디스크립터
    • backlog: 대기열에 올릴 최대 연결 수
  • 반환값: 성공 0, 실패 -1
  • 설명: 전화 교환원에게 “벨 울려도 돼” 신호를 주고 벨을 켜놓는 행위와 유사

 

2.5 accept()

int connfd = accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

 

  • 목적: 수신된 연결 요청 수락 후 새 소켓 디스크립터 반환
  • 인자
    • sockfd: listen() 상태의 소켓
    • cliaddr: 클라이언트 주소 저장용 버퍼
    • addrlen: 버퍼 크기 정보 입출력용
  • 반환값: 연결된 새 디스크립터 ≥0, 실패 -1
  • 비유: 교환원(커널)이 벨 울림을 받아 수화기를 들어 고객과 통화 연결을 맺는 단계

 

2.6 close()

int ret = close(int sockfd);

 

  • 목적: 소켓 자원 반환, 연결 종료
  • 인자: 닫을 소켓 디스크립터
  • 반환값: 성공 0, 실패 -1
  • 비유: 통화 종료 후 수화기를 내려놓는 과정

 


 

3. 헬퍼 함수

  • open_clientfd(host, port)
    내부적으로 getaddrinfo() → socket() → connect()를 순차 시도
  • open_listenfd(port)
    내부적으로 getaddrinfo(NULL,port,AI_PASSIVE) → socket() → setsockopt(SO_REUSEADDR) → bind() → listen()

open_listenfd에서 setsockopt(..., SO_REUSEADDR, ...)는 재시작 시 빠른 포트 재사용을 위해 로컬 주소 검증 규칙을 완화합니다.

 

소켓 인터페이스 기반 네트워크 응용프로그램의 개오

  1. 클라이언트(Client) 흐름
    • getaddrinfo → socket 으로 네트워크 파라미터(IP, 포트, 프로토콜) 준비
    • open_clientfd 헬퍼 함수로 위 두 단계를 캡슐화
    • 이어서 connect 호출로 서버에 연결 요청
    • 연결이 성립되면 rio_writen으로 데이터 전송 → rio_readlineb로 응답 수신
    • 입력 스트림이 끝나면 close로 소켓 종료
  2. 서버(Server) 흐름
    • getaddrinfo → socket → bind → listen 으로 수신 대기 소켓 준비
    • open_listenfd 헬퍼 함수로 위 과정을 캡슐화
    • accept 호출로 클라이언트 연결 요청을 대기 → 요청이 들어오면 새로운 연결 디스크립터(connfd) 반환
    • rio_readlineb로 클라이언트 데이터 수신 → rio_writen으로 에코 응답
    • 클라이언트가 연결을 닫으면(EOF) 다시 rio_readlineb가 0을 반환 → close → 다음 클라이언트 요청 대기

이 다이어그램은 “전화 통화”에 비유하면, 클라이언트가 교환국(getaddrinfo/socket)을 거쳐 전화를 걸고(connect), 음성을 주고받은 후 종료(close)하는 과정을 한눈에 보여 줍니다. 서버는 “벨을 켜놓고(listen)” 고객 전화를 기다리다가(accept), 연결이 들어오면 수화기를 들어 응대(echo)하고, 통화가 끝나면 내려놓는(close) 형태입니다.


4. 소켓 주소 구조체

/* IP 소켓 주소 구조체 */
struct sockaddr_in {
    uint16_t sin_family;   /* 프로토콜 패밀리 (AF_INET) */
    uint16_t sin_port;     /* 포트 번호 (네트워크 바이트 오더) */
    struct in_addr sin_addr; /* IPv4 주소 (네트워크 바이트 오더) */
    unsigned char sin_zero[8]; /* struct sockaddr 크기 패딩 */
};

/* 제네릭 소켓 주소 구조체 */
struct sockaddr {
    uint16_t sa_family;    /* 프로토콜 패밀리 */
    char     sa_data[14];  /* 주소 데이터 (IP+포트 등) */
};

 

  • sin_family: 주소 체계(AF_INET)
  • sin_port: htons(port)로 변환 후 저장
  • sin_addr.s_addr: inet_pton() 또는 htonl(INADDR_ANY)
  • sin_zero: struct sockaddr와 크기 맞추기 위한 패딩

struct sockaddr와 첫 멤버가 같아 포인터 캐스트가 자유롭습니다.


5. C(예시)

서버(Echo)

#include <stdio.h>              // 입출력 함수 사용을 위해 포함
#include <stdlib.h>             // 표준 라이브러리 함수 사용을 위해 포함
#include <string.h>             // 문자열 처리 함수 사용을 위해 포함
#include <unistd.h>             // POSIX 시스템 호출(예: close) 사용을 위해 포함
#include <arpa/inet.h>          // 인터넷 주소 변환 함수 사용을 위해 포함

#define PORT 1100               // 서버가 사용할 포트 번호 정의
#define BACKLOG 10              // 대기열(벨) 최대 연결 수 정의
#define BUFSIZE 1024            // 버퍼 크기 정의

int main() {
    int listenfd, connfd;                            // 듣기용·연결용 소켓 디스크립터 변수 선언
    struct sockaddr_in serv_addr;                    // 서버 주소 정보를 담을 구조체 선언

    listenfd = socket(AF_INET, SOCK_STREAM, 0);      // 소켓 생성: IPv4, TCP 방식, 프로토콜 자동 선택
    if (listenfd < 0) perror("socket"), exit(1);     // 오류 시 메시지 출력 후 종료

    memset(&serv_addr, 0, sizeof(serv_addr));        // 구조체를 0으로 초기화
    serv_addr.sin_family = AF_INET;                  // 주소 체계를 IPv4로 설정
    serv_addr.sin_port   = htons(PORT);              // 포트 번호를 네트워크 바이트 오더로 변환하여 설정
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 모든 로컬 인터페이스에서 수신 허용

    if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
        perror("bind"), exit(1);                     // 소켓과 주소(번호) 연결, 실패 시 종료

    if (listen(listenfd, BACKLOG) < 0)
        perror("listen"), exit(1);                   // 연결 요청 대기열 초기화, 실패 시 종료

    while ((connfd = accept(listenfd, NULL, NULL)) >= 0) {  // 클라이언트 연결 수락 반복
        char buf[BUFSIZE];                           // 데이터 송수신용 버퍼 선언
        ssize_t n;                                   // 읽기/쓰기 바이트 수 저장 변수

        while ((n = read(connfd, buf, BUFSIZE)) > 0) {  // 클라이언트로부터 데이터 읽기
            write(connfd, buf, n);                   // 받은 데이터를 그대로 다시 클라이언트로 전송
        }
        close(connfd);                               // 클라이언트 연결 종료
    }
    close(listenfd);                                 // 듣기 소켓 닫기
    return 0;                                        // 프로그램 정상 종료
}

클라이언트(client)

#include <stdio.h>              // 입출력 함수 사용을 위해 포함
#include <stdlib.h>             // 표준 라이브러리 함수 사용을 위해 포함
#include <string.h>             // 문자열 처리 함수 사용을 위해 포함
#include <unistd.h>             // POSIX 시스템 호출(예: close) 사용을 위해 포함
#include <arpa/inet.h>          // 인터넷 주소 변환 함수 사용을 위해 포함

#define PORT 1100               // 서버 연결에 사용할 포트 번호 정의
#define BUFSIZE 1024            // 버퍼 크기 정의

int main() {
    int sockfd;                                   // 클라이언트 소켓 디스크립터 변수 선언
    struct sockaddr_in serv_addr;                 // 서버 주소 정보를 담을 구조체 선언
    char *msg = "Hello, CSAPP Socket!";           // 서버로 보낼 메시지 선언
    char buf[BUFSIZE];                            // 서버 응답 수신용 버퍼 선언

    sockfd = socket(AF_INET, SOCK_STREAM, 0);     // 소켓 생성: IPv4, TCP 방식, 프로토콜 자동 선택
    if (sockfd < 0) perror("socket"), exit(1);    // 오류 시 메시지 출력 후 종료

    memset(&serv_addr, 0, sizeof(serv_addr));     // 구조체를 0으로 초기화
    serv_addr.sin_family = AF_INET;               // 주소 체계를 IPv4로 설정
    serv_addr.sin_port   = htons(PORT);           // 포트 번호를 네트워크 바이트 오더로 변환하여 설정
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);  // 문자열 IP를 이진 형태로 변환

    if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
        perror("connect"), exit(1);               // 서버에 연결 요청, 실패 시 종료

    write(sockfd, msg, strlen(msg));              // 서버로 메시지 전송
    read(sockfd, buf, BUFSIZE);                   // 서버로부터 에코 응답 수신
    printf("Echo: %s\n", buf);                    // 수신된 메시지 출력

    close(sockfd);                                // 서버 연결 종료
    return 0;                                     // 프로그램 정상 종료
}

'CSAPP > 11장' 카테고리의 다른 글

소켓  (1) 2025.05.02
[CSAPP] 11장 네트워크 프로그래밍(Network Programming) 11.1 ~ 11.3  (0) 2025.05.02