AOSA Volume I, Ch01 – Asterisk

* 오픈 소스 응용 프로그램 구조(The architecture of Open Source Applications) 1권에 대한 모든 내용은 여기에서 볼 수 있다.

 

Russel Bryant

원문: http://aosabook.org/en/asterisk.html

Asterisk는 GPLv2로 배포 중인 오픈 소스 전화통신 응용 플랫폼이다. 간단히 얘기하면 전화를 걸거나 받고, 또는 전화 호출을 사용자 입맛에 맞춰 처리하는 서버 응용 프로그램이다.

이 프로젝트는 1999년에 마크 스펜서(Mark Spencer)가 시작했다. 당시 마크는 리눅스 지원 서비스(Linux Support Services)라는 회사를 차렸는데 자신의 사업을 관리하는 데 도움이 될 전화 시스템이 필요했다. 하지만 이를 구입할 자금이 충분치 않았기에 그냥 직접 만들었다. Asterisk에 대한 인기가 커져감에 따라 리눅스 지원 서비스 역시 Asterisk에 집중하고 사명을 디지움(Digium, Inc.)으로 바꿨다.

Asterisk란 이름은 유닉스 와일드카드 문자 *에서 따왔다. Asterisk 프로젝트의 목표는 모든 전화 통신을 처리하는 것이며, 이 목표를 좇으며 현재는 전화 통화를 처리하기 위한 수 많은 기술을 지원하고 있다. 이러한 기술에는 많은 VoIP(IP 기반 음성 통화, Voice over IP) 프로토콜을 비롯해, 기존 전화망에 대한 아날로그와 디지털 연결, 또는 PSTN(공중 교환 전화망, Public Switched Telephone Network) 등을 포함한다. 한 시스템에서 종류가 다른 많은 전화 통화를 처리하는 이러한 능력은 Asterisk의 주요 강점 중 하나이다.

Asterisk 시스템에는 전화 통화를 발신 또는 수신했을 때 해당 통화를 입맛 대로 처리할 수 있는 많은 부가 기능이 존재한다. 내장된 공통 응용 프로그램  중 일부 기능은 음성 메일처럼 상대적으로 크기가 크지만 함께 결합해 소리 파일 재생, 숫자 읽기 또는 음성 인식과 같은 사용자 정의 음성 응용 프로그램을 만들 수 있는 상대적으로 더 작은 기능도 많이 있다.

1.1. 핵심 구조 개념

이 절에서는 Asterisk의 모든 부분에 중요한 몇몇 구조적 개념을 논의한다. 이러한 아이디어는 Asterisk 구조의 기초를 이룬다.

1.1.1. 통신로

Asterisk에서 통신로(channel)는 Asterisk 시스템과 어떤 전화 통신 종점(endpoint) 사이 연결을 뜻한다(그림 1.1). 가장 일반적인 예는 Asterisk 시스템으로 전화를 걸 때이다. 이 연결은 단일 통신로로 표현하며, Asterisk 코드에서 통신로 하나는 ast_channel 데이터 구조체의 인스턴스 하나로 존재한다. 예를 들면, 이 호출 시나리오에서 한 호출자는 음성 우편과 상호 작용 중일 수 있다.

그림 1.1: 단일 통신로로 표현한 단일 호출 레그

그림 1.1: 단일 통신로로 표현한 단일 호출 레그

1.1.2. 통신로 브리징

아마 더 익숙한 호출 시나리오는 한 사람이 전화 A로 전화 B를 호출해 두 전화가 연결된 상황일 것이다. 이 호출 시나리오에서는 두 전화 통신 종점이 Asterisk 시스템에 연결되어 있으므로 두 통신로가 존재한다(그림 1.2).

그림 1.2: 두 통신로로 표현한 두 호출 레그

그림 1.2: 두 통신로로 표현한 두 호출 레그

이렇게 연결한 Asterisk 통신로를 통신로 브리지(channel bridge)라 하는데, 통신로 브리지는 채널 사이에 매체를 전달할 목적으로 통신로를 함께 연결하는 행위이다. 가장 일반적인 매체 스트림은 음성 스트림이지만, 통화에는 영상이나 텍스트 스트림도 있을 수도 있다. (음성과 영상 모두가 있는 것처럼) 매체 스트림이 하나 이상일 때조차 Asterisk에서는 해당 통화 각 종점에 대해 여전히 단일 통신로로 처리한다. 그림 1.2를 보면, 전화 A와 B에 대해 통신로가 둘이며, 브리지는 전화 A에서 전화 B로 또는 전화 B에서 A로 매체를 전달한다. 모든 매체 스트림은 Asterisk를 통해 협상하며, Asterisk에서 이해하지 못하거나 완전히 제어하지 못하는 것은 그 무엇도 허용하지 않는다. 이는 곧 Asterisk에서 녹음, 음성 조작, 서로 다른 기술 간 변환을 할 수 있음을 뜻한다.

