티스토리 뷰

CS/네트워크

소켓 프로그래밍 완전 정복하기

개발자 쿠키 2025. 3. 7. 18:08

소켓의 사전적 정의

전기 공급 인프라 환경에 연결할 수 있게 만들어진 연결부 

 

네트워크 프로그래밍에서의 소켓이란

프로그램이 네트워크에서 데이터를 송수신할 수 있도록 네트워크 환경에 연결할 수 있게 만들어진 연결부

 

소켓 (Socket) 

네트워크를 경유하는 프로세스 간 통신의 종착점. OSI 7계층 중 응용 계층에 속하는 프로세스들은 데이터 송수신을 위해 반드시 소켓을 거쳐 전송 계층으로 데이터를 전달해야한다.

즉, 소켓은 전송 계층과 응용 프로그램 사이의 인터페이스 역할을 하며 떨어져 있는 두 호스트를 연결해준다.

 

Socket API 흐름

client socket : 생성(socket) → 연결(connect) → 송수신(send/recv) → 닫기(close)
server socket : 생성(socket) → 결합(bind) → 주시(대기, listen) → 받기(accept) → 송수신(send/recv) → 닫기(close)

 

socket 프로그래밍 관련

함수명 원형

socket int socket(int family, int type, int protocol)
bind int bind(int sockfd, const struct sockaddr *my addr, socklen_t addrlen)
listen int listen(int sockfd, int backlog)
accept int accept(int sockfd, struct sockaddr *clientaddr, socklen_t *addrlen)
connect int connect(int sockfd, const struct sockaddr *serveraddr, socklen_t addrlen)
read ssize_t read(int fd, void *buff, size_t nbytes)
write ssize_t write(int fd, const void *buff, size_t nbytes)

 

socket.h 파일의 역할

/usr/include/sys/socket.h 소켓 API 제공 (사용자가 직접 #include하는 파일)
/usr/include/linux/socket.h 리눅스 커널 관련 소켓 상수 및 구조체
/usr/include/asm/socket.h 아키텍처별(예: x86, ARM) 소켓 관련 설정
/usr/include/asm-generic/socket.h 여러 아키텍처에서 공통적으로 사용되는 소켓 설정
/usr/include/bits/socket.h 내부적으로 사용되는 비트 필드 관련 설정

일반적인 네트워크 프로그래밍에서는 sys/socket.h를 포함하면 충분함.
커널이나 저수준 네트워크 프로그래밍을 할 때는 linux/socket.h 등을 직접 포함할 수도 있음.



#inlcude <socket.h>만 하면 되는 이유?

리눅스의 내부 구현과 관련된 파일들이며, 사용자가 직접 포함할 필요가 없음. (자동 참조)

 

socket 실습

int main() {
    int x = 1;
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(10000);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr));

    listen(listenfd, 5);

    int connfd = accept(listenfd, (struct sockaddr *) NULL, NULL);

    write(connfd, "Hello", 6);
    close(connfd);

    printf("Server: All done");

    return 0;
}

 

 

1. 소켓 생성

int listenfd = socket(AF_INET, SOCK_STREAM, 0)
  • AF_INET : ipv4 version 사용하겠다는 의미
  • SOCK_STREAM : TCP, SOCK_DGRAM: UDP
  • 0은 소켓에서 사용할 프로토콜
    • IPPROTO_TCP : TCP
    • IPPROTO_UDP : UDP
    • 0 : type(SOCK_STREAM or SOCK_DGRAM)에서 정해진 경우

 

2. bind 함수

struct sockaddr_in servaddr
servaddr.sin_family = AF_INET
servaddr.sin_port = htons(10000)
servaddr.sin_addr.s_addr = htonl(INADDR_ANY)
bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr))
  • bind(소켓 fd, 주소 정보 (형변환), 구조체 크기)
  • 핵심
    • sockaddr_in 구조체 bind 함수
    • #include <sys/types.h>
    • #include <sys/socket.h>

2. bind 함수 원형

int bind(SOCKET sockfd, const sockaddr *my_addr, int namelen);
  • sockfd : 앞서 socket 함수로 생성된 endpoint 소켓의 식별 번호
  • my_addr : IP 주소와 port 번호를 저장하기 위한 변수가 있는 구조체
  • namelen : 두 번째 인자의 데이터 크기
  • 반환값 : 성공하면 0, 실패하면 -1

 

* htons() vs htonl() 비교
함수 변환 대상 크기 사용 예시

htons() short (16비트, 2바이트) 2B 포트 번호 (sin_port)
htonl() long (32비트, 4바이트) 4B IP 주소 (sin_addr.s_addr)

* ntohs()와 ntohl()

  • ntohs() : 네트워크 바이트 순서를 호스트 바이트 순서로 변환 (htons()의 반대)
  • ntohl() : 네트워크 바이트 순서를 호스트 바이트 순서로 변환 (htonl()의 반대)

 

3. listen 함수

int listen(int sockfd, int backlog);
  • socketfd : 수신 대기할 소켓
  • backlog : 대기 큐 크기 (최대 대기 중인 연결 요청 개수)
  • 성공 시 0, 실패 시 -1 리턴
  • 클라이언트 연결 요청에 대한 정보는 시스템 내부적으로 관리되는 queue에서 쌓이게 되는데, 이 시점에서 클라이언트와의 연결은 아직 완전히 연결되지 않은(not ESTABLISHED state) 대기 상태

4. accept 함수

accept(listenfd, (struct sockaddr *) NULL, NULL)

 

client socket과 연결이 만들어지는 socket은 서버 소켓이 아니라 accept API 내부에서 새로 만들어지는 socket
accept() API에서 데이터 송수신을 위한 새로운 소켓을 만들고 서버 소켓의 대기 큐에 쌓여있는 첫 번째 연결 요청을 매핑시킴

 

5. write 함수
write로 client에게 메시지 전달 후 close 함수로 연결을 끊음

 

구조체 2종류

  • sockaddr
  • sockaddr_in

 

sockaddr 구조체

struct sockaddr {
   sa_family_t sa_family;    // 소켓 주소체계 (대부분 AF_INET)
   char sa_data[14];         // 14 바이트로 실제 주소 저장을 위함
}

 

sa_family_t가 AF_INET인 경우 사용하는 구조체

struct sockaddr_in {
    sa_family_t sin_family;      // 2바이트: 주소 체계(AF_INET)
    unsigned short int sin_port; // 2바이트: 포트 번호
    struct in_addr sin_addr;     // 4바이트: IPv4 주소 (in_addr 구조체)
    unsigned char sin_zero[8];   // 8바이트: 패딩(사용되지 않음), 0으로 채움
};