Socket API

이재원·2024년 2월 28일

Socket API란?

  • 정의
    • System Call API 중 하나
    • 운영체제가 응용프로그램에 제공하는 통신용 인터페이스
  • 역할
    • 컴퓨터 하드웨어의 네트워크 통신장치를
      응용프로그램이 간접적으로 이용할 수 있게 해줌

Socket이란?

  • 정의
    • 응용프로그램에 설치하는 데이터 송수신 출입구
  • 쓰는 이유
    • 응용프로그램은 응용프로그램의 일만 해도된다.
      • 응용프로그램이 데이터 송수신을 위한 각종의 일을 직접 수행할 필요 없다.
      • 통신의 업무는 운영체제의 네트워크 스택이 해준다.
        • 네트워크 스택이 하는 일
          • 데이터 패킷화
          • 주소 지정
          • 라우팅
          • 전송 오류 처리
    • 컴퓨터가 데이터들을 송수신할 때, 꼬이지 않게 함
      • 컴퓨터 내에서 실행되는 다양한 프로세스가 엄청난 양의 데이터 송수신을 일으킨다.
      • 운영체제에 의해 데이터 송수신이 중앙 집권적으로 관리하면 다양한 문제를 해결할 수 있다.
      • 운영체제는 응용프로그램에게 통신시 사용할 수 있는 socket API를 제공하여 응용프로그램은 힘든 과정 없이 쉽게 보내고 받을 수 있다.
      • 어떤 응용프로그램이 송수신하는 데이터인지 운영체제 측에서 알기 위해 socket이 end point역할을 하여 송수신지 문제 없이 관리된다.
      • socket == end point of the process

1) 소켓 생성 socket()

  • 네트워크 통신을 위한 소켓을 만든다.
  • 소켓 생성시 프로토콜 패밀리와 소켓 유형을 지정해야 한다.
  • 주로 사용되는 프로토콜 패밀리는 AF_INET(혹은 PF_INET)이며, 소켓 유형은 SOCK_STREAM(연결 지향 소켓) 혹은 SOCK_DGRAM(비연결 지향 소켓) 등이 있다.

2) 소켓 바인딩 bind()

  • 소켓에 IP 주소와 포트 번호를 할당
  • 서버 프로그램에서 주로 사용
  • 클라이언트가 서버와 통신할 수 있는 경로를 제공한다.

3) 연결 수립

  • 클라이언트
    • connect()
      • 클라이언트에서 사용되는 함수
      • 서버에 연결을 요청하는 함수
      • 서버의 IP 주소와 포트 번호를 지정하여 서버에 연결한다.
  • 서버
    • listen()
      • 연결 요청을 수신하기 위해 소켓을 대기 상태로 만드는 함수
      • 소켓을 수동 대기 모드로 설정하고, 클라이언트의 연결 요청을 기다린다.
    • accept()
      • 클라이언트의 연결 요청을 수락하는 함수
      • 연결된 소켓을 반환한다. 반환된 소켓을 통해 클라이언트와 통신할 수 있다.

4) 데이터 송수신

  • send()
    • 데이터 송신
  • recv()
    • 데이터 수신
  • read(), write()도 동일한 목적으로 사용 가능하다.
    • Linux에서만 사용가능
    • 네트워크 장치를 file로 인식하는 OS 특성상 사용 가능한 system call 함수
    • 특히 위 함수들은
      네트워크 실행 특성을 반영하는 정보를 읽고 쓰기 위해 만들어진 것이 아니기 때문에
      기능이 제한적임
    • 이 때문에, UDP의 경우 반드시 send를 써야 통신이 가능함
      위 내용 어디 있었는지 확인

5) 소켓 닫기

  • close()
    • 통신이 종료된 후 소켓을 닫는 함수
    • 호출 시 소켓이 제거되고 할당된 자원이 반환

네트워크 통신 과정

