앞서 소켓이 무엇인지 알아봤고, 이제 소켓으로 실제 통신이 어떻게 구현되는지 알아보자.
클라이언트의 동작에 대한 함수를 알아보고 난 뒤, 서버에 대해 알아보겠다.
일단 먼저, 서버든 클라이언트든 통신에 앞서 socket을 생성해야 할 것이다.
TCP 통신 전체 그림을 보면서 socket 녀석이 어딨는지 보자.
소켓은 socket 함수를 이용하여 생성한다. 어떤 친구인지 한번 알아보자.
어떤 프로토콜로 통신할 것인지, 어떤 종류의 소켓을 사용할 것인지 미리 정해진 값들을 집어넣기만 하면 된다.
domain 부분에서 PF_INET 와 AF_INET 이부분이 좀 혼동할 수 있는데,
PF_INET는 프로토콜 체계(프로토콜 패밀리)이고
AF_INET는 주소 체계(주소 패밀리)이다.
connect()
소켓을 생성하였으니 연결을 해야 할 것이다. (클라이언트 측)
일단 지금은 서버가 뭐 소켓을 열었니 말았니 생각하지말고 클라이언트 관점만 집중하자.
소켓을 생성하고 나면 connect() 함수를 이용하여 TCP 클라이언트가 서버로 연결을 설정해야 할 것이다.
먼저 connect 함수가 어떻게 생겼는지 부터 확인하자.
위에서 말했다시피, connect는 서버로 연결하는 함수이다.
sockfd는 socket 함수를 이용하여 생성한 소켓의 파일 디스크립터를 가져오는 것이고,
*servaddr 와 addrlen 은 위에서 설명되어 있다 시피.
그리고 Return failure errono에 대해 얘기해야 하는데,
connect 함수가 시전하는 3 Ways Hand-shaking이 연관되어 있다.
1. ETIMEDOUT
클라이언트가 SYN에 대한 응답을 받지 못할 때 생기는 에러이다.
한번 안오면 타이머를 2배로 늘려서 다시 재전송한다. 그러다 일정 시간이 지나면 오류로 처리함.
( 몇배로 늘리는 지는 시스템마다 다르다. 4.4BSD는 6초 -> 24초 -> 75초 경과 Timeout)
2. ECONNREFUSED
서버에서 요청받을 소켓이 없는 경우 서버는 클라이언트로 RST 세그먼트를 전송하는데,
이 때 발생하는 오류이다.
3. EHOSTUNREACH, ENETUNREACH
EHOSTUNREACH 오류는 호스트가 속한 네트워크까지 도달했지만 호스트가 뒤져있는 경우
ENETUNREACH 오류는 호스트가 속한 네트워크까지 가지도 못한 경우에 생기는 오류이다.
둘다 ICMP의 Destination unreachable 메세지를 받고, 다시 SYN을 날렸지만 응답이 없는 경우 생기는 오류이다.
bind()
자 이제 다시 서버쪽으로 넘어와 보자. 서버에서도 마찬가지로 socket을 생성했을 터이니,
다음은 무슨 작업을 해주어야 할까. 일단 계속 첨부했던 소켓 통신 전체 과정을 다시 보자.
bind() 함수가 나온다. bind함수는 소켓에 지역 프로토콜 주소를 부여하는 함수이다.??
그냥 socket에 IP주소와 포트번호를 부여하는 거임.
이름이 없는 것에 이름을 지어주는게 bind이다.
socket을 생성할 때는 그냥 주소체계와 소켓 타입만 지정해 주었으니 이제 bind함수를 이용해
직접적으로 소켓을 사용할 수 있도록 해주는 거라고 보면됨. 어째 생겼는지 함보자.
바인딩할 sockfd (소켓), 주소, 주소 구조체 길이 등이 인자로 쓰인다.
bind함수를 이용할 때 IP주소와 포트에 대해 좀 생각해봐야할 부분이 있다.
먼저 서버는 시작할 때 Well-known port를 바인딩해야 한다.
그렇지 않으면 connect나 listen함수가 호출될 때 커널님께서 dynamic port로 할당해버린다.
서버가 포트번호를 지정해주지 않으면 커널에서 지맘대로 아무거나 택한단 말. (당연한 말이지)
주소 같은 경우를 보면, 서버가 IP주소를 와일드카드(*)로 바인딩 하는 경우,
커널은 소켓이 연결되거나(TCP), 데이터그램을 받기전(UDP)까지 로컬 IP를 할당하지 않는다.
IPv4 에서는 이렇게 와일드 카드를 INADDR_ANY 라는 상수로 명시한다.
대부분의 서버 구현에서 INADDR_ANY 로 IP를 바인딩한다.
표에 정리된걸 보자.
뭐 그냥 지정해주지 않으면 커널에서 할당한다고 말하고 싶은 것이다.
listen()
빈 깡통에 이름을 지어 줬으니 이제 클라이언트를 받아들일 상태로 변경시켜줘야 할 것이다.
클라이언트 접속 대기 상태로 만들어 주는 것을 말한다. 바로 listen() 함수가 그 일을 한다.
listen 함수는 크게 2가지 일을 한다.
1. socket() 함수로 소켓을 생성하는 것을 능동적 소켓이라고 하는데, 이것이 클라이언트 소켓이다.
(자발적으로 연결을 요청하는 것이므로 능동적 소켓이라고 함..)
listen함수는 close 상태에 있는 서버 소켓을 수동적 소켓으로 바꾼다.
2. listen 함수는 backlog queue 라는 대기가능한 최대 연결 갯수를 지정한다.
일단 함수 원형부터 보자.
sockfd 는 앞서 bind() 함수를 통해 바인딩 된 소켓의 소켓 디스크립터이다.
backlog 값은 0으로 하면 default 값이 설정된다.
빽로그 빽로그 하는데, 이놈이 무엇인가 좀 알아보자..
커널은 listen 소켓에 대해 2가지 큐(Queue)를 가지고 있다.
불완전 연결 큐(incomplete connection queue)와 완전 연결 큐(complete connection queue)
커널은 위 두가지 큐를 유지시킨다.
1. 불완전 연결 큐 (incomplete connection queue)
클라이언트가 3HSK를 맺기 위해서 보낸 SYN 패킷을 저장하는 큐이다.
이런 소켓을 SYN_RCVD 상태에 있다고 말한다.
2. 완전 연결 큐 (complete connection queue)
3HSK가 맺어진 상태에 있는 클라이언트의 항목을 저장하는 큐이다.
이런 소켓은 ESTABLISHED 상태에 있다고 말한다.
accept가 되면 완전 연결 큐에 있는 첫번째 엔트리의 소켓이 반환된다.
backlog는 위 두가지 큐의 합에 대한 최대값을 규정한다.
그리고 backlog가 가득 찼을 때 연결 요청이 들어오면 거절된다.
HTTP 웹서버 같이 요청이 많은 경우 backlog를 크게 키워줘야 할 것이다.
SYN flooding 공격이 이 backlog와 직접적인 관계가 있는데,
해결책으로 큐를 늘리거나 소켓이 불완전 큐에 남아있는 timeout을 줄이는 방법이 있다.
accept()
아까 backlog 큐를 설명하면서 accept 되면 완전한 대기 큐에서 소켓이 반환된다고 말했다.
그래 이걸 보면 accept() 함수가 무슨일 하는지 대충 감이 올 것이다.
accept() 함수는 위에서 말한대로 완전 대기열 큐에서 연결된 소켓을 끄집어 내는 것이다.
즉, accept함수가 클라이언트와 통신하는 connected 소켓을 만든다는 얘기가 됨.
accept 함수는 총 3가지 인자가 필요하다. 연결된 소켓, 클라이언트의 주소, 그리고 그것의 길이
'IT 그리고 정보보안 > Knowledge base' 카테고리의 다른 글
시그널 핸들링(SIGCHLD) (0) | 2021.04.11 |
---|---|
TCP echo Server/Client (1) | 2021.04.11 |
네트워크 소켓(Socket)은 무엇인가 (0) | 2021.04.11 |
UDP (User Datagram Protocol) (0) | 2021.04.11 |
TCP (Transmission Control Protocol) (0) | 2021.04.11 |