두 통신로를 함께 브리지할 때 사용할 수 있는 방법에는 두 가지, 즉 일반화 브리징(generic bridging)과 네이티브 브리징(native bridging)이 있다. 일반화 브리징은 사용하는 통신로 기술에 관계 없이 작동하며, Asterisk 추상 통신로 인터페이스를 통해 모든 음성과 신호를 전달한다. 이는 가장 유연한 브리징 방법인 동시에 해당 작업을 처리하는 데 필요한 추상화 수준으로 인해 가장 효율이 떨어진다. 그림 1.2는 일반화 브리지에 대한 예이다.

네이티브 브리지는 기술에 따라 통신로를 연결하는 방법이다. 동일한 매체 운반 기술을 사용해 Asterisk에 두 채널을 연결한다면, 서로 다른 기술을 연결하기 위해 존재하는 Asterisk 내 추상화 계층를 통하는 것보다 더 효율적인 연결 방법이 있을 수 있다. 예를 들어 전문 하드웨어를 사용해 전화망에 연결한다면 통신로를 그 하드웨어에 브리지함으로써 응용 프로그램을 전혀 통하지 않고 매체를 전달할 수 있다. 일부 VoIP 프로토콜에서는 호출 신호 정보만 계속 서버를 통할 뿐, 각 종점에서 상대방에게 직접 매체 스트림을 보낼 수 있다.

일반화 브리징과 네이티브 브리징 중 선택은 두 채널을 브리지할 때 그 채널을 비교해 결정한다. 두 채널 모두 동일한 네이티브 브리징 방법을 지원한다면 그 방법을 사용하며, 그렇지 않으면 일반화 브리징 방법을 사용한다.  두 채널에서 네이티브 브리징 방법을 지원하는지 여부는 간단한 C 함수 포인터 비교를 사용한다. 분명히 가장 세련된 방법은 아니지만 아직까지 요구를 벗어나는 상황은 보지 못했다. 통신로에 네이티브 브리지 함수를 제공하는 방법은 1.2절에서 더 상세히 논의하며, 그림 1.3은 네이티브 브리지에 대한 예이다.

그림 1.3: 네이티브 브리지 예

그림 1.3: 네이티브 브리지 예

1.1.3. 프레임

통화하는 동안 Asterisk 코드에서 통신은 ast_frame 데이터 구조체의 인스턴스인 프레임을 사용해 처리한다. 프레임은 매체 프레임과 신호 프레임 중 하나일 수 있다. 음성을 담고 있는 매체 프레임의 스트림은 기본적인 전화 통화 동안 시스템을 통해 전달한다. 신호 프레임은 숫자를 누르거나 전화 대기 신호 또는 전화를 끊는 신호와 같은 통화 신호 사건(event)에 관한 메시지를 보내는 데 사용한다.

사용 가능한 프레임 타입 목록은 정적으로 정의하며, 프레임은 수치로 부호화한 타입과 하위 타입으로 표시한다. 전체 목록은 소스 코드 중 include/asterisk/frame.h에서 볼 수 있으며 일부 예는 다음과 같다.

  • VOICE: 이 프레임은 음성 스트림을 운반한다.
  • VIDEO: 이 프레임은 영상 스트림을 운반한다.
  • MODEM: IP 기반 FAX 전송을 위한 T.38처럼 이 프레임 내 데이터에 사용하는 부호화 방법이다. 이 프레임 타입은 주로 FAX를 처리하는 데 사용한다. 데이터 프레임은 신호를 다른 종점에서 성공적으로 복호화할 수 있도록 완벽히 보전하는 것이 중요하다. 이는 음성 스트림과는 다른데, 음성 스트림은 음질에 드는 비용인 대역폭을 절약하기 위해 다른 음성 코덱으로 변환할 수 있다.
  • CONTROL: 이 프레임에서는 통화 신호 메시지를 나타내며, 통화 신호 사건을 나타내는 데 사용한다. 이러한 사건에는 전화 응답, 전화 끊기, 전화 대기 등이 있다.
  • DTMF_BEGIN: 어떤 숫자를 막 시작했다. 이 프레임은 호출자가 자신의 전화에서 DTMF 키를 누를 때 보낸다.
  • DTMF_END: 어떤 숫자를 막 마쳤다. 이 프레임은 호출자가 자신의 전화에서 DTMF 키 누르기를 멈췄을 때 보낸다.

1.2. Asterisk 구성 요소 추상화

Asterisk는 고도로 모듈화한 응용 프로그램이다. 코어(core) 응용 프로그램은 소스 트리에서 main/ 디렉터리 내 소스로 만들지만, 그 자체로는 그리 유용하지 않다. 코어 응용 프로그램은 주로 모듈 레지스트리(registry) 역할을 하며, 이 프로그램에는 전화 통화를 위해 모든 추상 인터페이스를 함께 연결하는 방법을 알고 있는 코드도 있다. 이러한 인터페이스의 구체적 구현은 실행 중에 적재 가능 모듈(loadable module)을 통해 등록한다.

