본문 바로가기

MS/P2P

TURN - Server (Relay server)

출처 : http://purematter.blog.me/110102481519



샘플 소스코드에 포함된 TURN-Server는 속칭 중계서버, 릴레이서버라고 불리는 TURN 서버 어플리케이션입니다. 샘플 소스코드에 포함된 P2P 클라이언트를 위한 TURN 서버로 사용될 수 있습니다. 이 서버는 STUN 서버의 역할도 할 수 있기 때문에, 역시 P2P 클라이언트에서 필요로 하는 STUN 서버로 사용할 수 있습니다.

P2P 클라이언트를 이 서버와 연동하기 위해서는, 단지 TURN-Server를 실행시키고 난 후, 서버의IP주소와 portp2pnt 라이브러리에 제공하기만 하면 됩니다.

여러분은 기본 설정으로 제공되는 이 서버를 커스터마이징하여 실전에서 적용되도록 할 수 있습니다. 그 중 클라이언트에 대한 인증 부분은 빼놓을 수 없습니다. 예를 들어, 로그인 한 클라이언트만이 TURN 서버를 사용할 수 있게 할 수 있습니다. 아니면 TURN 서버에서 별도로 인증을 처리할 수도 있습니다. 별도로 인증을 처리하는 방법은 여러가지가 있을 수 있습니다. 인증서버로 질의를 보낼 수도 있고, 직접 데이터베이스에 조회를 하는 것도 가능하며, 클라이언트 스스로가 credential이 있음을 증명하게 할 수도 있습니다.

기본으로 제공되는 TURN 서버는 실행파일로 제공되기 때문에 단지 실행만 하면 사용하는 데 무리가 없습니다. 기본적인 TURN의 기능을 테스트하는 것에는 특별한 수정 또한 필요치 않습니다. 그러나 여러분은 필요에 따라 코드를 수정하여 TURN 서버를 동적/정적 라이브러리로 만들 수 있습니다. 그리고 그것을 게임서버, 로비서버 등에 중계 기능을 내장할 수 있을 것입니다. 혹은 전용서버(dedicated) 형태로도 가능하며, 클라이언트가 TURN 서버의 역할을 하게 할 수도 있습니다.

특히 클라이언트가 TURN 서버의 역할을 하게 하는 것은 매우 장점을 많은 배치입니다. 이를 통하면 온라인게임에서 방장이 패킷을 중계하게 하는 것을 매우 쉽게 할 수 있습니다. 방장을 교체하는 것도, 단지 다시 그룹을 생성하면 됩니다. 이전 챕터에서 설명한 랑데부 라이브러리도 특정 호스트가 쉽게 랑데부서버의 역할을 할 수 있게 하는 것과 같습니다. TURN 라이브러리와 랑데부 라이브러리를 적절히 사용한다면 특정 클라이언트를 서버처럼 운용하는 것도 가능할 것입니다. 중계처리 및 이를 응용한 분산처리를 쉽게! 적용할 수 있다면, 전반적인 서비스 시스템의 퀄리티를 유지하거나 높이기 쉬워질 뿐만 아니라 비용절감에도 큰 이득을 기대할 수 있습니다.

분석하는 순서

두 가지 단계로 구분하여 TURN서버를 분석합니다.

1. 초기화부터 실행까지

서버가 시작하여 모든 초기화과정이 끝나는 지점까지를 다룹니다.

2. 패킷 처리 과정

다시 3가지 항목으로 구분하여 살펴봅니다.

클라이언트의 Allocation request를 처리하는 과정

Permission이 등록되는 과정

클라이언트가 피어에게 데이터패킷을 전송할때 TURN 서버가 그 패킷을 중계처리하는 과정

초기화에서 실행까지

main() 함수에서부터 살펴봐야 합니다.

int main()

