우선 제일 먼저 정의해야 할 부분은 패킷 사이즈의 가변 여부이다. 패킷 사이즈가 가변적이지 않은 경우에는 딱 정해진 사이즈만큼 소켓에서 읽어들여서 패킷을 처리하면 간단하지만, 가변적인 경우에는 지금 전송하는 패킷이 어디서 끝난다는 것을 반드시 알려줘야 한다. 그리고 패킷 내에서도 각각의 필드를 구별하는 방법이 필요하다.
가변적인 경우 보통 선두에 패킷의 사이즈가 나오거나 또는 패킷의 맨 끝에 구분자(보통 \r\n)를 넣고 각각의 필드는 공백이나 탭 문자를 이용해서 구별하는 경우가 일반적이다. 하지만 이 경우에도 좀 더 제약 사항을 둬야지만 프로그래밍이 편리해진다.
예를 들어 패킷의 끝에 \r\n 구분자를 사용하고 각 필드를 공백으로 구분하도록 한 프로토콜에서 클라이언트가 아래와 같이 두 개의 패킷을 한꺼번에 서버로 전송했다고 가정하자.
CMD data\r\nCMD another data\r\n
위와 같이 수신된 패킷을 읽어들일 때, \r\n을 찾으려고 1바이트 단위로 read를 하게 되면 엄청난 횟수의 시스템콜로 인해 성능 저하가 발생한다. 그렇다고 무턱대고 임의 사이즈로 read 하고 \r\n을 찾아서 패킷을 분리하게 되는 경우, 뒤에 연결된 다른 패킷이 같이 read 되기 때문에 처리하기가 상당히 복잡해진다. 만약 꼭 이렇게 처리를 해야되는 경우에는 시스템 소켓 버퍼 레이어 위에 프로그래머가 또 다른 버퍼 레이어를 만들어서 처리해야 한다.
따라서 이런 경우에는 한 번에 하나의 명령만 전송을 하고 서버로부터 응답을 받은 후에 다음 명령을 전송하도록 제약을 두는 것이 좋다. 그래서 만약 클라이언트가 서버의 응답을 기다리지 않고 한 번에 여러 개의 명령을 보낸다면 수신된 패킷에서 처음 발견되는 \r\n 뒤에 있는 모든 패킷을 그냥 버리면 된다.
클라이언트: CMD data\r\n
서버: OKAY\r\n
클라이언트: CMD another data\r\n
서버: OKAY\r\n
서버: OKAY\r\n
클라이언트: CMD another data\r\n
서버: OKAY\r\n
그리고 위와 같이 \r\n으로 패킷의 끝을 구분할 때 주의할 점이 하나 더 있다. 무작정 \r\n이 패킷에서 나올 때까지 소켓으로부터 read 해서 메모리에 저장하게 되면 악의적인 사용자가 매우 큰 사이즈의 패킷을 보내는 경우 취약점이 발생할 가능성이 있다. 따라서 최대 수신 사이즈를 지정해서 그 사이즈만큼 패킷을 읽어도 \r\n이 나오지 않는 경우 에러 처리를 하는 것이 좋다.
또한 명령어가 아닌 데이터를 전송하는 경우에는 전송할 데이터의 사이즈를 미리 서버에게 알려줘서 서버가 지정된 사이즈만큼 read 할 수 있도록 해야 한다.
클라이언트: CMD SEND 1048576\r\n
서버: OKAY\r\n
클라이언트: 1048576 바이트의 데이터 전송 (마지막 \r\n 없음)
서버: OKAY\r\n
서버: OKAY\r\n
클라이언트: 1048576 바이트의 데이터 전송 (마지막 \r\n 없음)
서버: OKAY\r\n
지금까지 설명한 내용은 패킷의 모든 내용이 아스키 캐릭터로 구성되어 있는 경우로서 구현이 쉽고 프로토콜 분석도 용이한 편이다. 그러나 빅 엔디안 형태의 raw data를 직접 주고 받는 경우에 비해 더 많은 공간이 필요하고 항상 가변적이라는 단점이 있다. 예를 들어 1048576 바이트를 표현할 때, 전자의 경우 총 7바이트(가변 길이)가 필요하지만 후자의 경우에는 4바이트 (고정 길이) integer면 충분하다.
빅 엔디안 형태의 raw data를 직접 전송하는 예로는, 패킷의 최초 4바이트가 big endian 32bit integer로서 전체 가변 패킷의 길이를 나타내도록 하고 그 뒤에 해당 사이즈만큼 패킷이 오는 방법이 있다. 이 경우에는 우선 소켓에서 4바이트를 읽어서 전체 패킷의 사이즈를 확인한 후 정확히 해당 사이즈만큼 소켓에서 읽어들이면 된다. 물론 이 경우에도 패킷의 사이즈가 가변적이기 때문에 공백이나 탭 문자를 사용해서 각각의 필드를 구별하는 방법은 역시 필요하다.
그리고 서버/클라이언트 모두 상대방의 응답을 기다릴 때, 소켓의 수신 타임아웃 옵션(setsockopt()의 SO_RCVTIMEO)을 사용해서 정해진 시간동안 응답이 없으면 에러로 처리할 수 있도록 해야 한다.
- 관련글
http://superkkt.com/229
http://superkkt.com/159

comments
comments rss (+댓글 쓰러가기)