기본적으로, 해당 파일 시스템에 미리 정의해 둔 Asterisk 모듈 디렉터리에서 찾은 모든 모듈은 주 응용 프로그램을 시작할 때 적재하며 이는 단순함을 위한 선택이었다. 하지만 적재할 모듈과 적재 순서를 정확히 지정하기 위해 갱신할 수 있는 설정 파일도 있다. 이로 인해 설정이 좀 더 복잡해지긴 하지만 적재할 필요가 없거나 하지 말아야 할 모듈을 지정할 수 있다. 주요 이점은 이 응용 프로그램의 메모리 사용량 감소이지만 보안에 관한 이점도 일부 있다. 정말 필요한 게 아니면 네트워크를 통해 연결을 허용하는 모듈을 적재하지 않는 것이 최상이다.

모듈을 적재할 때는 Asterisk 코어 응용 프로그램에 구성 요소 추상화의 모든 구현을 등록한다. Asterisk 코어 응용 프로그램에 모듈을 구현하고 등록할 수 있는 인터페이스는 다양하며,  모듈에서는 서로 다른 인터페이스를 원하는 만큼 많이 등록할 수 있다. 일반적으로 연관된 기능은 단일 모듈로 통합한다.

1.2.1. 통신로 드라이버

Asterisk 통신로 드라이버 인터페이스는 사용 가능한 인터페이스 중 가장 복잡하고 중요하다. Asterisk 통신로 API는 사용 중인 전화통신 프로토콜과 별개로 다른 모든 Asterisk 기능이 작동할 수 있도록 하는 전화 통신 프로토콜 추상화를 제공한다. 이 구성 요소에서는 Asterisk 통신로 추상화와 이에서 구현한 전화 통신 기술 상세 내용 사이에 변환을 책임진다.

Asterisk 통신로 드라이버 인터페이스 정의는 ast_channel_tech 인터페이스라 하며, 통신로 드라이버에서 구현해야 하는 메서드 집합을 정의한다. 통신로 드라이버에서 구현해야 하는 첫 번째 메서드는 ast_channel 팩토리 메서드인데, 이는 ast_channel_techrequester 메서드이다. 통화 수신이나 발신 중 어느 하나에 대해 Asterisk 통신로를 생성할 때, 필요한 통신로 종류와 연관시킨 ast_channel_tech 구현에서는 해당 호출에 대한 ast_channel 인스턴스화와 초기화를 책임진다.

ast_channel을 생성하면, 이것에서는 자신을 생성한 ast_channel_tech에 대한 참조를 유지한다. 기술에 따라 달리 처리해야 하는 많은 연산이 있는데, 그러한 연산은 반드시 ast_channel을 대상으로 하며 그 연산에 대한 처리는 ast_channel_tech에 속한 적절한 메서드에서 담당한다. 그림 1.2에서는 Asterisk의 두 통신로를 볼 수 있으며, 그림 1.4에서는 브리지한 두 통신로와 이에 맞춘 통신로 기술 구현 방법을 보여주기 위해 그 그림을 확장했다.

그림 1.4: 통신로 기술과 추상 통신로 계층

그림 1.4: 통신로 기술과 추상 통신로 계층

ast_channel_tech에서 가장 중요한 메서드는 다음과 같다.

  • requester: 이 콜백은 통신로 드라이버에 요청해 ast_channel 객체를 인스턴스화하고 해당 통신로 종류를 적절히 초기화하는 데 사용한다.
  • call: 이 콜백은 ast_channel로 나타내는 종점으로 아웃바운드(outbound) 호출을 시작한다.
  • answer: ast_channel과 연관된 인바운드(inbound) 호출에 응답함을 Asterisk에서 결정하면 호출한다.
  • hangup: 통화를 끊어야 함을 시스템에서 결정하면 호출한다. 그런 다음 통신로 드라이버에서는 프로토콜별 방법을 사용해 해당 통화가 끝났음을 해당 종점에 통보한다.
  • indicate: 통화가 연결되면 종점에 알려야 하는 많은 사건이 있다. 예를 들어 장치가 대기 상태가 되면 이 콜백을 호출해 그 상태를 나타낸다. 통화가 대기 상태임을 나타내는 프로토콜별 방법이 있을 수 있으며, 또는 단순히 통신로 드라이버에서 해당 장치에 대기 상태 음악을 재생할 수도 있다.
  • send_digit_begin: 이 함수는 해당 장치에 보낼 숫자(DTMF) 하나를 시작함을 나타내기 위해 호출한다.
  • send_digit_end: 이 함수는 해당 장치에 보낼 숫자(DTMF) 하나를 마침을 나타내기 위해 호출한다.
  • read: 해당 종점으로부터 받은 ast_frame을 읽기 위해 Asterisk 코어에서 호출한다. Asterisk에서 ast_frame은 (음성이나 영상과 같은) 매체를 캡슐화할 뿐만 아니라 사건을 알리는 데 사용하는 추상화이다.
  • write: 장치에 ast_frame을 보내는 데 사용한다. 통신로 드라이버에서는 해당 데이터를 취해, 구현하는 전화통신 프로토콜에 적합하게 패킷화하고 종점으로 전달한다.
  • bridge: 해당 통신로 종류에 대한 네이티브 브리지 콜백이다. 앞에서 논의한 것처럼 네이티브 브리징은 불필요한 추가적인 추상화 계층을 통해 모든 신호와 매체 흐름을 처리하는 대신 종류가 같은 두 통신로에 대해 통신로 드라이버에서 더 효율적인 브리징 방법을 구현할 수 있을 때 한다.

