serv_addr 구조체에 값을 채워넣음으로써 socket의 ip address와 port number를 지정해준 후에 발생하는 bind -> listen -> accept 과정을 이번 포스팅에서 살펴보고자 한다.
소켓에 주소를 할당하는 bind 함수
bind 함수는 2가지 인자를 전달함으로써 소켓에 주소를 할당할 수 있다. 즉, 앞서 socket()함수로 받아온 descriptor sockfd가 존재하는데, 이 descriptor file에 해당하는 소켓에 serv_addr 주소를 할당하겠다는 의미이다.
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);
- sockfd : socket() 함수를 통해 배정받은 디스크립터 번호. serv_sock
- *addr : IP address와 Port number를 지정한 serv_addr 구조체. serv_addr은 sockaddr_in이므로 sockaddr 구조체로 변환해준다.
- addrlen : 주소 정보를 담은 변수의 길이
이러한 bind() 함수는 성공시에는 0을 반환하고, 실패시에는 -1을 반환한다.
연결요청을 대기하는 listen 함수
bind를 통해서 하나의 소켓에 ip주소와 port number까지 할당했으므로, 이제 클라이언트가 해당 소켓에 연결할 수 있도록 그 요청을 대기하는 상태로 만들어주어야 한다. 이 과정을 담당하는 함수가 listen 함수이다. 따라서 listen 함수가 호출된 후 부터 클라이언트에서 connect(연결을 요청하는 함수)를 호출할 수 있게 된다.
int listen(int sock, int backlog);
- sock : socket descriptor number
- backlog : 연결 요청을 대기하는 큐의 크기
이렇게 지정한 descriptor의 socket이 listening socket이 되며, backlog 만큼의 Queue 공간을 갖는다. 여기서 연결요청을 대기하는 Queue에 대해 조금 더 자세히 알아보자. 우선 연결요청을 대기한다는 것은 클라이언트가 연결을 요청했을 때, 그 요청을 대기시킬 수 있음을 의미한다. 그리고 Queue는 쉽게 말해서 대기실이라고 이해할 수 있는데, 시스템에서 순서대로(tcp인 경우에) 클라이언트를 연결시킬 수 있도록 Queue에 모아놓는 것이다.
예를 들어, 다음과 같이 Queue의 크기를 5로 설정하면 이는 5개까지의 클라이언트 연결 요청을 대기시킬 수 있음을 의미한다.
if (listen(serv_sock, 5) == -1) {...}
연결 요청을 수락하는 accept 함수
accept 함수는 연결 요청을 수락하는 함수이다. 즉, 대기중인 클라이언트의 요청을 차례로 수락함으로써 데이터를 주고 받을 수 있게 해주는 것이다. 여기서 주의해야 할 점은 accept의 반환값은 성공 또는 실패에 대한 정수값이 아닌, 새로운 descriptor 번호라는 것이다. 즉, 앞서 이용했던 server socket(listening socket)은 연결 요청을 대기시키는 과정까지를 담당하며, accept() 함수를 통해서 새롭게 할당받은 소켓을 이용해서 데이터 송수신을 할 수 있게 하는 것이다.
int accept(int sock, struct sockaddr*addr, socklen_t *addrlen);
- sock : server socket(listening socket)의 descriptor 번호
- addr : 대기 Queue를 참조해서 얻은 클라이언트의 주소 정보
- addrlen : addr 변수의 크기
그리고 client_socket에 새로운 socket descriptor를 반환할 때는 대기 Queue에서 첫번째로 대기중인 연결 요청을 참조한다. 이때, 대기 Queue가 비어있는 상황이라면 새로운 요청이 올 때까지 accept 값은 반환되지 않고, 대기(blocking)한다.
데이터 송수신 및 연결 해제
앞선 순서대로 bind(), listend(), accept() 세가지 함수를 순차적으로 호출함으로써 서버측에서의 데이터 송수신 준비를 마칠 수 있다. 그리고 최종적으론느 write() 함수를 통해서 실제 데이터를 출력할 수 있으며(데이터 수신도 함께 한다면 read()함수를 이용한다), 데이터 송수신을 완료한 후에는 이용한 소켓을 완전히 소멸시킬 때는 close() 시스템 콜을 호출해준다. 그러면 커널이 해당 소켓의 자원을 모두 시스템에서 제거한다.
write(clnt_sock, message, sizeof(message));
close(clnt_sock);
close(serv_sock);
인용
'컴퓨터 사이언스 > Network' 카테고리의 다른 글
클라이언트의 시스템 콜(connect) (1) | 2023.11.21 |
---|---|
stream socket vs datagram socket (0) | 2023.11.21 |
HTTP version별 특징 (0) | 2023.11.21 |
proxy 서버 만들기 (0) | 2023.11.21 |
tiny 서버 만들기 (0) | 2023.11.20 |