1. 서버설정

  1. 서버는 클라이언트와 통신을 하기위해 socket()함수를 호출하여 소켓을 생성한다.
  2. 생성된 소켓에 bind()함수를 이용하여 IP주소와 포트번호를 할당한다.
  3. 서버는 클라이언트의 연결요청을 대기하기 위해 listen() 함수를 호출하여 소켓을 대기 상태로 설정한다.

2. 클라이언트 연결

  1. 클라이언트는 서버와 통신을 하기위해 socket() 함수를 호출하여 소켓을 생성한다.
  2. connect함수를 호출하여 서버의 IP주소와 포트번호를 지정하여 서버에 연결을 시도한다.

3. 연결수락

  1. 서버는 클라이언트의 연결 요청을 받아들이고 처리한다.
  2. 클라이언트의 연결요청이 도착하면, accept() 함수를 호출하여 이를 수락하고 클라이언트와 연결된 새로운 소켓을 반환한다.

4. 데이터 송수신

  1. 연결된 소켓을 통해 데이터를 주고받는다.
    • 클라이언트는 send()나 write()함수를 사용하여 서버에 데이터를 보낸다.
    • 서버는 recv()나 read()함수를 사용하여 클라이언트로 부터 데이터를 수신한다.
    • ※ 서버에서 데이터를 응답하여 보낼때도 send()나 write()함수 사용 (데이터를 보낼 때 사용)

5. 연결 종료

  1. 통신이 완료되면 서버와 클라이언트는 close() 함수를 사용하여 연결된 소켓을 닫는다.

getaddrinfo vs freeaddrinfo

  • getaddrinfofreeaddrinfo 함수는 네트워크 프로그래밍에서 주소 정보를 가져오고 해제하는 데 사용한다.

getaddrinfo

  • getaddrinfo 함수는 호스트 이름과 서비스 이름(또는 포트 번호)을 기반으로 주소 정보를 가져온다.
  • 주어진 호스트 및 서비스에 대한 IP 주소 및 관련 정보를 가져온다.
  • EX) 주어진 호스트 이름과 포트 번호에 대해 IPv4 또는 IPv6 주소를 가져올 수 있다.
  • 주소 정보를 저장하기 위한 메모리를 할당하고 해당 메모리에 주소 정보를 채운다.
  • 주요 헤더: #include <netdb.h>
  • 함수 원형
int getaddrinfo(
const char *node, 
const char *service, 
const struct addrinfo *hints, 
struct addrinfo **res
);

freeaddrinfo

  • freeaddrinfo 함수는 getaddrinfo 함수에 의해 할당된 메모리를 해제한다.
  • getaddrinfo 함수 호출 후에는 반드시 해당 함수를 호출하여 할당된 메모리를 해제해야 한다.
  • 메모리 누수를 방지하기 위해 사용된다.
  • 주요 헤더: #include <netdb.h>
  • 함수 원형
void freeaddrinfo(struct addrinfo *res);

open_clientfd vs open_listenfd

  • open_clientfdopen_listenfd 함수는 클라이언트 및 서버 소켓을 생성하고 연결하는 데 사용한다.

open_clientfd

  • open_clientfd 함수는 클라이언트 소켓을 열고 서버에 연결한다.
  • TCP 클라이언트를 생성하고 서버로의 연결을 수행한다.
  • 주어진 호스트 이름과 포트 번호를 기반으로 서버에 연결한다.
  • 연결이 성공하면 클라이언트 소켓 파일 디스크립터를 반환하고, 실패하면 -1을 반환한다.
  • 주요 헤더: #include "csapp.h"
  • 함수 원형
int open_clientfd(const char *hostname, const char *port);

open_listenfd

  • open_listenfd 함수는 서버 소켓을 생성하고 클라이언트의 연결을 대기한다.
  • 주어진 포트 번호에 바인딩된 서버 소켓을 생성하고 클라이언트의 연결을 수신할 수 있도록 대기한다.
  • 서버 소켓 파일 디스크립터를 반환하고, 실패하면 -1을 반환한다.
  • 주요 헤더: #include "csapp.h"
  • 함수 원형