{

pj_turn_srv *srv;

pj_turn_listener *listener;

pj_status_t status;

status = pj_init();

if (status != PJ_SUCCESS)

return err("pj_init() error", status);

pjlib_util_init();

pjnath_init();

pj_caching_pool_init(&g_cp, NULL, 0);

pj_turn_auth_init(REALM);

status = pj_turn_srv_create(&g_cp.factory, &srv);

if (status != PJ_SUCCESS)

return err("Error creating server", status);

status = pj_turn_listener_create_udp(srv, pj_AF_INET(), NULL, TURN_PORT, 1, 0, &listener);

if (status != PJ_SUCCESS)

return err("Error creating UDP listener", status);

/*

#if PJ_HAS_TCP

status = pj_turn_listener_create_tcp(srv, pj_AF_INET(), NULL, TURN_PORT, 1, 0, &listener);

if (status != PJ_SUCCESS)

return err("Error creating listener", status);

#endif

*/

status = pj_turn_srv_add_listener(srv, listener);

if (status != PJ_SUCCESS)

return err("Error adding listener", status);

puts("Server is running");

pj_log_set_level(LOG_LEVEL);

console_main(srv);

pj_turn_srv_destroy(srv);

pj_caching_pool_destroy(&g_cp);

pj_shutdown();

return 0;

}

밑줄 그어진 pj_turn_srv_create (), pj_turn_listener_create_udp (), pj_turn_srv_add_listener ()가 서버를 초기화하고 실행하는 함수입니다.

pj_turn_srv_create ()

PJ_DEF(pj_status_t) pj_turn_srv_create(pj_pool_factory *pf, pj_turn_srv **p_srv)

{

pj_pool_t *pool;

pj_stun_session_cb sess_cb;

pj_turn_srv *srv;

unsigned i;

pj_status_t status;

PJ_ASSERT_RETURN(pf && p_srv, PJ_EINVAL);

/* Create server and init core settings */

pool = pj_pool_create(pf, "srv%p", 1000, 1000, NULL);

srv = PJ_POOL_ZALLOC_T(pool, pj_turn_srv);

srv->obj_name = pool->obj_name;

srv->core.pf = pf;

srv->core.pool = pool;

srv->core.tls_key = srv->core.tls_data = -1;

/* Create ioqueue */

status = pj_ioqueue_create(pool, MAX_HANDLES, &srv->core.ioqueue);

if (status != PJ_SUCCESS)

goto on_error;

/* Server mutex */

status = pj_lock_create_recursive_mutex(pool, srv->obj_name, &srv->core.lock);

if (status != PJ_SUCCESS)

goto on_error;

/* Allocate TLS */

status = pj_thread_local_alloc(&srv->core.tls_key);

if (status != PJ_SUCCESS)

goto on_error;

status = pj_thread_local_alloc(&srv->core.tls_data);

if (status != PJ_SUCCESS)

goto on_error;

/* Create timer heap */

status = pj_timer_heap_create(pool, MAX_TIMER, &srv->core.timer_heap);

if (status != PJ_SUCCESS)

goto on_error;

/* Configure lock for the timer heap */

pj_timer_heap_set_lock(srv->core.timer_heap, srv->core.lock, PJ_FALSE);

/* Array of listeners */

srv->core.listener = (pj_turn_listener**) pj_pool_calloc(pool, MAX_LISTENERS, sizeof(srv->core.listener[0]));

/* Create hash tables */

srv->tables.alloc = pj_hash_create(pool, MAX_CLIENTS);

srv->tables.res = pj_hash_create(pool, MAX_CLIENTS);

/* Init ports settings */

srv->ports.min_udp = srv->ports.next_udp = MIN_PORT;

srv->ports.max_udp = MAX_PORT;

srv->ports.min_tcp = srv->ports.next_tcp = MIN_PORT;

srv->ports.max_tcp = MAX_PORT;

/* Init STUN config */

pj_stun_config_init(&srv->core.stun_cfg, pf, 0, srv->core.ioqueue, srv->core.timer_heap);

/* Init STUN credential */

srv->core.cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;

srv->core.cred.data.dyn_cred.user_data = srv;

srv->core.cred.data.dyn_cred.get_auth = &pj_turn_get_auth;

srv->core.cred.data.dyn_cred.get_password = &pj_turn_get_password;

srv->core.cred.data.dyn_cred.verify_nonce = &pj_turn_verify_nonce;

/* Create STUN session to handle new allocation */

pj_bzero(&sess_cb, sizeof(sess_cb));

sess_cb.on_rx_request = &on_rx_stun_request;

sess_cb.on_send_msg = &on_tx_stun_msg;

status = pj_stun_session_create(&srv->core.stun_cfg, srv->obj_name, &sess_cb, PJ_FALSE, &srv->core.stun_sess);

if (status != PJ_SUCCESS) {

goto on_error;

}

pj_stun_session_set_user_data(srv->core.stun_sess, srv);

pj_stun_session_set_credential(srv->core.stun_sess, PJ_STUN_AUTH_LONG_TERM, &srv->core.cred);

/* Array of worker threads */

srv->core.thread_cnt = MAX_THREADS;

srv->core.thread = (pj_thread_t**)

pj_pool_calloc(pool, srv->core.thread_cnt, sizeof(pj_thread_t*));

/* Start the worker threads */

for (i=0; i<srv->core.thread_cnt; ++i) {

status = pj_thread_create(pool, srv->obj_name, &server_thread_proc, srv, 0, 0, &srv->core.thread[i]);

if (status != PJ_SUCCESS)

goto on_error;

}

/* We're done. Application should add listeners now */

PJ_LOG(4,(srv->obj_name, "TURN server v%s is running", pj_get_version()));

*p_srv = srv;

return PJ_SUCCESS;

on_error:

pj_turn_srv_destroy(srv);

return status;

}