통화가 끝나면 Asterisk 코어에 살고 있는 추상화 통신로 처리 코드에서는 ast_channel_tech hangup을 호출한 다음 ast_channel 객체를 소멸한다.

1.2.2. 번호 계획 응용 프로그램

Asterisk 관리자는 /etc/asterisk/extensions.conf에 있는 Asterisk 번호 계획(dialplan)을 사용해 호출 전달 경로를 설정한다.  번호 계획은 확장(extension)이라고 하는 일련의 호출 규칙으로 구성한다. 시스템에서 전화 호출을 받으면 전화를 건 번호로, 해당 호출을 처리하는 데 사용할 확장을 번호 계획에서 찾는다. 해당 확장에는 해당 통신로에 실행할 번호 계획 응용 프로그램 목록이 있다. 번호 계획을 실행하는 데 사용 가능한 응용 프로그램은 응용 프로그램 레지스트리에서 관리하며, 실행 중에 모듈을 적재함에 따라 이 레지스트리를 채운다.

Asterisk에 있는 용응 프로그램은 거의 200개에 이른다.  응용 프로그램에 대한 정의는 매우 느슨하며, 응용 프로그램에서는 Asterisk의 모든 내부 API를 사용해 통신로와 상호 작용할 수 있다. 일부 응용 프로그램은 호출자에게 소리 파일을 재생해 들려주는 Playback과 같은 단일 작업을 하는 반면, 다른 것은 Voicemail 응용 프로그램처럼 훨씬 복잡하고 많은 연산을 수행한다.

Asterisk 번호 계획을 사용하면 여러 응용 프로그램을 함께 사용해 호출 처리를 입맛 대로 처리할 수 있다. 만약 제공하는 번호 계획 언어로 할 수 있는 것보다 더 폭넓게 처리해야 한다면 아무 프로그래밍 언어를 사용해 호출 처리를 입맛 대로 처리할 수 있도록 하는 스크립팅 인터페이스를 사용할 수 있다. 다른 프로그래잉 언어로 이러한 스크립팅 인터페이스를 사용할 때조차 해당 통신로와 상호 작용하는 데 여전히 번호 계획 응용 프로그램을 호출한다.

예를 살펴보기 전에 번호 1234에 대한 호출을 처리하는 Asterisk 번호 계획에 대한 구문을 살펴보자. 여기서 1234는 임의로 선택한 것임에 주의한다. 세 가지 번호 계획 응용 프로그램을 호출하는데 먼저 호출에 응답하고, 다음으로 소리 파일을 재생하며, 마지막으로 해당 통화를 끊는다.

; Define the rules for what happens when someone dials 1234.
;
exten => 1234,1,Answer()
    same => n,Playback(demo-congrats)
    same => n,Hangup()

exten 키워드는 확장을 정의하는 데 사용한다. exten 줄 오른편에 있는 1234는 누군가가 1234를 호출했을 때에 대한 규칙을 정의했음을 뜻한다. 다음 1은 그 번호로 전화를 걸었을 때 취할 첫 번째 단계임을 뜻하며, 마지막으로 Answer는 해당 호출에 응답하도록 시스템에 지시한다. same 키워드로 시작하는 다음 두 줄은 지정한 마지막 확장, 즉 이 예에서는 1234에 대한 규칙이다. n는 해당 내용이 다음 단계로 취할 것임을 알리는 간략한 표현이다. 이 두 줄에서 마지막 항목은 취할 행동을 지정한다.

다음은 Asterisk 번호 계획을 사용하는 다른 예이다. 이 예에서는 수신한 호출에 응답하고, 호출자에게 삑 소리를 들려준 다음 호출자가 입력한 숫자 4개를 읽어 DIGITS 변수에 저장한다. 그런 후 그 숫자를 호출자에게 다시 들려주고 통화를 마친다.

exten => 5678,1,Answer()
    same => n,Read(DIGITS,beep,4)
    same => n,SayDigits(${DIGITS})
    same => n,Hangup()

