소켓에서 발생하는 이벤트 - 발생 감지의 기준 level trigger와 egde trigger(1/2)
TCP로 연결된 두 지점은 소켓을 통하여 데이터를 전송하고 수신합니다. 소켓의 내부를 조금 더 들여다보면 데이터 전송을 위한 임시 공간이 존재하는데, Read Buffer, Write Buffer 입니다.
(1) Read Buffer: 원격으로부터 전송받은 데이터를 임시로 저장하는 공간입니다. 데이터를 수신하게 되면 임시로 이 공간에 저장합니다. 이 공간은 커널영역의 메모리입니다. 어플리케이션에서 소켓으로부터 읽기를 시도하면 이 버퍼로부터 데이터를 가져오는 동작을 수행합니다. (즉, 커널영역의 메모리 공간에서 유저모드의 메모리 공간으로 복사가 발생함)
(2) Write Buffer: 원격으로 데이터를 전송하기 전에 임시로 이 버퍼에 저장합니다. (이 공간도 커널영역의 메모리 입니다.) 어플리케이션에서 데이터 전송(send)를 수행하면 실제로는 이 버퍼에 데이터 쓰기 작업을 진행합니다. 네트워크 스택은 이 버퍼의 데이터를 전송함으로써 실제로 데이터 전달이 수행되는 것입니다.
소켓에서 발생하는 이벤트를 정의하는 것은 이 두 버퍼의 상태 변화를 의미하는 것입니다. 예를 들어, 어플리케이션에서 데이터를 수신(Read) 동작을 수행하기 위해서는 Read Buffer에 수신된 데이터가 존재한다는 것을 의미합니다. 어플리케이션에서 데이터의 전송(send)이 가능하다는 것은 Write Buffer에 여유공간이 있어서 데이터 쓰기 작업이 가능하다는 것을 의미합니다.
즉, 읽기 가능(Readable) 한가 또는 쓰기 가능(Writable)한가를 판단하는데 있어서 무엇을 기준으로 판단할 것인지 정하는 것인데, 단순히 버퍼의 데이터의 존재 유무가 판단 기준뿐만 아니라, 버퍼 공간의 변경 시점도 연관이 있습니다. 이 두가지지 기준이 level trigger, egde trigger 입니다.
두 트리커의 차이점은 소켓버퍼에 데이터가 있는 경우에 그것을 소켓 이벤트로 간주하는 기준입니다. 참고로 이벤트 감지에 사용되는 구현이 각 OS에 있습니다.
MS 윈도우: IOCP
리눅스: epoll(구 버전 select, poll)
BSD Unix(FreeBSD): kqueue
레벨트리거(Level Trigger): 소켓버퍼에 데이터가 들어있으면 이벤트가 발생하는 트리거입니다. 즉, 소켓에서 read 할 수 있는 데이터가 1바이트 이상 있을때 이벤트가 발생했다고 리턴해줍니다. 여기서 1바이트라고 했지만 그 기준을 소켓옵션설정을 통해 조정할 수 있습니다. 이것을 조정하면 10바이트 이상일 경우에만 이벤트가 발생하는 것으로 기준을 설정할 수도 있습니다. (select(), poll() 등이 레벨트리거에 속함)
에지트리거(Edge Trigger): 소켓버퍼의 데이터가 들어오는 시점 자체가 트리거의 기준입니다. 소켓버퍼가 비어 있다가 상대방이 전송한 데이터가 수신 버퍼에 들어오면(또는 비어있지 않더라도 추가로 수신하면) 이때 이벤트가 발생한 것으로 트리거 됩니다. 소켓에서 수신한 데이터를 어플리케이션에서 read 했는지 여부는 무관합니다. 즉, 에지트리거에서 이벤트가 발생했고(데이터가 들어왔고), 어플리케인션에서 read 하지 않더라도 그 이후에 다른 데이터가 추가로 들어오면 에지트리거는 다시 이벤트가 발생했다고 리턴합니다. (epoll(), kqueue() 등이 에지트리거에 속함. 이것들은 설정에 따라 레벨트리거로도 사용할 수 있음.)
일반적으로.... 에지트리거가 대량의 접속시에 더 나은 응답속도((이벤트 트리거 감지가 빠름)를 제공합니다. 다만, 에지의 관리의 어려움으로 인해 레벨 트리거가 더 많이 사용되는 것 같습니다. 성능이 우수하다고 알려진 Redis도 레벨트리거를 사용하고, 레벨트리거를 사용함에도 불구하고 대부분의 요건에 만족할 만한 성능을 보여준다고 봐도 되겠네요.
이해를 돕기 위해서 그림하나 추가합니다. 아래 그림은 소켓 버퍼의 상태와 edge와 level를 표현합니다.
에지트리거를 다룰 때 주의점:
에지트리거를 통해 이벤트 받았고 read 작업을 해야하는데, 이 시점에 read 할 수 있는 모든 데이터를 read 해야합니다(즉, Read Buffer의 데이터를 모두 가져와야함). 왜냐하면, 에지트리거는 데이터 유입에 대한 이벤트이므로 추가로 데이터 유입이 없으면 이벤트가 오지 않으므로 이번 이벤트를 통해 read를 할 때 다음에 다시 이벤트가 온다는 보장이 없으므로 이 시점에 읽을 수 있는 모든 데이터를 read 해야만 정상적인 처리가 됩니다. 어플리케이션의 read 버퍼가 작아서 꽉찬 상태로 리턴되면 이것을 다른 버퍼에 저장해 두고, 나머지를 다시 소켓으로부터 read 해야한다. 더 읽을 것이 없을때까지 계속 read 해야 한다. 이번에 read를 모두 하지 않으면 영원히 다시 read할 이벤트가 오지 않을 수 있기 때문입니다. 이런 요소가 어플리케이션에서의 구현을 복잡하게 만드는 요소입니다.