함수의 이름에서부터 서버를 생성한다고 말하고 있으니, 염두에 두고 살펴보겠습니다.

1. 함수의 시작부분에서 pj_turn_srv 를 위한 메모리를 할당하는 것을 볼 수 있습니다. 전체 루틴을 훑어본다면, 또한 함수 전체가 서버자료구조를 초기화한다는 것을 알 수 있습니다.

2. ioqueue, timer, lock을 포함하고 있습니다. pj_turn_srvTURN 서버의 최상위 자료구조임을 나타내고 있습니다.

3. STUN session을 생성하고 있습니다. TURN 서버인 만큼 STUN session을 포함하는 것은 납득할만 합니다. TURN STUN을 확장한 것이기 때문입니다(때문에 STUN TURN역할도 하는 것입니다). STUN 패킷의 송수신 과정에서 STUN session이 구체적으로 어떤 일을 수행하는 지 알기 위해서는 제공하고 있는 STUN callback 함수들을 살펴봐야 합니다.

4. 지정된 수만큼의 스레드를 생성하고 있습니다. server_thread_proc 가 무슨 일을 하는지 살펴봐야 합니다.

5. port 설정입니다. TURN 서버에서 port를 선택할 때 참고될 것입니다.

서버오브젝트를 생성하는 것이 이 함수의 목적이라는 것을 알 수 있습니다. 또한 스레드를 실행하였으므로, 스레드함수가 어떤 일을 수행하는 지 파악해야합니다. 이것은 잠시 후에 보기로 하고 먼저 pj_turn_srv STUN session callback이 어떻게 정의되는지 살펴보겠습니다.

pj_turn_srv 구조체

struct pj_turn_srv

{

/** Object name */

char *obj_name;

/** Core settings */

struct {

/** Pool factory */

pj_pool_factory *pf;

/** Pool for this server instance. */

pj_pool_t *pool;

/** Global Ioqueue */

pj_ioqueue_t *ioqueue;

/** Mutex */

pj_lock_t *lock;

/** Global timer heap instance. */

pj_timer_heap_t *timer_heap;

/** Number of listeners */

unsigned lis_cnt;

/** Array of listeners. */

pj_turn_listener **listener;

/** STUN session to handle initial Allocate request. */

pj_stun_session *stun_sess;

/** Number of worker threads. */

unsigned thread_cnt;

/** Array of worker threads. */

pj_thread_t **thread;

/** Thread quit signal */

pj_bool_t quit;

/** STUN config. */

pj_stun_config stun_cfg;

/** STUN auth credential. */

pj_stun_auth_cred cred;

/** Thread local ID for storing credential */

long tls_key, tls_data;

} core;

/** Hash tables */

struct {

/** Allocations hash table, indexed by transport type and

* client address.

*/

pj_hash_table_t *alloc;

/** Relay resource hash table, indexed by transport type and

* relay address.

*/

pj_hash_table_t *res;

} tables;

/** Ports settings */

struct {

/** Minimum UDP port number. */

pj_uint16_t min_udp;

/** Maximum UDP port number. */

pj_uint16_t max_udp;

/** Next UDP port number. */

pj_uint16_t next_udp;

/** Minimum TCP port number. */

pj_uint16_t min_tcp;

/** Maximum TCP port number. */

pj_uint16_t max_tcp;

/** Next TCP port number. */

pj_uint16_t next_tcp;

} ports;

};

