ice开发流程(一)

这一篇,我们先讲一些跟ice有关的接口函数的设计

我们仿照icedemo写两个结构体

struct zktrans_nat_cfg

{

/** STUN srv */

const char *stun_srv;

int stun_port;

/** TURN srv */

const char *turn_srv;

int turn_port;

const char *turn_username;

const char *turn_passwd;

/** callback */

PFN_zktrans_nat_cb_rtp cb_rtp;

PFN_zktrans_nat_cb_rtcp cb_rtcp;

PFN_zktrans_nat_cb_state cb_state;

void *userptr;

};

 

struct zktrans_nat_t

{

zktrans_nat_cfg cfg; // 用户配置信息

pj_caching_pool cp;

pj_pool_t *pool;

pj_ice_strans_cfg ice_cfg;

pj_ice_strans *ice_strans;

/** 保存本地, 对方 sdp 字符串 */

char *local_sdp, *remote_sdp;

pj_thread_t *thread;

pj_bool_t thread_quit_flag;

char *stun_srv, *turn_srv, *turn_user, *turn_passwd;

PFN_zktrans_nat_cb_rtp rtp_cb;

PFN_zktrans_nat_cb_rtp rtcp_cb;

void *userptr;

pj_thread_t *th_rtp, *th_rtcp;

pj_thread_desc th_rtp_desc, th_rtcp_desc;

pj_sockaddr def_rtp, def_rtcp;

};


1 结构体配置及库初始化 这一步主要围绕zktrans_nat_t 结构体中的ice_cfg这个变量展开。

int zktrans_nat_send_rtp(zktrans_nat_t *trans,const char *data,int len)

{

     static bool _inited = false;

     if(!_inited)// lib initialize

    {

        pj_init();

        pjlib_util_init();

        pjnath_init();

        _inited = true;

    }

    zktrans_nat_t *ins =new zktrans_nat_t;

    ins->cfg =*cfg;

    ins->ice_strans=0;

    pj_cacing_pool_init(&init->cp,0,0);

    //default ice stream trans cfg

    pj_ice_strans_cfg_default(&ins->ice_cfg);

    ins->ice_cfg.stun.cfg.pf=&ins->cp.factory;

    //ins->pool=pj_pool_create(&ins->cp.factory,"zktrans_nathlp",512,512,0);

    //time heap

    pj_timer_heap_create(ins->pool,100,&ins->ice_cfg.stun.cfg.timer_heap);//创建定时器堆,最多可放入100个定时器

    //ioqueue

    pj_ioqueue_create(ins->pool,16,&ins->ice_cfg.stun_cfg.ioqueue);

    ins->quit=false;

// start worker thread for timer & ioqueue

     pj_thread_create(ins->pool,"zktrans_nathlp poll thread",_poll_worker_thread,ins,0,0,&ins->worker_thread);

   ins->local_stun_server=0,ins->local_turn_server=0,ins->local_turn_user=0,ins->local_turn_passwd=0;

    if(ins->cfg.stun_srv)

    {

        ins->local_stun_server=strdup(ins->cfg.stun_srv);

        ins->ice_cfg.stun.server.ptr=ins->local_stun_server;

        ins->ice_cfg.stun.server.slen=strlen(ins->local_stun_server);

        ins->ice_cfg.stun.port=ins->cfg.stun_port;

    }

    if(ins->cfg.turn_srv)

    {

        ins->local_turn_server=strdup(ins->cfg.turn_srv);

        ins->local_turn_user=strdup(ins->cfg.turn_username);

        ins->local_turn_passwd=strdup(ins->cfg.turn_passwd);

        

        ins->ice_cfg.turn.server.ptr=ins->local_turn_server;

        ins->ice_cfg.turn.server.slen=strlen(ins->local_turn.server);

        ins->ice_cfg.turn.port=ins->cfg.turn_port;

            

        ins->ice_cfg.turn.auth_cred.type= PJ_STUN_AUTH_CRED_STATIC;

        ins->ice_cfg.turn.auth_cred.data.static_cred.username.ptr = ins->local_turn_user;

     ins->ice_cfg.turn.auth_cred.data.static_cred.username.slen = strlen(ins->local_turn_user);

    ins->ice_cfg.turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;

    ins->ice_cfg.turn.auth_cred.data.static_cred.data.ptr = ins->local_turn_passwd;

    ins->ice_cfg.turn.auth_cred.data.static_cred.data.slen = strlen(ins->local_turn_passwd);

        ins->local_sdp=0;

        ins->remote_sdp=0;

}

         return ins;    

}