이전에 언급했듯이 응용 프로그램 정의는 매우 느슨하므로 등록되어 있는 함수 원형은 매우 단순하다.

int (*execute)(struct ast_channel *chan, const char *args);

하지만 실질적으로 응용 프로그램 구현에서는 include/asterisk/에서 찾을 수 있는 모든 API를 사용한다.

1.2.3. 번호 계획 함수

번호 계획 응용 프로그램 대부분에서는 여러 인자로 구성한 문자열 하나를 취한다. 일부 값은 코드에 직접 기입되어 있을 수 있지만 더 동적인 행동을 필요로 하는 곳에서는 변수를 사용한다. 다음 예는 변수를 설정한 다음 Verbose 응용 프로그램을 사용해 Asterisk 명령 줄에 그 값을 출력하는 번호 계획 코드 조각이다.

exten => 1234,1,Set(MY_VARIABLE=foo)
    same => n,Verbose(MY_VARIABLE is ${MY_VARIABLE})

이전 예와 동일한 구문을 사용해 번호 계획 함수를 호출한다. Asterisk 모듈에서는 일부 정보를 가져와 그 정보를 번호 계획에 반환할 수 있는 번호 계획 함수를 등록할 수 있는 반면, 번호 계획 함수는 번호 계획으로부터 데이터를 받아 그에 따라 행동할 수 있다. 일반적으로 번호 계획 함수는 통신로 메타 정보를 설정하거나 가져올 수 있는 반면에 신호를 보내거나 매체를 처리하지는 않는다. 이는 번호 계획 응용 프로그램 몫이다.

다음 예에서는 번호 계획 함수 사용법을 볼 수 있다. 먼저 현재 통신로의 CallerID를 Asterisk 명령 줄 인터페이스에 출력한 다음 Set 응용 프로그램을 사용해 CallerID를 변경한다. 이 예에서 VerboseSet은 응용 프로그램이고 CALLERID는 함수이다.

exten => 1234,1,Verbose(The current CallerID is ${CALLERID(num)})
    same => n,Set(CALLERID(num)=<256>555-1212)

여기서는 단순 변수가 아니라 번호 계획 함수가 필요한데 CallerID 정보는 ast_channel의 인스턴스에 대한 데이터 구조체에 저장되어 있기 때문이다. 번호 계획 함수 코드에는 이러한 데이터 구조체에 값을 설정하거나 그 구조체에서 값을 가져오는 방법이 있다.

번호 계획 함수를 사용하는 다른 예는 사용자 정의 정보를 호출 로그에 추가하는 것인데 이를 CDR(호출 상세 기록, Call Detail Record)이라 한다. CDR 함수를 사용하면 호출 상세 기록 정보를 가져올 수 있을 뿐만 아니라 사용자 정의 정보를 추가할 수도 있다.

exten => 555,1,Verbose(Time this call started: ${CDR(start)})
    same => n,Set(CDR(mycustomfield)=snickerdoodle)

1.2.4. 코덱 변환기

VOIP 세계에서는 네트워크를 가로질러 보낼 매체를 부호화하는 데 사용하는 코덱이 다양하며, 그 선택에 따라 매체 품질, CPU 소비량, 필요 대역폭 등이 달라진다. Asterisk에서는 수 많은 코덱을 지원하며 필요하면 서로 다른 코덱으로 변환할 수 있다.

호출을 설정할 때 Asterisk에서는 변환이 필요없도록 일반 매체 코덱을 사용해 두 종점을 연결하려 시도하지만 항상 가능한 것은 아니라. 일반 코덱을 사용할 때조차 여전히 변환해야 할 수도 있다. 예를 들어, 시스템을 통해 음성을 전달할 때 (소리 크기를 증가시키거나 감소시키는 것처럼) 그 음성에 어떤 신호 처리를 하도록 Asterisk를 설정한다면 신호를 처리하기 전에 해당 음성을 비압축 형식으로 되돌리는 변환을 해야 할 것이다. 또한 호출 기록을 처리하도록 Asterisk를 설정할 수도 있는데, 기록을 위해 설정한 형식이 호출 형식과 다를 때도 변환이 필요하다.

코덱 협상

매체 스트림에 어느 코덱을 사용할지 협상하는 방법은 호출을 Asterisk에 연결하는 데 사용한 기술에 따른다. 전통적인 공중 교환 전화망(PSTN)과 같은 일부 상황에서는 협상이 전혀 없을 수도 있지만, 특히 IP 프로토콜을 사용하는 다른 상황에서는 성능과 선호도를 표현하고 공통 코덱에 동의하는 협상 메커니즘이 있다.