이 정도에서 위의 모든 자료형을 파악해야 하는 것은 아닙니다. 먼저 큰 그림을 살펴보는 것이 중요합니다.

main () 에서부터 pj_turn_srv_create () 함수와 pj_turn_srv를 분석한 바로는 다음과 같이 서버 자료구조를 도식화할 수 있습니다.

클래스 다이어그램으로 TURN server 구조체들간의 관계를 나타낸 것입니다.

비지니스 로직처리를 위한 pj_stun_session I/O를 처리하는 ioqueue, timer를 포함하고 있습니다. 임의의 갯수만큼 스레드도 실행시켰습니다. server_thread_proc() pj_turn_srv 오브젝트에 의존하고 있습니다. server_thread_proc ()

STUN session 콜백 함수

pj_turn_srv STUN session에 제공된 2개의 STUN 콜백함수입니다.

on_rx_stun_request

on_tx_stun_msg

static pj_status_t on_rx_stun_request(pj_stun_session *sess, const pj_uint8_t *pdu,

unsigned pdu_len, const pj_stun_rx_data *rdata,

void *token, const pj_sockaddr_t *src_addr,

unsigned src_addr_len)

{

pj_turn_transport *transport;

const pj_stun_msg *msg = rdata->msg;

pj_turn_srv *srv;

pj_turn_allocation *alloc;

pj_status_t status;

PJ_UNUSED_ARG(pdu);

PJ_UNUSED_ARG(pdu_len);

transport = (pj_turn_transport*) token;

srv = transport->listener->server;

/* Respond any requests other than ALLOCATE with 437 response */

if (msg->hdr.type != PJ_STUN_ALLOCATE_REQUEST) {

stun_respond(sess, transport, rdata, PJ_STUN_SC_ALLOCATION_MISMATCH, NULL, PJ_FALSE, src_addr, src_addr_len);

return PJ_SUCCESS;

}

/* Create new allocation. The relay resource will be allocated

* in this function.

*/

status = pj_turn_allocation_create(transport, src_addr, src_addr_len, rdata, sess, &alloc);

if (status != PJ_SUCCESS) {

/* STUN response has been sent, no need to reply here */

return PJ_SUCCESS;

}

/* Done. */

return PJ_SUCCESS;

}

1. pj_turn_transport pj_turn_srv를 참조하고 있습니다. on_rx_stun_request () 는 패킷이 수신되면 호출되는 함수이므로 납득할 만한 참조입니다. pj_turn_transport는 아직 살펴보지 않았습니다만 I/O와 관련이 있습니다. 그리고 그래야만 합니다. 그렇지 않다면 명칭이 잘 못지은 것입니다.

2. 이 콜백함수가 하는 일은 입력된 패킷이 TURN Allocate request (PJ_STUN_ALLOCATE_REQUEST)pj_turn_allocation_create ()을 호출하는 것입니다. pj_turn_srv, 다시 말해 TURN 서버가 TURN 프로토콜과 관련하여 하는 일은 Allocation request를 처리하는 것 뿐입니다. Allocate request를 처리하는 pj_turn_allocation_create ()는 잠시 후에 살펴봅니다.

아래는 STUN sessionSTUN 패킷을 전송하면 호출되는 콜백함수입니다.

static pj_status_t on_tx_stun_msg (pj_stun_session *sess, void *token, const void *pdu, pj_size_t pdu_size, const pj_sockaddr_t *dst_addr, unsigned addr_len)

