[LINUX] TINY webserver 만들기 (1) - socket interface

piopiop·2021년 1월 24일
2

Linux

목록 보기
2/6

1. 클라이언트와 서버의 연결

클라이언트와 서버는 연결(connection)을 통해서 통신이 가능해진다.

소켓(socket)연결의 종단점이자 네트워크 상의 다른 프로세스와 통신하기 위해 사용되는 파일이다. 여기서는 일단 연결의 종단점 역할을 한다는 것만 기억해두자.
(소켓을 우편함이라 생각하면 이해하기 조금 쉬울 것이다.)

연결은 연결의 종단점인 두 소켓의 주소에 의해 유일하게 식별된다. 각 소켓은 인터넷 주소와 16비트의 포트 주소로 이루어진 소켓 주소를 갖는다.
연결이 성공하면 이제 그 연결은 아래와 같은 소켓 쌍으로 규정되고 튜플(tuple)로 나타낸다.
(client address : client port, server address : server port)

2. 소켓 인터페이스(Socket interface)

소켓 인터페이스네트워크 응용(Network application)을 만들기 위한 함수들의 집합이다.
아래의 그림은 전형적인 클라이언트-서버 통신에 사용되는 소켓 인터페이스의 전체적인 모습을 보여준다.

3. 소켓 주소 구조체

(1) sockaddr_in
ipv4를 위한 소켓 주소 구조체
ipv4에서 sin_family는 항상 AF_INET이 들어가기에 실제로는 필요가 없는 멤버이다.
과거 소켓 개발과정에서 전문가들이 하나의 프로토콜 체계 안에서 여러 주소 체계가 사용될 수도 있을 거라 예상해 sin_family를 넣었다고 한다.

다른 구조체 멤버로는 포트 정보, ip주소 그리고 아래서 설명할 sockaddr와 크기를 맞춰주기 위한 8byte의 padding이 들어간다.

(2)sockaddr
이어서 설명할 bind나 connect 등의 함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터를 필요로 한다. 그래서 여러 종류 주소 정보 구조체의 포인터를 받기 위해서 일반적인 소켓 주소 구조체를 만들었는데 이것이 바로 sockaddr이다.
void 포인터를 사용할 수도 있었지만 소켓 인터페이스가 만들어질 당시에는 void 포인터가 존재하지 않았다고 한다.

struct sockaddr_in {
    uint16_t  sin_family; // 주소체계(ipv4는 AF_INET)
    uint16_t  sin_port;	//포트 정보
    struct    in_addr sin_adr; // ip주소
    unsigned  char sin_zero[8]; // sockaddr 구조체와 크기를 맞추기 위한 패딩
};

struct sockaddr {
    uint16_t  sa_family;
    char      sa_data[14]; //주소 정보(ip + 포트)
};

4. 소켓 인터페이스 내 함수들

(1) socket

int socket(int domain, int type, int protocol)

	성공시 3 이상의 파일 식별자, 실패시 -1 반환

클라이언트와 서버가 소켓 식별자를 생성하기 위해 사용하는 함수이다.
함수 실행에 성공하면 소켓을 식별할 수 있는 3 이상의 정수를 반환한다.
(파일 식별자의 0, 1, 2는 표준 입,출력과 표준에러에 할당된다.)

두번째 인자인 type에는 여러가지가 들어올 수 있는데 TCP를 사용하고자 한다면 SOCK_STREAM을 인자로 넣어주면 된다.
스트림 소켓은 전화 같이 믿을 수 있는 양방향 통신을 제공하는 소켓이고 TCP를 사용한다.
tiny webser를 만들 때는 스트림 소켓만을 사용할 것이다.

(2) connect

int connect(int clientfd, const struct sockaddr *addr, socklent_t addrlen)

	성공시 0 반환, 실패시 -1 반환

connect 함수는 클라이언트에서 서버 측에 연결을 시도할 때 사용하는 함수이다.
connect 함수는 소켓 주소인 addr와 연결을 시도하고 성공한다면 위에서 말했던 것처럼 연결은 소켓 쌍으로 규정된다.
비로소 서버의 소켓과 연결이 된 것이다.
성공했으면 이제 클라이언트 측에서 만들었던 소켓인 clientfd는 읽거나 쓸 준비가 되었다.

(3) bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
	
    성공시 0 반환, 실패시 -1 반환

bind 함수는 서버에서 만들었던 소켓 식별자인 sockfd에 서버의 소켓 주소를 연결한다.
우편함에 주소가 붙었다고 생각하면 이해하기 쉬울 것이다.

(4) listen 함수

int listen(int sockfd, int backlog)
	
    성공시 0, 실패시 -1 반환

listen 함수는 서버와 bind된 소켓을 듣기 소켓으로 변환하며, 듣기 소켓은 클라이언트로부터의 연결 요청을 승락할 수 있다. backlog 인자는 연결 요청들을 거절하기 전에 저장해놓아야 하는 연결의 수에 대한 정보를 제공한다고 한다.
listen 함수가 실행에 성공하면 bind 되었던 sockfd는 듣기 소켓인 listenfd로 변환된다.

(5) accept 함수

int accept(int listenfd, struct sockaddr *addr, int *addrlen)
	
    성공 시 3 이상의 식별자, 실패시 -1 반환

두 번째 인자와 세 번째 인자로는 연결 될 클라이언트의 주소, 주소의 길이를 담을 공간을 받는다.
accept 함수는 listenfd에 연결 요청이 들어오기만을 기다리고 있다가 연결 요청이 들어오면 요청이 들어온 클라이언트와 연결을 시도한다.
성공한다면 clientfd와 연결된 연결 식별자 connfd를 반환한다.

이때 listenfd와 connfd가 혼동될 수 있다.
listenfd클라이언트의 연결 요청에 반응하는 역할을 한다. 이것은 보통 한 번 생성되며 서버가 살아있는 동안 계속 존재한다.
connfd는 클라이언트와 서버 사이에 성립된 연결의 끝점이다. 즉 새로운 클라이언트의 연결 요청을 수락할 때마다 새로운 connfd가 생성되는 것이다. 이 식별자는 서버가 클라이언트에 서비스하는 동안에만 존재한다.


많이 부족합니다. 피드백 환영합니다!
-끝-

profile
piopiop1178@gmail.com

0개의 댓글