서버는 충분한 리소스가 없더라도 수천 개의 연결을 처리 할 수 있어야 한다.
서버가 연결을 더 많이 받아들일수록 점점더 리소스의 한계치에 도달하게 된다.
서버가 고려해야 되는 리소스의 종류가 locked page(잠겨진 메모리 페이지) 와 non-page pool(
페이징 되지않은 메모리풀) 두 가지 리소스가 있다.
locked pages의 한계는 non-paged pool 고갈보다 덜 심각하고 처리하기 쉽다.
1) locked pages
모든 overapped i/o 호출시에는 호출에 사용한 데이터 버퍼가 잠겨질(lock) 가능성이 있다.
메모리가 잠겨지면 물리적 메모리로 페이징되지 않는다.
OS는 잠겨질 수(locked)있는 메모리의 한계를 정해 놓고 있다. 이 한계에 도달하면
overapped i/o 호출은 WSAENOBUFS로 실패한다.
서버가 각 연결마다 많은 수의 overapped i/o를 호출하면 연결이 증가될수록 이 한계치에 도달할 확률
이 높아진다. 만일 서버가 동시에 아주 많은 수의 연결을 처리해야 한다면 각 연결별로 0 바이트 크기의
수신용 overapped i/o 를 한번씩만 호출하는 방법을 사용 할 수 있다.
이런 경우 수신용으로 할당된 버퍼가 없으므로 잠겨지는(locked) 메모리가 없다.
또한 이런 방식을 사용할때는 소켓의 수신 버퍼의 크기는 바꾸지 않아야 한다.
왜냐하면 0 바이트의 overapped i/o작업이 완료 되면 논블록킹 수신으로 소켓버퍼에 있는 데이터를
가져와야 하기 때문이다.이때 넌 블록킹 수신이 WSAEWOULDBLOCK를 반납하면 소켓 버퍼에 더이상
읽을 데이터가 없는 것이다.
이러한 설계는 각 연결별 데이터 처리 능력을 희생해서 서버의 연결 용량을 증가시키는 방법이다.
또 하나 고려해야 할 사항은 서버가 구동되고 있는 시스템의 메모리 페이지 크기이다.
시스템은 overapped i/o에 전달된 메모리를 페이지 단위로 lock한다. x86에서는 페이지는 4KB의
배수로 lock 된다. 만일 overapped i/o로 1KB의 버퍼를 전달했다면 시스템은 이 버퍼를 포함한 4KB를
lock한다. 따라서 메모리 낭비를 막기 위해서는 버퍼를 페이지 단위(페이지의 배수)로 사용 해야 한다.
GetSystemInfo함수를 호출하면 시스템에서 사용하는 페이지 크기를 얻어 올 수 있다.
SYSTEM_INFO sysInfo;
GetSystemInfo( &sysInfo );
해보면 sysInfo 안에 dwPageSize로 4096 으로 되어 있다.
2) non-page pool
한계치에 다다르는 경우는 문제를 해결하기가 어렵다.
non-page pool은 물리적 메모리에 상주하면서 페이징 되지 않는영역이다.
드라이버와 같은 커널모드 콤포넌트 들이 non-page pool을 사용하는데 윈속과 tcpip.sys와 같은
프로토콜 드라이버들도 여기에 속한다. 소켓을 생성 할 때마다 소켓의 상태정보를 저장하기 위한
용도로 작은 양의 non-page pool이 소비된다. 여기에 추가로 소켓이 특정 주소에 바인드되면
TCP/IP스택은 로컬 주소 정보를 저장하기 위한 용도로 non-page pool을 할당한다.
결과적으로 연결된 소켓은 2KB, accept나 acceptEX에 의해 리턴된 소켓은 1.5KB의 non-page pool을
소비한다.( accept, acceptEX에 의해 리턴도니 소켓은 리모트 주소만 저장하므로) 또한
각 overapped i/o 별로 IRP( I/O request packet)를 발생시키면서 대략 500바이트의 non-page pool을
소비한다. 연결별로 사용되는 non-page pool은 얼마되지 않는다.하지만 연결이 증가할수록 non-page
pool의 문제는 심각해진다. 1GB의 물리적 메모리를 갖춘 windows에서 서버가 동작중이라 가정하면
이런경우 256MB가 non-page pool로 사용 될 수 있다. 보통 non-page pool은 windows2000 이상의
버전에서는 물리적 메모리의 4분의1이 한계이다. windows NT4.0에서는 128MB가 한계이다.
서버가 50,000개 이상의 연결을 관리한다고 생각해보면 여기에는 데이터의 송신과 수신용 overapped i/o뿐만 아니라 연결수락(accept)용 overapped i/o작업도 고려해야 한다. 이 경우 accept나 acceptex
에 의하여 생성된 소켓으로 75MB의 non-page pool을 소비한다. 0 바이트의 overapped I/O호출 기법을 사용한다고 가정하면 ITP에 의해 25MB의 non-page pool이 소비된다.
시스템에서 non-page pool이 부족하면 나타나는 증상은 두가지 이다.
운이 좋은 경우 윈속 함수가 WSAENOBUFS에러를 발생시킨다. 그렇지 않을 경우 시스템이
에러로 손상(crash)될 수 있다. 이런 에러는 non-page pool을 회복하는 방법이 없다.
더군다나 사용가능한 non-page pool을 알아보는 방법도 없다. 결국 얼마나 많은 수의 연결과
overlapped i/o작업이 가능한지 경정하는 프로그래밍 기법이나 정해진 한계치가 없다.
non-page pool이 부족하거나 locked page가 초과된 경우 모두 WSAENOBUFS에러를 발생하므로
둘을 구별하는것도 불가능하다. 이런 이유로 개발자는 반드시 서버의 연결 용량과 overlapped i/o
작업의 용량을 테스트해봐야 한다. non-page pool의 부족이 프로그래밍적으로 일어나지 않도록 막았다면 WSAENOBUFS 에러가 locked page의 초과로 인해 발생됬다고 가정하고 더 이상의 overlapped
i/o작업을 금지하거나 몇개의 연결을 닫는것으로 에러를 피할수 있다.
서버는 대용량 처리용 서버와 대량 연결용 서버로 나눌 수 있다.
대용량 처리용 서버
1. 각 연결에 대하여 전송 시간을 최소화 시켜야 한다.
이런 목적으로 서버는 동시에 연결 할 수 있는 수를 제한해 놓은 경우가 많다.
제한 함으로써 많아 졌을때 연결별로 처리 능력이 떨어지는 현상을 방지 할 수 있다.
일정수의 연결을 세트로 해서 처리하는 방법이 있다.
예를 들어 서버는 클라의 연결을 100개씩 FIFO방식으로 처리한다. 첫번째 100개를 처리하면
큐에 쌓여있던 다른 100개의 클라를 처리한다. 송수신 I/O작업은 연결별로 일정수를 유지
할 수 있다. 따라서 클라는 연결이 증가됨에 따라 서버의 I/O작업이 무작정 늘어나서 리소스가
고갈되는 것을 방지 할 수 있다.
악의적인 클라에 의해 서버가 공격받는것을 방지하기 위해서는 서버에서는 각 연결별로
I/O작업이 발생되는 수를 체크해야 한다. 예를 들어 서버가 클라의 데이터를 수신하도록
설계되어 있다면 얼마나 보내졌는지 응답하는 구조로 사용한다. 클라는 서버에게 데이터를
마구 보내면서 응답하지 않는다면 많은 수의 overlapped i/o 송신이 누적 될 것이다.
이런경우 서버는 이 연결을 종료 해야한다.