{

pj_turn_transport *transport = (pj_turn_transport*) token;

PJ_ASSERT_RETURN(transport!=NULL, PJ_EINVALIDOP);

PJ_UNUSED_ARG(sess);

return transport->sendto(transport, pdu, pdu_size, 0, dst_addr, addr_len);

}

1. on_rx_stun_request () 와 마찬가지로 transport를 참조합니다. pj_turn_transport가 다시 등장했습니다. I/O 자료구조는 초기화가 필요하므로 곧 어딘가에서 반드시 관련된 루틴이 등장할 것입니다.

2. TURN transport를 사용해서 실제로 패킷을 송신합니다. pj_turn_transport는 잠시 후에살펴보겠습니다만, 중요한 것은 STUN session이 전송계층과 분리되어 있다는 것입니다. STUN session은 직접적인 I/O와 관련이 없다는 것은 중요합니다.

스레드 함수

아래는 스레드를 실행하는 코드입니다.

pj_thread_create(pool, srv->obj_name, &server_thread_proc, srv, 0, 0, &srv->core.thread[i]);

static int server_thread_proc(void *arg)

{

pj_turn_srv *srv = (pj_turn_srv*)arg;

while (!srv->core.quit) {

pj_time_val timeout_max = {0, 100};

srv_handle_events(srv, &timeout_max);

}

return 0;

}

시간값과 함께 svr_handle_events를 다시 호출하고 있습니다.

static void srv_handle_events(pj_turn_srv *srv, const pj_time_val *max_timeout)

{

/* timeout is 'out' var. This just to make compiler happy. */

pj_time_val timeout = { 0, 0};

unsigned net_event_count = 0;

int c;

/* Poll the timer. The timer heap has its own mutex for better

* granularity, so we don't need to lock the server.

*/

timeout.sec = timeout.msec = 0;

c = pj_timer_heap_poll( srv->core.timer_heap, &timeout );

/* timer_heap_poll should never ever returns negative value, or otherwise

* ioqueue_poll() will block forever!

*/

pj_assert(timeout.sec >= 0 && timeout.msec >= 0);

if (timeout.msec >= 1000) timeout.msec = 999;

/* If caller specifies maximum time to wait, then compare the value with

* the timeout to wait from timer, and use the minimum value.

*/

if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) {

timeout = *max_timeout;

}

/* Poll ioqueue.

* Repeat polling the ioqueue while we have immediate events, because

* timer heap may process more than one events, so if we only process

* one network events at a time (such as when IOCP backend is used),

* the ioqueue may have trouble keeping up with the request rate.

*

* For example, for each send() request, one network event will be

* reported by ioqueue for the send() completion. If we don't poll

* the ioqueue often enough, the send() completion will not be

* reported in timely manner.

*/

do {

c = pj_ioqueue_poll( srv->core.ioqueue, &timeout);

if (c < 0) {

pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));

return;

} else if (c == 0) {

break;

} else {

net_event_count += c;

timeout.sec = timeout.msec = 0;

}

} while (c > 0 && net_event_count < MAX_NET_EVENTS);

}

srv_handle_events () I/O 디스패칭 루프입니다. srv_handle_events() 루프를 돌면서 pj_ioqueue_poll () 를 호출하고 있습니다. pj_ioqueue_poll () 함수는 제공된 ioqueue에서 이벤트 발생시 유저함수(콜백함수)를 호출하여 이벤트를 통보합니다.

아래는 I/O 이벤트가 발생하면서부터 클라이언트에게 응답하는 것까지의 함수 호출 체인(call chain)입니다.

아직 설명하지 않은 함수들의 역할은 차차 드러날 것입니다. 지금은, 네트워크의 input output 과정에서 서버상의 호출 체인이 이렇다라는 큰 그림을 아는 것이 중요합니다.

