소개
C 언어에서 소켓 프로그래밍 중 accept() 와 connect() 에 대한 글입니다. '윤성우의 열혈 TCP/IP 소켓 프로그래밍' 을 통해 학습한 내용을 바탕으로 정리한 내용이니, 더 궁금하다면 책을 읽어보시는 것도 추천드립니다.
소켓 동작 방식 (TCP)
기본적으로 서버와 클라이언트 입장에서의 소켓 단계들을 설명드리고 그 중에서 중요한 부분 중 하나인 accept, connect 에 대해서 알아보겠습니다.
서버 소켓 입장
서버에서는 다음과 같이 총 6단계 정도를 거칩니다.
- 소켓 생성 - socket()
- 소켓 바인딩 - bind()
- 리스닝 - listen()
- 수락 - accept()
- 데이터 송수신 - read()/write()
- 종료 - close()
1. 소켓 생성 - socket()
소켓을 활용해 송수신을 해야 하는 서버 입장에서는 최소 2개의 소켓을 요구합니다. 1번에서의 소켓 생성은 서버 소켓으로 일종의 '문지기' 역할을 합니다.
socket() 함수를 호출하게 될 때 내부적으로는 사용할 소켓을 만들고 이에 대한 file descriptor 을 반환합니다. 해당 file descriptor 가 서버 소켓이 되는 겁니다.
추가적으로 소켓 생성 시에는 소켓의 종류에 대해서 선택을 해야 합니다. IPv4 인지, TCP/UDP 인지 등.
2. 소켓 바인딩 - bind()
소켓을 무사히 생성했으면 이제 바인딩 작업이 필요합니다. 즉, 1번 단계에서 생성한 file descriptor 를 특정 주소에 묶어두는 것이죠. 여기에서는 쉽게 말해 127.0.0.1:5890 주소로 소켓을 연결하는 부분입니다.
이 때 바인딩에 들어가는 것 중 하나가 `SOCKADDR_IN` 의 struct 타입을 활용해 묶어줍니다.
3. 리스닝 - listen()
서버 소켓은 일종의 '문지기' 역할을 한다고 했는데 그 이유가 여기에서 나오게 됩니다. 서버 소켓과 연결하고자 하는 요청들이 오게 되면 무한히 받아줄 수는 없습니다. 그래서 일종의 대기줄을 생성하죠. 이런 대기줄에 대한 정보를 서버 소켓이 관리하게 됩니다. 그런데, 대기줄도 무한히 받아줄 수는 없어 제한을 둬야 하는데 listen() 함수를 통해 대기줄의 길이를 정하게 됩니다.
예를 들어 다음과 같습니다.
listen(server_socket, 10);
위 코드에서는 대기줄이 10개로 제한이 되어 있습니다. 다만, listen 함수는 대기줄의 길이를 정하고 OS 에서 소켓이 듣고 있다고 알려줄 뿐이지, 실제로 대기줄의 요청들을 받아드리는 것은 accept 입니다.
4. 수락 - accept()
수락 단계에 들어서야 이제 서버 소켓을 통해 관리되는 대기줄을 처리하게 됩니다. 일단 accept 에서는 2개의 작업 중 하나를 하게 됩니다.
- 대기줄에 있는 요청 수락
- 요청 기다리기
대기줄에 만약에 요청들이 존재한다면, 들어온 순서 기준으로 하나를 가져와 연결하게 됩니다. 혹은, 요청이 없고 nonblocking 으로 표시가 안 되어 있는 경우에는 새로운 요청이 올 때까지 기다립니다.
accept 를 통해서 연결이 생성이 된다면, 연결에 사용될 새로운 소켓을 하나 생성해줍니다. 기존에 만들어진 서버 소켓은 사용하지 않습니다. 문지기의 역할만 충실히 할 수 있도록 해줍니다. 이 때 만들어지는 새로운 소켓은 운영체제에서 알아서 만들어주기 때문에 특별한 이유가 없는 경우에는 신경쓰지 않아도 됩니다.
5. 데이터 송수신 - read() / write()
이제 요청과 소통할 소켓이 있으니 해당 소켓을 통해서 read() 작업이나 write() 작업을 진행합니다. 해당 read(), write() 작업들을 통해 바로 데이터가 송수신 되지는 않습니다. 예를 들어 write() 를 하게 되면 출력버퍼라는 곳에 저장이 됩니다. 그리고 클라이언트 측에서 read() 를 호출할 때 불러들입니다. 따라서, 서버 측에서 write() 를 한다고 클라이언트에 바로 전송이 되지는 않습니다. read() 도 마찬가지로 입력버퍼를 통해서 데이터를 받아오고 이를 읽어오는 구조입니다.
이러한 입력버퍼/출력버퍼 들은 소켓이 생성될 때 자동으로 같이 만들어지게 되고 사용하기만 하면 되는 구조입니다.
버퍼도 물론 무한히 사용할 수 없기에, 버퍼의 크기만큼 데이터가 송수신 될 수 있도록 TCP 연결은 조절합니다. 이를 위해 SYN, ACK 을 포함한 데이터를 전송합니다.
6. 종료 - close()
마지막으로는 소켓을 종료할 때입니다. 소켓이 종료될 때는 close() 함수를 호출하게 되고 이를 기반으로 accept 단계에서 생성한 소켓을 닫게 됩니다. 물론, 서버 소켓 또한 마찬가지로 close() 함수를 호출해서 닫으면 서버 소켓도 종료가 됩니다.
다만, 소켓으로 생성된 버퍼에 있는 데이터는 소켓 종료 이후에도 남은 데이터를 읽어올 수 있습니다. 정확하게는, 출력버퍼에 있는 데이터는 전송이 이뤄지고 입력버퍼에 있는 데이터는 소멸됩니다.
'Network' 카테고리의 다른 글
HTTP 1.0/1.1/2 (2) | 2023.10.26 |
---|---|
네트워크 기본 분석 명령어 모음 (0) | 2023.10.19 |
[C] sockaddr_in 와 sockaddr (2) | 2023.04.30 |
네트워크 구조 간단 정리 (0) | 2022.05.31 |