(가장 흔히 사용하는 VOIP 프로토콜인) SIP를 예로 들면, Asterisk에서 호출을 받았을 때 수행하는 개략적인 코덱 협상 방법을 다음에서 볼 수 있다.

  1. 한 종점에서는 기꺼이 사용할 코덱 목록을 포함한 새로운 호출 요청을 Asterisk에 보낸다.
  2. Asterisk에서는 관리자가 허용 코덱을 선호순으로 나열한 목록이 있는 설정을 참조한다. Asterisk에서는 그 설정에서 허용하고 수신한 요청에서도 지원하는 것으로 나열한 것 중 가장 선호하는 코덱을 (설정한 선호도를 바탕으로) 선택해 응답한다.

더 복잡한 코덱, 특히 영상은 Asterisk에서 그다지 잘 처리하지 못하는 영역 중 하나이다. 코덱 협상 요구는 지난 10년 동안 더 복잡해졌으며, 우리는 최신 음성 코덱을 더 잘 처리하고 영상을 현재 수준보다 더 잘 처리할 수 있도록 더 열심히 작업하고 있다. 이는 Asterisk의 다음 주요 배포판 개발에서 최우선 과제 중 하나이다.

코덱 변환기 모듈에서는 ast_translator 인터페이스에 대한 구현을 하나 이상 제공한다. 변환기에는 출발지와 목적지 형식 속성이 있으며, 출발지에서 목적지 형식으로 매체 덩어리(chunk)를 변환하는 데 사용할 콜백도 제공한다. 또한 전화 통화 개념에 관해서는 전혀 모르며, 매체를 한 형식에서 다른 것으로 변환하는 방법만 알 뿐이다.

변환기 API에 관한 더 자세한 정보는 include/asterisk/translate.hmain/translate.c를 참조한다. 변환기 추상화에 관한 구현은 codecs 디렉터리에서 찾을 수 있다.

1.3. 스레드

Asterisk는 스레드를 엄청나게 사용하는 응용 프로그램이다. 잠금과 같은 관련 서비스와 스레드를 관리하는 데에는 POSIX 스레드 API를 사용한다. 스레드와 상호작용하는 모든 Asterisk 코드는 디버깅 목적으로 사용하는 래퍼(wrapper) 집합을 통한다. Asterisk에서 스레드 대부분은 네트워크 스레드와 (통신로를 위해 PBX를 실행하는 것이 주목적이어서 때때로 PBX 스레드라고도 하는) 통신로 스레드 중 하나로 분류할 수 있다.

1.3.1. 네트워크 감시 스레드

네트워크 감시 스레드는 Asterisk 내 모든 주요 통신로 드라이버에 존재하며, (IP 네트워크, PSTN 등) 그 드라이버에서 연결한 모든 네트워크를 감시하고, 수신한 호출이나 다른 여러 수신 요청을 감시한다. 또한 인증과 전화를 건 번호에 대한 유효성 검증과 같은 초기 연결 설정 단계도 처리한다. 호출 설정을 완료하면 감시 스레드에서는 Asterisk 통신로(ast_channel)의 인스턴스를 생성하고, 살아 있는 동안 호출을 처리하는 통신로 스레드를 실행한다.

1.3.2. 통신로 스레드

앞에서 논의한 것처럼 통신로는 Asterisk에서 기본 개념이다. 통신로는 인바운드 또는 아웃바운드이다. 인바운드 통신로는 Asterisk 시스템에서 호출을 받을 때 생성하며, Asterisk 번호 계획을 실행한다. 번호 계획을 실행하는 모든 인바운드 통신로에 대해 스레드를 생성하며 이를 통신로 스레드라 한다.

번호 계획 응용 프로그램은 항상 통신로 스레드 문맥에서 실행한다. 번호 계획 함수 역시 거의 언제나 그렇다. Asterisk CLI와 같은 비동기 인터페이스에서 번호 계획 함수를 읽고 쓸 수 있지만, ast_channel 데이터 구조체의 소유자이며 그 객체의 생명 주기를 제어하는 것은 여전히 언제나 통신로 스레드이다.

1.4. 호출 시나리오

이전 두 절에서는 Asterisk 구성 요소에 대한 중요 인터페이스와 스레드 실행 모델을 소개했다. 이 절에서는 전화 통화를 처리하기 위해 여러 Asterisk 구성 요소가 함께 작동하는 방법을 보여주기 위해 몇 가지 일반적인 호출 시나리오 중 일부를 살펴본다.

1.4.1. 음성 우편 확인

호출 시나리오 중 한 예는 음성 우편을 확인하기 위해 전화 시스템을 호출할 때이다. 이 시나리오에 연관된 첫  번째 주요 구성 요소는 통신로 드라이버이다. 통신로 드라이버는 해당 전화로부터 수신한 호출 요청을 처리할 책임이 있으며, 그 요청은 해당 통신로 드라이버의 감시 스레드에서 발생한다. 해당 호출을 시스템에 전달하는 데 사용하는 전화 통신 기술에 따라 그 호출을 설정하는 데 어떤 협상이 필요할 수 있다. 호출을 설정하는 다른 단계에서는 해당 호출에 대해 의도한 목적지를 결정하며, 일반적으로 이는 호출자가 전화를 건 번호로 지정한다. 하지만 호출을 전달하는 데 사용한 기술에서 전화 건 번호를 지정하는 것을 지원하지 않으므로 사용할 수 있는 특정 번호가 없는 때도 있다. 이러한 예는 아날로그 전화선으로 호출을 수신할 때이다.