pj_ioqueue_poll () 내부에서 패킷수신을 확인하면 on_read_complete () 함수를 호출합니다. 이 함수는 ioqueue의 읽기 이벤트 콜백함수로서 기초적인 I/O 루틴을 구현하고 있음을 확인한 바 있습니다. pj_turn_srv_on_rx_pkt () 를 호출하여 STUN session으로 처리를 넘깁니다. 이 함수는 서버를 생성할 때 STUN session에게 제공했던 on_rx_stun_request () 콜백함수를 호출합니다. 패킷이 Allocation을 요청하는 패킷이라면 pj_turn_allocation_create ()를 호출합니다. 이 함수는 pj_turn_allocation이라는 구조체를 생성합니다. Allocate request가 성공했다면 send_allocate_response ()pj_stun_session_send_msg (), on_tx_stun_msg () 호출로 이어집니다. 마지막으로 송신을 담당하는 pj_turn_transport 오브젝트를 통해서 ioqueue에 전달됩니다.

위의 그림은 마치 하나의 메시지 파이프라인을 보는 듯합니다.

pj_turn_allocation 구조체

Allocation 오브젝트는 pj_turn_allocation 으로 정의되는 구조체입니다. 이 구조체는 클라이언트가 TURN Allocate request를 전송하면 생성됩니다. 새로운 클라이언트일 경우, 서버의 STUN 패킷 수신 처리 콜백 함수인 on_rx_stun_request ()에서 호출하는 pj_turn_allocation_create ()에 의해 생성됩니다. pj_turn_allocationpj_turn_srv과 마찬가지로 pj_stun_session을 가지고 있습니다.

struct pj_turn_allocation

{

...

pj_stun_session *sess;

...

}

이는 패킷중계 프로토콜을 처리하는 STUN session입니다.

Allocation reqeust 처리를 거의 마쳤으면 이제 TURN client로 응답을 전송하는 일이 남았습니다. send_allocate_response () TURN 응답 패킷을 생성하고 pj_stun_session_send_msg ()를 호출하여 서버의 STUN session으로 송신처리를 넘깁니다. 최종적으로 송신콜백함수가 호출되고 ioqueue를 통해 클라이언트로 응답을 보냅니다.

TURN Allocation을 할당하는 pj_turn_allocation_create () 함수는 잠시후에 살펴봅니다.

pj_turn_listener_create_udp ()

이 함수는 UDP 리스너(listener)를 생성합니다. 리스너는 TCP listen() 함수를 떠올리면 됩니다. 특정 포트에 UDP 데이터그램 수신 감시자를 생성합니다. 직접적으로 I/O를 나타내는 것보다 좀 더 일반적인(추상화된) 자료구조입니다.

아래는 그와같은 리스너를 구현하는 구조체의 정의입니다

struct pj_turn_listener

{

/** Object name/identification */

char *obj_name;

/** Slightly longer info about this listener */

char info[80];

/** TURN server instance. */

pj_turn_srv *server;

/** Listener index in the server */

unsigned id;

/** Pool for this listener. */

pj_pool_t *pool;

/** Transport type. */

int tp_type;

/** Bound address of this listener. */

pj_sockaddr addr;

/** Socket. */

pj_sock_t sock;

/** Flags. */

unsigned flags;

/** Destroy handler */

pj_status_t (*destroy)(pj_turn_listener*);

};

PJ_DEF(pj_status_t) pj_turn_listener_create_udp( pj_turn_srv *srv,int af, const pj_str_t *bound_addr, unsigned port, unsigned concurrency_cnt, unsigned flags, pj_turn_listener **p_listener)

{

pj_pool_t *pool;

struct udp_listener *udp;

pj_ioqueue_callback ioqueue_cb;

unsigned i;

pj_status_t status;

/* Create structure */

pool = pj_pool_create(srv->core.pf, "udp%p", 1000, 1000, NULL);

udp = PJ_POOL_ZALLOC_T(pool, struct udp_listener);

udp->base.pool = pool;

udp->base.obj_name = pool->obj_name;

udp->base.server = srv;

udp->base.tp_type = PJ_TURN_TP_UDP;

udp->base.sock = PJ_INVALID_SOCKET;

udp->base.destroy = &udp_destroy;

udp->read_cnt = concurrency_cnt;

udp->base.flags = flags;

udp->tp.obj_name = udp->base.obj_name;

udp->tp.info = udp->base.info;

udp->tp.listener = &udp->base;

udp->tp.sendto = &udp_sendto;

udp->tp.add_ref = &udp_add_ref;

udp->tp.dec_ref = &udp_dec_ref;

/* Create socket */

status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &udp->base.sock);

