Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

소개

이 문서는 실무에 적용할 수 있는 네트워크 프로그램의 모델에 대해서 알아보도록 할 것이다. 네트워크 프로그래밍에 대해서 기본적인 이해는 가지고 있다고 가정할 것이다. 다수의 연결이나 대량의 데이터를 서비스 타입에 따라 어떻게 효율적으로 처리할 수 있을 것인지에 촛점을 맞출 것이다.

기본 지식

Open 가능한 파일 갯수

Linux(:12) Kernel(:12) 2.6을 기준으로 설명을 하겠다. Open 가능한 파일은 운영체제 제한과 프로세스 제한 두가지를 모두 고려해야 한다.

운영체제 제한

운영체제에서 열수 있는 파일의 제한은 아래와 같이 확인 할 수 있다.
# cat /proc/sys/fs/file-max
50442
운영체제가 열 수 있는 파일의 갯수는 위의 file-max의 값을 수정하는 정도로 간단히 조절할 수 있다.
# echo 81920 > /proc/sys/fs/file-max 
그러나 그렇다고 해서 무한대로 늘릴 수는 없을 것이다. 기본 값은 보통 kilobyte로 환산한 램의 크기의 10%로 잡는다. 1Gbyte의 램을 확보하고 있다면 104857이 기본으로 잡힐 것이다.

프로세스 제한

프로세스당 열 수 있는 파일의 제한값은 아래와 같이 확인할 수 있다. 기본값을 1024로 되어 있을 것이다.
# ulimit -n 
1024
다음과 같이 열 수 있는 파일의 갯수를 조절할 수 있다.
# ulimit -n 8192
그러나 ulimit를 이용한 파일의 갯수 조정은 현재 쉘에서만 유효하다. 그러므로 해당 프로세스의 실행시에 능동적으로 파일의 갯수를 조절하고 싶다면 setrlimit(:2)함수를 이용해서 설정을 해주어야 한다. 또는 해당 쉘의 profile에서 ulimit를 실행하도록 해도 될 것이다.

프로세스가 열 수 있는 파일의 갯수는 운영체제의 파일제한 갯수와 균형을 맞출 수 있도록 해야 할 것이다. 8192정도로 조정하는게 무난하리라 생각된다.

Port 영역

서비스에 따라서 밖으로 나가는 다수의 포트가 생길 수도 있을 것이다. 이럴 경우 outgoing 포트의 범위를 조정해줄 필요가 있다. 기본적으로 1024에서 4999까지가 outgoing 포트로 할당이 되는데, 3975이상의 outgoing 연결이 이루어질 경우 문제가 발생할 수 있다. outgoing 포트의 범위는 다음과 같이 확인할 수 있다.
# cat /proc/sys/net/ipv4/ip_local_port_range
범위는 다음과 같이 조정할 수 있다.
# echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range
이 방법을 사용할 경우 시스템이 리붓되면 다시 기본값으로 되돌아가게 되는데, 시스템 부팅시에도 변경된 값이 적용되게 하려면 /etc/sysctl.conf를 수정하면 된다.
# increase system IP port limits
net.ipv4.ip_local_port_range = 1024 65535

입출력 다중화

입출력 다중화는 select(:2)와 poll(:2)을 통해 구현된다. 입출력 다중화를 이용할 수 있는 몇가지 모델에 대해서 알아보도록 하겠다.

Port 분산

서비스 포트의 범위를 지정하고, 각 서비스 포트에 프로세스를 두는 방식으로 분산시킨다. 예를 들어 1000개의 클라이언트 연결을 처리해야 한다면, 10개의 포트로 나누어서 각 프로세스당 100개씩의 클라이언트를 처리하도록 하는 방식이다.

클라이언트가 연결을 시도할경우 처리량(혹은 연결량)이 적은 프로세스를 확인해서 이 정보를 클라이언트에게 알려주면 클라이언트는 해당 포트로 다시 연결을 하는 방식이다.

때문에 프로세스를 생성하고, 프로세스의 서비스 상태를 관리할 수 있는 Process Manager프로그램이 있어야 한다. 이 프로그램은 실행시 설정파일을 읽어서 포트의 갯수만큼 프로세스를 fork&exec 방식으로 실행시키게 된다. 프로세스 머니저는 서비스 프로세스의 상태를 알고 있어야 하는데, 이러한 정보는 공유메모리나 파일시스템등을 이용해야 한다.

각 서비스 프로세스는 다시 fork(2)나 입출력다중화(:12)등의 기술을 이용해서 다수의 클라이언트를 처리할 수 있다.

장점

일단 모델 자체가 단순하며, 직관적이라서 비교적 쉽게 구현할 수 있다. 또한 비교적 버그가 적은 안전한 프로그램을 만들 수 있다. 하나의 프로세스가 죽는다고 하더라도 전체서비스가 죽을 염려가 없으며, 서비스 프로세스의 비정상 종료로 클라이언트 연결이 종료되었을 경우 클라이언트는 다른 서비스 프로세스에 붙을 수 있으므로 안정적으로 서비스할 수 있다는 장점을 가진다.

단점

프로세스 관리자의 구현이 복잡할 수 있다. 공유자료구조를 유지해야 하며, 프로세스의 상태를 확인할 수 있어야 한다.

프로세스의 상태는 다음과 같은 방법중 하나를 이용할 수 있을 것이다.
  • proc(:12)를 이용한 검사
  • 파일잠금(:12)등을 통한 통보 : 프로세스가 죽으면 파일잠금이 풀린다.
  • pipe(:12)를 이용한 프로세스간 통신
만약 프로세스간 데이터 교환이 빈번한 서비스라면 구현이 더욱 복잡해 질것이다. 예를 들어 채팅서비스를 위의 방식으로 구현한다고 하면, 8000포트 프로세스에 연결된 유저가 80001번 포트 프로세스에 연결된 유저와 메시지를 교환해야 하는 경우가 발생할 것이다. 이럴경우에는 별도의 메시지 전달 프로세스를 두어야 하며 , 로그인된 유저정보를 전달하기 위한 별도의 자료구조도 공유해야 한다. 메시지 전달 프로세스는 각 서비스 프로세스와 파이프로 연결되어서 메시시의 경로를 설정할 수 있어야 한다. 혹은 메시지큐를 만들어서 자기에게 전달되는 메시지가 있는지 확인해서 읽어갈 수 있도록 코딩해야 할 것이다. 어느 방법을 이용해도 수월하지 않다는 것을 직감할 수 있을 것이다.

<!> 메시지의 전달을 위한 메시지큐의 구현에 대해서 생각해 보자.

적당한 서비스

포트번호를 얻기 위해서 Process Manager과 한번 더 연결을 해야 한다. 그러므로 웹과 같이 빈번한 연결/종료가 이루어지는 서비스에는 적합하지 않다. 파일전송 서비스, 스트리밍 서비스, 메시징 서비스와 같이 장시간 연결이 이루어지는 서비스에 적당하다.

PreFork

클라이언트가 연결되는 새로운 프로세스를 fork()해서 할당하는 방법은 지금까지도 널리 사용되고 있다. 제어하기가 용이하고 신뢰도가 높다는 장점이 있지만, fork시에 많은 비용이 소비된다는 단점이 있다. 바쁜서버일 경우 fork되는 시간이 문제가 될 수가 있다. prefork는 미래 자식프로세스를 포크 시켜놓고, 클라이언트의 연결이 들어오면, 만들어져 있는 자식프로세스에 업무를 할당하는 방식으로 운용된다. 이때 소켓전달 기술이 사용된다 자세한 내용은 소켓전달문서를 참고하기 바란다.