전화 건 번호에 대해 번호 계획(호출 전달 경로 설정)에 정의한 확장이 Asterisk 설정에 있음을 통신로 드라이버에서 검증하면, 그 드라이버에서는 Asterisk 통신로 객체(ast_channel)를 할당하고 통신로 스레드를 생성한다. 통신로 스레드의 주요한 책임은 해당 호출 나머지를 처리하는 것이다(그림 1.5).

그림 1.5: 호출 설정 순차도

그림 1.5: 호출 설정 순차도

통신로 스레드의 주 루프에서는 번호 계획 실행을 처리하며, 수신한 번호에 대한 확장에 정의한 규칙을 찾아 정의해 둔 단계를 실행한다. 다음은 extensions.conf 번호 계획 구문으로 표현한 확장에 관한 예이다. 이 확장에서는 누군가가 *123으로 전화를 걸었을 때 그 호출에 응답하고 VoicemailMain 응용 프로그램을 실행하는데, 사용자는 이 응용 프로그램을 호출해 자신의 우편함에 있는 메시지를 확인할 수 있다.

exten => *123,1,Answer()
    same => n,VoicemailMain()

통신로 스레드에서 Answer 응용 프로그램을 실행하면 Asterisk에서는 수신한 호출에 응답한다. 호출에 응답할 때는 기술에 따라 달리 처리해야 하므로 어떤 일반적인 응답 처리에 추가로, 연관된 ast_channel_tech 구조체 내 answer 콜백을 호출해 해당 호출을 처리한다. 여기엔 IP 네트워크로 특별한 패킷을 보내거나 아날로그 전화를 걸기 위해 드는 등의 행위를 포함할 수 있다.

다음 단계에서는 통신로 스레드에서 VoicemailMain을 실행한다(그림 1.6). 이 응용 프로그램은 app_voicemail 모듈에서 제공한다. 주목할 중요한 한 가지는 음성 우편 코드에서 많은 호출 상호 작용을 처리하지만 해당 호출을 Asterisk 시스템에 전달하는 데 사용하고 있는 기술에 관해서는 전혀 모른다는 점이다. Asterisk 통신로 추상화에서는 이러한 상세 내용을 음성 우편 구현에서 모르도록 숨긴다.

호출자가 자신의 음성 우편에 접근할 수 있도록 해 주는 많은 기능이 있지만, 그 모두는 주로 호출자가 입력(주로 숫자를 누르는 형식이다)한 것에 대한 응답으로 소리 파일을 읽거나 기록하도록 구현되어 있다. DTMF 숫자는 Asterisk에 다양한 방법으로 전달할 수 있으며, 다시 얘기하지만 이러한 상세 구현은 통신로 드라이버에서 처리한다. 어떤 키를 눌렀음이 Asterisk에 도착하면 그 정보를 일반화 키 누름 사건으로 변환하고 음성 우편 코드에 전달한다.

이미 논의한 Asterisk 내 중요 인터페이스 중 하나는 코덱 변환기 인터페이스이다. 이러한 코덱 구현은 이 호출 시나리오에 매우 중요하다. 음성 우편 코드에서 호출자에게 소리 파일을 재생하려 할 때 그 소리 파일 내 음성 형식이 Asterisk 시스템과 호출자 사이 통신에 사용하는 음성 형식과 다를 수 있다. 그 음성을 변환해야 한다면 출발지에서 목적지 형식에 이르기까지 코덱 변환기가 하나 이상인 변환 경로를 구성할 것이다.

그림 1.6: VoicemailMain에 대한 호출

그림 1.6: VoicemailMain에 대한 호출

 

언젠가 호출자는 음성 우편 시스템과 상호 작용을 마치고 끊을 것이다. 통신로 드라이버에서는 이를 감지해 일반화 Asterisk 통신로 신호 전달 사건으로 변환하며, 음성 우편 코드에서는 이 신호 전달 사건을 받고 종료한다. 호출자가 끊으면 처리할 일이 전혀 없기 때문이다. 제어는 통신로 스레드 내 주 루프로 반환해 번호 계획 실행을 계속하도록 한다. 이 예에는 처리할 번호 계획 처리 과정이 더 없으므로, 통신로 드라이버에서 기술별 종료 과정을 처리할 수 있으며 그런 후 ast_channel 객체가 소멸한다.

1.4.2. 브리지한 호출