2 通过pj_ice_strans_create收集候选者信息

void zktrans_nat_start(zktrans_nat_t *ins,int caller)

{

        assert(ins->ice_strans==0);

        pj_ice_strans_cb *cb=new pj_ice_strans_cb;

        cb->on_rx_data=cb_on_rx_data;

        cb->on_ice_complete=cb_on_ice_complete;

        ins->caller=caller;

        //通过pj_ice_strans_create收集候选者信息

        pj_ice_strans_create("zktrans_nat",&ins->ice_cfg,2,ins,cb,&ins->ice_strans);

        //这个函数用ins->ice_strans记录创建的成果,然后将ins作为数据信息传入,我们要在后面的cb_on_ice_complete中获取这个数据信息

}

这个函数执行完,我们本方的候选ip端口对已经收集好了,我们看看cb_on_rx_data和cb_on_ice_complete这两个函数。

其中cb_on_rx_data用于接收跟ice的消息数据无关的应用程序数据。例如RTP/RTCP。而cb_on_ice_complete用来报告ICE各种操作的结果。也就是说我们收集完候选者信息之后,也可以在这里得到报告。下面我们看看实现吧。

(1)先看cb_on_ice_complete 

static void cb_on_ice_complete (pj_ice_strans *strans,pj_ice_strans_op op,pj_status_t status)

{

       // 我们通过op参数获取ICE状态完成信息

        const char *opname    =    op == PJ_ICE_STRANS_OP_INIT ? "initialization":op == PJ_ICE_STRANS_OP_NEGOTIATION? "negotiation":"unknown_op";

        //下面我们获取数据信息

       zktrans_nat_t *ctx=(zktrans_nat_t *)pj_ice_strans_get_user_data(strans);

        if(op==PJ_ICE_STRANS_OP_INIT &&status==PJSUCCESS)

        {

            //说明收集候选者任务已经完成,启动ice

            pj_ice_strans_init_ice(ctx->ice_strans,ctx->caller?PJ_ICE_SESS_ROLE_CONTROLLING:PJ_ICE_SESS_CONTROLLED,0,0);

             //开始建立本地的sdp信息

            _bulid_local_sdp(ctx);

        }

        if(status == PJ_SUCCESS)

        {

                const char *info="OK";

                fprintf(stdout,"[%s] ICE %s successful \n",_FUNCTION_,opname);

                if(op== PJ_ICE_STRANS_OP_INIT)

                        ctx->cfg.cb_state(ctx,1,0,info,ctx->cfg.userptr);

                else if(op == PJ_ICE_STRANS_OP_NEGOTIATION)

                        ctx->cfg.cb_state(ctx,2,0,info,ctx->cfg.usertpr);

                else if(op== PJ_ICE_STRANS_OP_KEEP_ALIVE)

                          ctx->cfg.cb_state(ctx,3,0,info,ctx->cfg.userptr);

                 else

                        ctx->cfg.cb_state(ctx,100+op,0,info,ctx->cfg.userptr);

            }

            else{
                      char errmsg[PJ_ERR_MSG_SIZE];
                      pj_strerror(status, errmsg, sizeof(errmsg));
                          fprintf(stderr, "[%s] ICE %s failed: %s\n", __FUNCTION__, opname, errmsg);

                          if (op == PJ_ICE_STRANS_OP_INIT)
                   ctx->cfg.cb_state(ctx, 1, status, errmsg, ctx->cfg.userptr);
                      else if (op == PJ_ICE_STRANS_OP_NEGOTIATION)
                       ctx->cfg.cb_state(ctx, 2, status, errmsg, ctx->cfg.userptr);
                      else if (op == PJ_ICE_STRANS_OP_KEEP_ALIVE)
                       ctx->cfg.cb_state(ctx, 3, status, errmsg, ctx->cfg.userptr);
                      else {
                           ctx->cfg.cb_state(ctx, 100+op, status, errmsg, ctx->cfg.userptr);
                          }

        }

}

