UDP 패킷 자체는 메시지의 전달을 보장하지 못합니다.
그 의미는 보내는 쪽에서 아무리 많이 보내도 받는 쪽에서 제대로 처리하지
못하면 패킷 자체가 유실된다는 것을 내포합니다.
해당 소스를 아래와 같이 위치 시킵니다.
./Makefile
./inc/fuq.h
./src/fuq.c
./src/udp_recv.c
./src/udp_recv_file.c
./src/udp_send.c
위와 같이 놓고,
~ 에서
$ make clean && make
하면,
$ ll out
합계 88
drwxr-xr-x 1 future staff 238 9월 8 15:28 ./
drwxr-xr-x 1 future staff 238 9월 8 14:09 ../
-rw-r--r-- 1 future staff 11152 9월 8 15:28 fuq.o
-rw-r--r-- 1 future staff 11372 9월 8 15:28 libfuq.a
-rwxr-xr-x 1 future staff 21451 9월 8 15:28 udp_recv*
-rwxr-xr-x 1 future staff 17226 9월 8 15:28 udp_recv_file*
-rwxr-xr-x 1 future staff 17572 9월 8 15:28 udp_send*
와 같이 udp_send, udp_recv_file, udp_recv 라는 세 개의 실행파일이 나옵니다.
자~~ 이제 부터 테스트를 해 봅시다.
우선 out/udp_send 프로그램은 1500바이트의 payload를 담고 UDP로 무조건 보냅니다.
대신 각 UDP를 보내면서 usleep 만큼 sleep 을 주어 초당 보내는 시간을 지연시켜 봅니다.
그 다음 out/udp_recv_file 은 하나의 쓰레드 안에서 recv_from으로 UDP 패킷을 받아
stdout 으로 출력을 하는 것입니다.
우선
$ out/udp_recv_file 10240 > out.txt
라고 하여 10240 UDP 포트를 보고 있으면서 패킷을 받으면 결과를 out.txt 로 redirect 시킵니다.
(매번 보내기 전에 위와 같이 다른 터미널에서 받는 것을 다시 실행시킵니다. out.txt 를 초기화하기 위함입니다)
그리고,
$ time out/udp_send 127.0.0.1 10240 100000 100 && wc -l out.txt
real 0m21.872s
user 0m0.243s
sys 0m1.529s
94018 out.txt
udp_send 프로그램을 수행하는데 127.0.0.1 10240 포트로 UDP 패킷을 100,000개 보내는데
패킷을 하나씩 보내면서 100마이크로 초 만큼 sleep을 합니다.
100,000개를 보냈는데 결과는 94,018 이라 UDP 패킷 자체가 유실되었음을 알 수 있습니다.
초당 약 4570여개를 채 처리 하지 못한다는 결과 이네요.
$ time out/udp_send 127.0.0.1 10240 100000 200 && wc -l out.txt
real 0m41.057s
user 0m0.655s
sys 0m4.061s
98742 out.txt
이제는 패킷을 보내는 사이에 200 마이크로 초 (0.2밀리초) 만큼 차이를 두고,
초당 약 2460여개를 보냈는데도 유실이 있습니다.
$ time out/udp_send 127.0.0.1 10240 100000 260 && wc -l out.txt
real 0m54.828s
user 0m1.018s
sys 0m6.434s
100000 out.txt
결국 초당 1820여개의 패킷을 처리하니 100,000 개의 패킷이 유실없이 모두
파일에 저장을 했습니다.
이것은 결국 파일에 저장하는 속도를 충분히 주지 못하면 UDP 패킷이 유실 됨을 나타냅니다.
(주의: 이 한계는 시스템의 패러미터 설정 혹은 저장 IO 속도 등에 따라 다를 수 있습니다)
그럼 이제는 udp_recv 라는 실행파일을 만들었는데,
쓰레드 안전한 메모리 큐를 만들어 UDP 패킷을 받자마자 큐에 넣고 (생산)
다른 쓰레드는 큐에서 소비하면서 stdout 으로 출력하도록 하였습니다.
우선 다음과 같이 패킷을 기다리고 있습니다.
$ out/udp_recv 10240 10000 > out.txt
Info: FutureUdpQueue (size=10000) initailized!
...
(매번 보내기 전에 위와 같이 다른 터미널에서 받는 것을 다시 실행시킵니다. out.txt 를 초기화하기 위함입니다)
10240 포트로 들어오는 UDP 패킷을 살펴보는데 10,000 개의 UDP 패킷을 담고 있을 메모리 큐를
가지고 있는 것입니다.
처음 처럼 보내보는데,
$ time out/udp_send 127.0.0.1 10240 100000 10 && wc -l out.txt
real 0m9.122s
user 0m0.219s
sys 0m0.968s
100000 out.txt
앗! 이제는 초당 10,900 여개의 UDP 패킷도 유실없이 처리를 하는 군요.
$ time out/udp_send 127.0.0.1 10240 100000 1 && wc -l out.txt
real 0m8.184s
user 0m0.173s
sys 0m0.797s
100000 out.txt
위와 같이 최소 단위로 1 마이크로 초를 sleep 해도 12,200 여개 처리를 합니다.
하지만...
$ time out/udp_send 127.0.0.1 10240 100000 0 && wc -l out.txt
real 0m0.131s
user 0m0.019s
sys 0m0.100s
4656 out.txt
앗... 무작정 0.1초만에 10만개를 밀어 넣으면 엄청난 유실이 있습니다.
결국 초당 10,000 개 정도 들어온다가 가정해야 되겠네요...
이제는 큐의 사이즈가 얼마만큼되어야 깨지지 않을까 하는 것입니다.
$ out/udp_recv 10240 100 > out.txt
Info: FutureUdpQueue (size=100) initailized!
...
100개의 큐 사이즈를 가지고 있는 상태에서,
위에 10000개 일때는 성공했던,
$ time out/udp_send 127.0.0.1 10240 100000 1 && wc -l out.txt
real 0m8.676s
user 0m0.177s
sys 0m0.994s
80900 out.txt
유실이 되고 있군요....
FutureUdpQueue Full with QLen(100)
FutureUdpQueue Full with QLen(100)
위와 같은 메시지가 udp_recv 프로그램에서 출력하는 것으로 보아, 큐의 크기가 부족한 것이네요.
유사하게 큐의 크기를 계속 변화시키면서 돌려보았더니
7000개의 큐 크기에서는 유실이 발생하고, 8000에서는 유실이 없었습니다.
결국 결론은 다음과 같습니다.
UDP를 받아들이는 입장에서는 들어오는 UDP를 memcpy 로 큐에 밀어넣는데 발생하는 비용이 소요되고
이 큐에 있는 것을 다른 쓰레드에서 소비하는데 제대로 소비하지 못하면 큐도 밀려 UDP 패킷 유실이
발생하는 것이네요.
이것은 최소한의 소비 패턴과 패킷의 들어오는 발생 빈도를 바탕으로 잘 튜닝을 해야하는 문제입니다.
마치 카메라에 셔터 스피드와 렌즈 조리개의 두 가지 조절 팩터로 수많은 다른 결과를
나타내듯이 그런 패러미터 조정을 필요로 하는 것이 이쪽 튜닝의 세계라 이해되었습니다.
주의: 각 시스템에 따라 큐의 크기와 초당 처리 UDP 패킷 개수는 달라질 수 있습니다.
어느 분께는 도움이 되셨기를...
덧글|신고