Asterisk에서 매우 일반적인 다른 호출 시나리오는 두 통신로 사이에 브리지한 호출이다. 이는 한쪽 전화에서 시스템을 통해 다른쪽 전화를 호출하는 시나리오이다. 초기 호출 단계 과정은 이전 예와 같지만, 호출을 설정하고 통신로 스레드에서 번호 계획을 실행할 때부터 처리할 내용이 달라진다.

다음 번호 계획은 브리지한 호출을 하는 간단한 예이다. 이 확장을 사용하면, 1234로 전화를 걸 때 해당 번호 계획에서 Dial 응용 프로그램을 실행한다. 이 프로그램은 아웃바운드 호출을 시작하는 데 사용하는 주 응용 프로그램이다.

exten => 1234,1,Dial(SIP/bob)

Dial 응용 프로그램에 지정한 인자는 SIP/bob로 참조하는 장치로 아웃바운드 호출을 해야 함을 시스템에 알린다. 이 인자에서 SIP 부분은 해당 호출을 전달하는 데 SIP 프로토콜을 사용해야 함을 지정하며, bob는 SIP 프로토콜을 구현하는 통신로 드라이버인 chan_sip에서 해석한다. bob로 부르는 계정에 대해 통신로 드라이버를 올바로 설정했다 가정하면 이 드라이버에서는 Bob의 전화에 이르는 방법을 안다.

Dial 응용 프로그램에서는 SIP/bob 식별자를 사용해 새로운 Asterisk 통신로를 할당하도록 Asterisk 코어에 요청한다. 코어에서는 SIP 통신로 드라이버가 기술에 따라 다른 초기화를 하도록 요청하며, 통신로 드라이버에서는 전화 호출 과정 역시 시작한다. 그 요청을 진행하면서 발생하는 여러 사건은 Asterisk 코어를 통해 Dial 응용 프로그램에서 받는다. 이러한 사건에는 응답한 호출에 대한 반응, 통화 중인 목적지, 네트워크 혼잡, 어떤 이유로 호출에 대한 응답 거부 또는 수 많은 반응 등이 있다. 이상적인 상황이라면 호출에 응답할 것이고, 그 사실은 인바운드 통신로로 전파된다. Asterisk에서는 아웃바운드 호출에 응답을 받을 때까지 시스템에서 받은 호출에 응답하지 않는다. 통신로 브리징은 두 통신로 모두 응답을 받으면 시작한다(그림 1.7).

그림 1.7: 일반화 브리지에서 브리지한 호출 계통도

그림 1.7: 일반화 브리지에서 브리지한 호출 계통도

통신로를 브리지하는 동안, 한 통신로에서 발생한 음성과 신호 전달 사건은 해당 호출의 어느 한 쪽에서 통화를 끊는 등 브리지를 종료하는 어떤 사건이 일어날 때까지 다른 쪽으로 전달한다. 그림 1.8에 있는 순차도에서는 호출을 브리지한 동안 음성 프레임에 수행하는 주요 연산을 볼 수 있다.

그림 1.8: 브리지한 동안 음성 프레임 처리 순차도

그림 1.8: 브리지한 동안 음성 프레임 처리 순차도

통화를 마친 후 종료 과정은 이전 예와 매우 비슷하다. 이 예와 주된 차이점은 통신로가 둘이라는 점이다. 통신로 기술별 종료 처리는 통신로 스레드 실행을 멈추기 전에 두 통신로 모두에 대해 실행한다.

1.5. 마지막으로 하고 싶은 말

Asterisk 구조는 이제 10년을 훌쩍 넘었다. 하지만 Asterisk 번호 계획을 사용한 유연한 호출 처리와 통신로라는 기본 개념은 계속해 발전하고 있는 복잡한 전화 통신 시스템 개발에 여전히 주춧돌 역할을 하고 있다. Asterisk 구조 중 별로 설명하지 않은 영역은 여러 서버 사이에 시스템 규모를 가변화하는 것이다. 현재 Asterisk 개발 공동체에서는 이러한 규모 가변성에 관한 문제를 다루는 Asterisk SCF(규모 가변적인 통신 프레임워크, Scalable Communications Framework)라는 자매 프로젝트 개발을 진행 중이다. 몇 년 후에는 훨씬 더 많은 설치 횟수를 포함해  전화 통신 시장을 상당 부분 계속해 점유하는 Asterisk를 Asterisk SCF와 함께 볼 수 있길 기대한다.

Notes:
1. http://www.asterisk.org
2. DTMF는 이중톤 다중 주파수(Dual-Tone Multi-Frequency)를 뜻한다. 이는 전화기에 있는 키를 누를 때 전화 통화 음성으로 보내는 음이다.
http://www.asterisk.org
DTMF는 이중톤 다중 주파수(Dual-Tone Multi-Frequency)를 뜻한다. 이는 전화기에 있는 키를 누를 때 전화 통화 음성으로 보내는 음이다.