소켓이란?
클라이언트와 서버는 소켓 식별자를 읽고 쓰면서 서로 통신하는데, 이때 소켓은 통신 양 끝 점을 담당한다. Berkeley Socket(BSD Socket)에서는 소켓을 File Descriptor의 형식이며, Unix의 철학인 모든 것은 파일이다에서부터 출발했다고 정의한다. 따라서 네트워크를 포함한 모든 Unix I/O 디바이스들은 파일이므로 소켓 역시 네트워크 상의 다른 프로세스와 통신하는 역할을 하는 파일로 볼 수 있다. 그리고 현재의 Internet 소켓들은 대부분 이런 BSD 표준을 따른다.
File Descriptor
소켓 주소 IP 주소 + 포트 번호로 표현할 수 있다.
generic 소켓 주소 구조체인 sockaddr은 connect, bind, accept 함수의 인자로 넣어주기 위한 16바이트의 구조체로 예전 C언어에는 (void*) 타입이 없어서 인자를 캐스팅해주기 위해서 필요했다. 그럼 sockaddr 함수에 대해서 조금 더 자세히 알아보자.
struct sockaddr {
unit16_t sa_family; // Protocol family
char sa_data[14]; // Address data
}
- sa_family 필드 : 이 소켓의 종류가 무엇인지. 즉, TCP, UDP, IPv6인지를 말해준다.
IPv4 인터넷 소켓 구조체인 sockaddr_in은 현재 인터넷에서 활용되는 IPv4 소켓 주소에서 사용되는 종류로, 더 구체적인 정보를 담고있는 16바이트 구조체이다. 이러한 소켓 주소 구체인 sockaddr을 인자로 받는 함수에 넣어주기 위해서는 (struct sockaddr*)으로 형변환해주어야 한다.
struct sockaddr_in {
uint16_t sin_family; // Protocol family (AF_INET)
uint16_t sin_port; // Port number in network byte order
struct in_addr sin_addr; // IP addr in network byte order
unsigned char sin_zero[8]; // Pad to sizeof(struct sockaddr)
}
- sin_family : AF_INET. 즉, 32비트 IPv4 주소를 사용한다는 것을 나타낸다.
- sin_port : 16비트 port 번호로 네트워크 바이트 순서로 저장된다.
- sin_addr : 32비트 IP 주소로 네트워크 바이트 순서로 저장된다.
- sin_zero : sockaddr 구조체와 사이즈를 맞취기 위한 패딩이다.
소켓 인터페이스
간단히 말해서 TCP/IP 계층의 응용 계층에서 전송 계층의 기능을 사용할 수 있도록 제공하는 응용 프로그램의 인터페이스(API. Application Programming Interface)이다. 즉, 이러한 소켓 인터페이스는 응용 프로그램과 TCP 계층을 연결하는 역할을 한다.
소켓 인터페이스 기반 네트워크 응용 프로그램
그렇다면 클라이언트와 서버가 요청을 송수신하고 요청을 어떻게 처리하는지 다음 그림을 통해 알아보자.
- Start server : 서버 위의 프로그램이 요청을 받아 수행하기 위해 서버가 setup되는 과정이다.
- Start Client
- Exchange Data : 클라이언트와 서버 세션에서 서로 데이터를 교환한다. 이때, Reliable I/O를 사용하여 읽고, 쓰는 작업을 수행한다.
- Disconnect Client
- Drop Client : 서버가 클라이언트를 drop하고, 새 클라이언트 요청을 받는다.
socket()
클라이언트와 서버가 소켓. 즉, 소켓 판별자를 만들기 위해서 사용하는 함수이다.
int socket(int domain, int type, int protocol);
- int domain : AF_INET. 즉, 32비트 IPv4 주소를 사용한다는 것을 알려준다.
- int type : SOCK_STREAM. 우리가 만들 소켓의 타입이 TCP protocol을 사용하는 통신의 소켓임을 알려준다.
- 리턴값으로 파일을 대신할 수 있는 식별자(작은 정수)를 리턴하거나, 실패하면 -1을 반환한다.
bind()
서버의 소켓 주소와 프로세스가 만든 소켓을 서로 묶는 역할을 하는 서버 함수이다. 그리고 커널이 이 역할을 담당한다. 즉, 응용 프로그램이 socket()을 이용해서 소켓을 만들면, 통신에서 이 소켓을 사용하기 위해 소켓의 네트워크 시스템(TCP/IP) 주소를 그 소켓에 붙여주어야 응용 프로세스와 네트워크 시스템 간의 패킷 전달이 가능하기 때문에 이 함수가 필요하다.
int bind(int sockfd, sockaddr *addr, socklen_t addrlen);
- int sockfd : 서버의 응용 프로그램이 만든 소켓의 번호. 즉, 식별자이다.
- sockaddr *addr : 서버의 소켓 주소. 즉, 소켓 연결의 끝 주소이다.
- socklen_t addrlen : 주소의 길이로 IPv4에서는 4바이트이다.
- 리턴값은 성공했으면 0을 반환하고, 실패하면 -1을 반환한다.
그렇다면 클라이언트는 왜 bind()가 필요없을까? 클라이언트는 포트 번호를 임의로 사용해도 된다. 즉, IP 주소나 포트 번호를 다른 클라이언트 또는 서버가 미리 알고있을 필요가 없다는 뜻이다. 하지만 서버의 응용 프로그램은 자신이 사용하고 있는 포트 번호를 통해서 클라이언트의 서비스를 처리해야 하므로 응용 프로그램이 소켓 번호와 소켓 주소를 bind()하는 작업이 필수인 것이다.
listen()
이 함수는 해당 소켓이 서버 소켓임을 알려주는 서버 함수이다. 즉, socket()이 만든 소켓은 기본적으로 클라이언트 단의 능동적인 소켓이고, 연결을 요청하는 역할을 한다. 반면에 서버 소켓은 클라이언트의 연결 요청을 기다리는 입장이므로 이렇게 수동적 소켓임을 알려주기 위해 사용한다.
int listen(int sockfd, int backlog);
- int sockfd : 서버의 응용 프로그램이 만든 소켓의 번호. 즉, 식별자이다.
- int backlog : 커널이 요청을 거절하기 전에 큐에 저장해야 하는 연결의 수이다. 1024가 default value이다.
- 리턴값은 성공했으면 0을 반환하고, 실패하면 -1을 반환한다.
accept()
서버가 연결 준비가 되었다는 것을 알려주고 연결 request를 받는 서버 함수이다.
int accept(int listenfd, SA *addr, int *addrlen);
- int listenfd : 클라이언트의 연결 요청을 받는 서버 소켓의 번호이다.
- (sockaddr *) addr : 클라이언트에게서 요청을 받고 나면 알게되는 클라이언트의 소켓 주소이다.
- int *addrlen : 주소의 길이다.
- 리턴값으로 성공하면 connected descriptor를 반환하는데, 이를 통해서 Unix I/O 함수들을 사용하여 클라이언트와 통신할 수 있다. 반면에 실패하면 -1을 반환한다.
connect()
서버에 연결 요청을 보내 서버와의 연결을 수립하는 클라이언트 함수이다.
int connect(int clientfd, SA *addr, socklen_t addrlen);
- int clientfd : 클라이언트의 소켓 번호이다.
- (sockaddr *) addr : 서버의 소켓 주소이다.
- socklen_t addrlent : 주소의 길이다.
- 리턴값으로 성공하면 0을 반환하고, 실패하면 -1을 반환한다.
이 함수의 결과로 성공하면 clientfd 파일을 읽고, 쓸 수 있게 된다. 그래서 성공한 연결은 (클라이언트 주소 : 클라이언트 호스트 내의 해당 프로세스를 식별하는 유일한 포트 주소, 서버 주소: 서버 포트 주소)와 같이 소켓 쌍으로 표현할 수 있게 된다.
듣기와 연결 식별자 (listenfd, connfd)
listenfd는 오로지 다수의 클라이언트로부터의 연결 요청만 받는 소켓이다. 그리고 connfd는 각각의 클라이언트들과 해당 서버의 포트를 이어주어 데이터를 주고받게 해준다. 즉, listenfd가 클라이언트의 connect()로부터 연결 요청을 받으면 서버가 accept()를 통해서 클라이언트와 connfd를 이어준다. 특징을 조금 더 정리하면 다음과 같다.
- listenfd는 서버가 종료될 때까지 유일하게 계속 살아있는다.
- 같은 서버 포트에 여러 개의 connfd가 있을 수 있다.
- connfd가 여러 개 존재함으로써 서버 하나가 여러 개의 클라이언트들과의 연결을 동시에 진행할 수 있다.
'컴퓨터 사이언스 > Network' 카테고리의 다른 글
echo 클라이언트와 서버 (0) | 2023.11.19 |
---|---|
소켓 인터페이스를 위해 도움을 주는 함수들 (0) | 2023.11.19 |
Client Server Architecture (0) | 2023.11.17 |
스위칭 허브와 공유기의 차이 + ipTIME 사용 팁 (0) | 2023.11.17 |
Hub, Switch, Router의 차이 (0) | 2023.11.17 |