int open_listenfd(const char *port);

Rio_readnb vs Rio_readlineb

  • 모두 Robust I/O (RIO) 패키지에 속하는 함수들로, 네트워크 프로그래밍에서 데이터를 안정적으로 읽기 위해 사용된다.
  • Rio_readnb

    • 지정된 바이트 수만큼 데이터를 읽는 데 사용
    • 바이너리 데이터 또는 특정 길이의 데이터를 읽을 때 유용하다.
    • Rio_readnb는 지정된 크기의 데이터를 모두 읽을 때까지 반복적으로 네트워크로부터 데이터를 읽는다.
    • 내부 버퍼를 사용하지 않고, 사용자가 지정한 버퍼에 직접 데이터를 채운다.
    • 호출자가 요청한 바이트 수가 완전히 읽힐 때까지 함수는 데이터를 계속 읽는다.
    • 파일 전송이나 이미지와 같은 바이너리 데이터를 처리할 때 매우 유용
  • Rio_readlineb

    • 한 번에 한 줄씩 데이터를 읽는 데 사용
    • 텍스트 기반의 프로토콜을 처리할 때 유용
    • 줄의 끝을 식별할 수 있다.
    • HTTP 요청이나 응답 헤더를 처리할 때 이 함수가 자주 사용
    • 내부 버퍼를 사용하여 네트워크로부터 데이터를 읽는다.
    • 줄의 끝(newline) 문자를 만날 때까지 데이터를 읽고, 이를 반환
    • \n 또는 \r\n으로 표현
  • 주요 차이점

    • 데이터 읽기 단위: Rio_readlineb는 줄 단위로 데이터를 읽는 반면, Rio_readnb는 바이트 단위로 지정된 크기만큼 데이터를 읽습니다.
    • 사용 케이스: Rio_readlineb는 텍스트 기반 데이터 처리에 적합하고, Rio_readnb는 바이너리 데이터 또는 고정 길이 데이터의 처리에 적합합니다.
    • 내부 동작: Rio_readlineb는 줄의 끝을 만날 때까지 데이터를 읽고, Rio_readnb는 요청한 바이트 수를 모두 읽을 때까지 데이터를 읽습니다.

Big-endian(빅 엔디안) VS Little-endian(리틀 엔디안)

  • 빅 엔디안( Big-endian)과 리틀 엔디안( Little-endian)은 컴퓨터 아키텍처에서 메모리나 데이터를 저장하는 방식을 나타낸다.

1. 빅 엔디안 (Big-endian):

  • 빅 엔디안은 가장 높은 유효 바이트부터 메모리에 저장한다.
  • Ex) 0x12345678이라는 4바이트 데이터를 저장할 때, 가장 높은 유효 바이트인 0x12가 메모리의 가장 낮은 주소에 저장되고, 그 다음으로 0x34, 0x56, 0x78이 순서대로 저장된다.
  • 네트워크 통신에서 일반적으로 사용한다.

2. 리틀 엔디안 (Little-endian):

  • 리틀 엔디안은 가장 낮은 유효 바이트부터 메모리에 저장한다.
  • EX) 0x78이 가장 낮은 주소에 저장되고, 그 다음으로 0x56, 0x34, 0x12가 순서대로 저장된다.
  • x86 아키텍처와 같은 많은 프로세서에서 사용됩니다.

  • 빅 엔디안과 리틀 엔디안은 데이터를 저장하고 해석하는 방식에 차이가 있기 때문에, 서로 다른 엔디안을 사용하는 시스템 간에 데이터 교환 시에는 엔디안 변환 작업이 필요할 수 있다.
  • 엔디안 변환은 데이터의 바이트 순서를 조정하여 호환성을 확보하는 데 사용된다.
profile
최고가 되기 위한 여정

0개의 댓글