if (status != PJ_SUCCESS)

goto on_error;

/* Init bind address */

status = pj_sockaddr_init(af, &udp->base.addr, bound_addr, (pj_uint16_t)port);

if (status != PJ_SUCCESS)

goto on_error;

/* Create info */

pj_ansi_strcpy(udp->base.info, "UDP:");

pj_sockaddr_print(&udp->base.addr, udp->base.info+4, sizeof(udp->base.info)-4, 3);

/* Bind socket */

status = pj_sock_bind(udp->base.sock, &udp->base.addr, pj_sockaddr_get_len(&udp->base.addr));

if (status != PJ_SUCCESS)

goto on_error;

/* Register to ioqueue */

pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));

ioqueue_cb.on_read_complete = on_read_complete;

status = pj_ioqueue_register_sock(pool, srv->core.ioqueue, udp->base.sock, udp,&ioqueue_cb, &udp->key);

/* Create op keys */

udp->read_op = (struct read_op**)pj_pool_calloc(pool, concurrency_cnt, sizeof(struct read_op*));

/* Create each read_op and kick off read operation */

for (i=0; i<concurrency_cnt; ++i) {

pj_pool_t *rpool = pj_pool_create(srv->core.pf, "rop%p", 1000, 1000, NULL);

udp->read_op[i] = PJ_POOL_ZALLOC_T(pool, struct read_op);

udp->read_op[i]->pkt.pool = rpool;

on_read_complete(udp->key, &udp->read_op[i]->op_key, 0);

}

/* Done */

PJ_LOG(4,(udp->base.obj_name, "Listener %s created", udp->base.info));

*p_listener = &udp->base;

return PJ_SUCCESS;

on_error:

udp_destroy(&udp->base);

return status;

}

1. udp_listener 라는 구조체 메모리를 할당하고 있습니다.

struct udp_listener

{

pj_turn_listener base;

pj_ioqueue_key_t *key;

unsigned read_cnt;

struct read_op **read_op; /* Array of read_op's */

pj_turn_transport tp; /* Transport instance */

};

pj_turn_srv pj_turn_listener를 포함하고 있고, 아직 생성되지 않았기 때문에 이 함수에서 pj_turn_listener를 생성하리라는 것을 예상할 수 있습니다. 다만 pj_turn_listener에 직접 메모리를 할당하는 것과 같이 단순하게 하지 않고 pj_turn_listener를 포함하고 있는 udp_listener라는 구조체를 통해 생성하고 있습니다. 이와 같은 디자인의 이유는, 리스너라는 추상화된 오브젝트를 통해서 서버로의 I/O 입력을 단순화한, 다시 말해 TURN 서버가 그러한 방식으로 추상화되어 있기 때문입니다. pj_turn_srv로의 네트워크 입력은 pj_turn_listener가 담당하도록 디자인되어 있습니다. UDP 리스너를 생성하는 pj_turn_listener_create_udp () 함수는 UDP 라는 특화된 리스너를 udp_listener 구조체를 자체적으로(지역적으로) 추상화한 것입니다. 편의를 위해 이렇게 했다고 볼 수도 있습니다. udp_listener는 소켓을 ioqueue에 등록할 때 토큰값으로 전달했다가, 나중에 ioqueue 콜백함수가 호출되면 다시 가져올 수 있습니다.

2. 이 함수가 원래 해야 할 일을 하고 있다고 할 수 있습니다.

3. 소켓을 ioqueue에 등록합니다.

4. 지정된 수만큼의 읽기 오퍼레이션을 실행합니다.

5. pj_turn_listener 오브젝트를 리턴합니다.

'MS > P2P' 카테고리의 다른 글

샘플 코드 (Client)  (0) 2012.11.07
PJNATH - TURN 전송 모듈  (0) 2012.11.07
PJNATH - TURN 세션 모듈  (0) 2012.11.07
PJLib - APIs  (0) 2012.11.07
P2P network library project  (0) 2012.11.07