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. 서버설정
- 서버는 클라이언트와 통신을 하기위해 socket()함수를 호출하여 소켓을 생성한다.
- 생성된 소켓에 bind()함수를 이용하여 IP주소와 포트번호를 할당한다.
- 서버는 클라이언트의 연결요청을 대기하기 위해 listen() 함수를 호출하여 소켓을 대기 상태로 설정한다.
2. 클라이언트 연결
- 클라이언트는 서버와 통신을 하기위해 socket() 함수를 호출하여 소켓을 생성한다.
- connect함수를 호출하여 서버의 IP주소와 포트번호를 지정하여 서버에 연결을 시도한다.
3. 연결수락
- 서버는 클라이언트의 연결 요청을 받아들이고 처리한다.
- 클라이언트의 연결요청이 도착하면, accept() 함수를 호출하여 이를 수락하고 클라이언트와 연결된 새로운 소켓을 반환한다.
4. 데이터 송수신
- 연결된 소켓을 통해 데이터를 주고받는다.
- 클라이언트는 send()나 write()함수를 사용하여 서버에 데이터를 보낸다.
- 서버는 recv()나 read()함수를 사용하여 클라이언트로 부터 데이터를 수신한다.
- ※ 서버에서 데이터를 응답하여 보낼때도 send()나 write()함수 사용 (데이터를 보낼 때 사용)
5. 연결 종료
- 통신이 완료되면 서버와 클라이언트는 close() 함수를 사용하여 연결된 소켓을 닫는다.
getaddrinfo vs freeaddrinfo
getaddrinfo 및 freeaddrinfo 함수는 네트워크 프로그래밍에서 주소 정보를 가져오고 해제하는 데 사용한다.
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_clientfd 및 open_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 아키텍처와 같은 많은 프로세서에서 사용됩니다.
- 빅 엔디안과 리틀 엔디안은 데이터를 저장하고 해석하는 방식에 차이가 있기 때문에, 서로 다른 엔디안을 사용하는 시스템 간에 데이터 교환 시에는 엔디안 변환 작업이 필요할 수 있다.
- 엔디안 변환은 데이터의 바이트 순서를 조정하여 호환성을 확보하는 데 사용된다.