(2)再看看cb_on_rx_data

 static void cb_on_rx_data (pj_ice_strans *strans,
  unsigned comp_id,
  void *pkt, pj_size_t size,
  const pj_sockaddr_t *from,
  unsigned from_len)
{
 // 收到的 rtp, rtcp 数据
 zktrans_nat_t *trans = (zktrans_nat_t *)pj_ice_strans_get_user_data(strans);
 if (comp_id == 1 && trans->cfg.cb_rtp)
  trans->cfg.cb_rtp(trans, pkt, size, trans->cfg.userptr);
 else if (comp_id == 2 && trans->cfg.cb_rtcp)
  trans->cfg.cb_rtcp(trans, pkt, size, trans->cfg.userptr);
}

    我们再看看如何建立本地的sdp,其实就把自己的ice的用户名,密码等组成sdp的格式放到结构体的一个数组中存好。

static void _build_local_sdp (zktrans_nat_t *trans)
{

   size_t pos = 0;
 pj_ice_sess_cand cands[PJ_ICE_ST_MAX_CAND];
 char ipaddr[128];

 trans->local_sdp = (char*)malloc(4096); // 足够了 :)

 // 实际这里的 v o s t 无用, 仅仅为了符合 sdp 要求
 strcpy(trans->local_sdp, "v=0\r\no=- 3414953978 3414953978 IN IP4 localhost\r\ns=ice\r\nt=0 0\r\n");
 pos = strlen(trans->local_sdp);

 // ice-ufrag & ice-pwd
 pj_str_t ufrag, pwd;
 pj_ice_strans_get_ufrag_pwd(trans->ice_strans, &ufrag, &pwd, 0, 0);

 sprintf(trans->local_sdp+pos, "a=ice-ufrag:%.*s\r\na=ice-pwd:%.*s\r\n",
   (int)ufrag.slen, ufrag.ptr,
   (int)pwd.slen, pwd.ptr);
 pos = strlen(trans->local_sdp);

 // default candidate for rtp
 pj_status_t rc = pj_ice_strans_get_def_cand(trans->ice_strans, 1, &cands[0]);
 if (rc != PJ_SUCCESS) {
  fprintf(stderr, "[%s] pj_ice_strans_get_def_cand for RTP err\n", __FUNCTION__);
  exit(-1);
 }
 sprintf(trans->local_sdp+pos, "m=%s %d RTP/AVP %d\r\n"
   "c=IN IP4 %s\r\n",
   "audio", (int)pj_sockaddr_get_port(&cands[0].addr),
   91,
   pj_sockaddr_print(&cands[0].addr, ipaddr, sizeof(ipaddr), 0));
 pos = strlen(trans->local_sdp);

 // default candidate for rtcp
 rc = pj_ice_strans_get_def_cand(trans->ice_strans, 2, &cands[0]);
 if (rc != PJ_SUCCESS) {
  fprintf(stderr, "[%s] pj_ice_strans_get_def_cand for RTCP err\n", __FUNCTION__);
  exit(-1);
 }
 sprintf(trans->local_sdp+pos, "a=rtcp:%d IN IP4 %s\r\n",
   (int)pj_sockaddr_get_port(&cands[0].addr),
   pj_sockaddr_print(&cands[0].addr, ipaddr, sizeof(ipaddr), 0));
 pos = strlen(trans->local_sdp);

 // all candidate attrs of RTP
 unsigned cnt = PJ_ICE_ST_MAX_CAND;
 rc = pj_ice_strans_enum_cands(trans->ice_strans, 1, &cnt, cands);
 pj_assert(rc == PJ_SUCCESS);
 for (int i = 0; i < cnt; i++) {
  sprintf(trans->local_sdp+pos, "a=candidate:%.*s %u UDP %u %s %u typ %s\r\n",
    (int)cands[i].foundation.slen, cands[i].foundation.ptr,
    1, cands[i].prio,
    pj_sockaddr_print(&cands[i].addr, ipaddr, sizeof(ipaddr), 0),
    (unsigned)pj_sockaddr_get_port(&cands[i].addr),
    pj_ice_get_cand_type_name(cands[i].type));
  pos = strlen(trans->local_sdp);
 }

 // all candidate attrs of RTCP
 cnt = PJ_ICE_ST_MAX_CAND;
 rc = pj_ice_strans_enum_cands(trans->ice_strans, 2, &cnt, cands);
 pj_assert(rc == PJ_SUCCESS);
 for (int i = 0; i < cnt; i++) {
  sprintf(trans->local_sdp+pos, "a=candidate:%.*s %u UDP %u %s %u typ %s\r\n",
    (int)cands[i].foundation.slen, cands[i].foundation.ptr,
    2, cands[i].prio,
    pj_sockaddr_print(&cands[i].addr, ipaddr, sizeof(ipaddr), 0),
    (unsigned)pj_sockaddr_get_port(&cands[i].addr),
    pj_ice_get_cand_type_name(cands[i].type));
  pos = strlen(trans->local_sdp);
 }

 trans->local_sdp[pos] = 0;

}

 3 协商出最佳路径,这里要好好说说了。

