학교를 다니며 네트워크 수업에서 TCP와 3 way handshake에 대해서 배웠었다. 하지만 실질적으로 어떻게 활용되는지에 대해선 미쳐 생각해보지 못했었다. 이번에 회사에서 관련된 작업을 진행하면서 알게된 내용과 해결한 의문점들을 정리하게 되었다.
회사에서 마주했던 상황은 아래와 같았다.
TCP 커넥션을 제한하는 기능을 개발하고자 하였다. 테스트 코드로써, 커넥션을 제한했을 때 새로운 클라이언트가 커넥션을 맺지 못하는 상황을 만들고자 하였다.
코드를 아무리 짜봐도, accept()
과정을 막았는데도 커넥션을 맺는데에는 에러가 발생하지 않았고 심지어 데이터 전송과정인 write()
과정에서도 오류없이 잘 이루어졌다.
따라서
왜 Accept 과정을 안거쳤는데도 커넥션이 맺어지고 데이터 전송에 에러가 없을까?
가 궁금해졌다.
이를 해결하기 위해서 TCP 커넥션에 대한 프로세스를 살펴보았다.
TCP에서 연결을 맺는 절차는 아래와 같다.
우선 서버 측에서 요청을 받기 위한 사전 작업이 필요하다.
bind()
: 포트와 IP 주소를 준비하는 과정이다.음식점으로 비유하자면, 상점을 짓는 과정이다.
listen()
: 준비되었음을 안내하는 과정이다.음식점으로 비유하자면, OPEN 표지판을 여는 과정이다.
이후 클라이언트 측에서 connect()
과정을 진행한다. 이 때, 우리가 모두 배웠던 3-way handshake가 일어난다. 사실 Application단에서 코드를 작성할 때에는 이 과정이 그리 중요하진 않았었다.
이렇게 맺은 커넥션은 SYN-queue를 거쳐 Accept-queue에 저장된다. (이미지를 보면 알겠지만, SYN까지 완료했는지 ESTABLISHED까지 완료했는지의 차이이다.) 음식점으로 비유하자면, 자리에 손님이 앉는 과정이다.
따라서 첫 번째 의문점이었던 accept()
과정이 없더라도 커넥션 자체는 맺을 수 있었다. Accept Queue에 저장되었다.
하지만 두 번째 의문점은 해소되지 않았다.
이를 해소하기 위해선 netstat 명령어가 필요했다.
netstat를 검색하면 위와같이 Recv-Q, Send-Q를 확인할 수 있다. 이는 각각
다시 말해, application에서 accept()
과정을 거치지 않더라도 Recv-Q에 데이터는 저장되므로 클라이언트에서 데이터를 전송하는 과정도 에러없이 이루어졌던 것이다.
실제로 테스트코드에서도 accpet하지 않은 데이터들은 Recv-Q에 저장된 것을 확인했다.
추가로 Recv-Q가 가득차게 되면, 클라이언트에서 전송하는 데이터의 크기를 줄이는 방식으로 대응한다. (자세한 내용은 TCP 윈도우 크기를 찾아보길..)
이후 서버 측에서는 두 가지 과정을 거친다.
accept()
: application까지 데이터를 보내는 것을 허용하는 과정이다.음식점으로 비유하자면, 주문한 메뉴를 조리(처리) 시작(허용)하는 과정이다.
read()
: 요청에 대한 처리를 시작하는 과정이다.음식점으로 비유하자면, 조리-음식이 나가는 과정이다.
사실 커넥션 제한
이라는 단어가 굉장히 범용적으로 쓰이다보니 커넥션을 application단에서 막을 수 있구나 했는데 표현에 오류가 있었다는 걸 알게되었다.
서버에서 listen()
을 해버리면 커넥션을 맺어지는 거고 이를 application단에서 막을 순 없었다. (서버단에서 설정 파일을 변경하면 바꿀 순 있었다. 참고)
application단에서는 accept하는 커넥션을 제한하는 과정이 가능한 전부였다. 실제로 범용적인 app인 톰캣에서도 아래 이미지와 같이
음식점으로 비유하자면, 대기줄의 길이
음식점으로 비유하자면, 앉을 수 있는 자리의 수
음식점으로 비유하자면, 쓸 수 있는 그릇의 수
TCP에 대해 몰랐기에 삽질을 했었다. 수업을 잘 들어놓을 껄 하고 후회되기도 하지만 하지만 이렇게 실전을 겪어봐야 잘 이해가 되는 것 같다 ㅎㅎ..