假设我们是clientA,跟clientB要通信。clientA收集好自己候选者IP端口队后,要和clientB的候选者进行配对。找出最好的一条路径。这就是所谓的协商。那么我们需要知道clientB的候选者们,还有clientB的用户名密码,这些信息都得通过sdp消息格式获取

 void zktrans_nat_negotiate (zktrans_nat_t *ins, const char *sdp)//这里的sdp就是clientB的sdp信息
{

        xfree(ins->remote_sdp);

        ins->remote_sdp=strdup(sdp);//存好

        char ufrag[128], pwd[128];
     char default_rtp[80], default_rtcp[80];
     int default_rtp_port, default_rtcp_port;
     pj_ice_sess_cand cands[PJ_ICE_ST_MAX_CAND];

     int cnt = _parse_sdp(ins, sdp, ufrag, pwd, default_rtp, &default_rtp_port,
      default_rtcp, &default_rtcp_port, cands);
     pj_str_t user = pj_str(ufrag);
     pj_str_t passwd = pj_str(pwd);

    到这里我们就获取了clientB的候选者,用户,密码了,另外我们还获取了rtp和rtcp的端口,地址,我们存好,以后好用来向clientB发送rtp,rtcp数据

     // save def_rtp, def_rtcp
     pj_sockaddr_init(pj_AF_INET(), &ins->def_rtp, 0, 0);
     pj_sockaddr_init(pj_AF_INET(), &ins->def_rtcp, 0, 0);
     pj_str_t ip = pj_str(default_rtp);
     pj_sockaddr_set_str_addr(pj_AF_INET(), &ins->def_rtp, &ip);
     ip = pj_str(default_rtcp);
     pj_sockaddr_set_str_addr(pj_AF_INET(), &ins->def_rtcp, &ip);
     pj_sockaddr_set_port(&ins->def_rtp, default_rtp_port);
     pj_sockaddr_set_port(&ins->def_rtcp, default_rtcp_port);

     pj_ice_sess_options opts;
     pj_ice_strans_get_options(ins->ice_strans, &opts);
     opts.controlled_agent_want_nom_timeout = -1;
     pj_ice_strans_set_options(ins->ice_strans, &opts);

}

4 协商完成后,我们可以发送rtp,rtcp数据了

 int zktrans_nat_send_rtp (zktrans_nat_t *trans, const char *data, int len)
{
     if (!pj_thread_is_registered()) {
      pj_thread_register("zknath_rtp_sender_thread", trans->th_rtp_desc, &trans->th_rtp);
     }

     // sendto()
     pj_status_t rc = pj_ice_strans_sendto(trans->ice_strans, 1, 
       data, len, &trans->def_rtp, 
       pj_sockaddr_get_len(&trans->def_rtp));
     if (rc == PJ_SUCCESS)
      return len;

     return -1;
}

你可能感兴趣的:(ice开发流程(一))