这一篇,我们先讲一些跟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;

}

java ice开发_ice开发流程(一)相关推荐

  1. Java项目(一)--MyBatis实现OA系统项目实战(7)--开发多级审批流程

    开发多级审批流程 请假流程 设计约束 每一个请假单对应一个审批流程. 请假单创建后,按业务规则生成部门经理.总经理审批任务. 审批任务的经办人只能审批自己辖区内的请假申请. 所有审批任务"通 ...

  2. 【Java】ios应用开发证书申请流程

    [Java]ios应用开发证书申请流程 步骤 下面是生成钥匙串文件流程 打开mac电脑的启动台,找到钥匙串 这个就是在mac电脑上得到的钥匙串文件 下面是开发者平台申请流程 登录iOS开发者平台:[i ...

  3. python编写程序的一般步骤-Python:开发_基本流程

    Python开发--基本流程 开发: 开发运行在操作系统之上的软件 操作系统是运行在硬件上的另一种"软件" -编码 ASCII码 是最早美国用的标准信息交换码,把所有的字母的大小写 ...

  4. java ee web高级,Java EE Web高级开发案例

    核心提示:Java EE Web高级开发案例 内容简介:<Java EE Web高级开发案例>充分体现了高等职业教育的特点,突出了理论和实践的紧密结合,以充分掌握基本技术技能和必要的基本知 ...

  5. Java微信公众平台开发--番外篇,对GlobalConstants文件的补充

    转自:http://www.cuiyongzhi.com/post/63.html 之前发过一个[微信开发]系列性的文章,也引来了不少朋友观看和点评交流,可能我在写文章时有所疏忽,对部分文件给出的不是 ...

  6. camera (19)---Android 相机开发的基本流程

    [Android 相机]Android 相机开发的基本流程 https://blog.csdn.net/bluewindtalker/article/details/54563910 相机开发现在有2 ...

  7. java高级框架应用开发案例教程_Java高级框架应用开发案例教程:struts2+spring+hibernate PDF...

    资源名称:Java高级框架应用开发案例教程:struts2+spring+hibernate PDF 第1章 struts+spring+hibernate概述 1.1 框架概述 1.2 struts ...

  8. 广州牵引力教育详细分析学习Java能从事前端开发吗?

    相信很多从事互联网的人都知道,Java开发属于后端开发,那么也会有人问:学习Java能够从事前端开发吗?其实一般来说,只要你具备了一两年的后端开发经验转前端开发,相比那些只是通过编写页面进入前端的人会 ...

  9. APP开发的详细流程

    随着互联网的发展越来越多的企业要开发独立的APP进行业务拓展,那么APP开发的流程是怎么样的呢?需要注意什么呢,今天我们来探讨一下.北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作. 1 ...

最新文章

  1. python代码块所属关系的语法-天元高校邦数据科学通识课【Python基础语法】答案...
  2. JS获取鼠标位置,兼容IE FF
  3. Unchecked call to ‘mapoPair(PairFunction<T,K2,V2>)‘ as a member of raw type
  4. mysql支持事务的存储引擎_MySQL基础(三)【MySQL事务与存储引擎】
  5. Python3网络爬虫开发实战分析Ajax爬取今日头条街拍美图
  6. 常用的后端命令 【笔记】
  7. C#读取文本数据(按行读取)
  8. 学习笔记之ulimit
  9. 19年春第十五周学习
  10. sql附加服务器数据库文件,批量附加sql数据库
  11. log4j日志整合输出(slf4j+commonslog+log4j+jdklogger)
  12. ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon runn
  13. 【论文笔记】CondConv: Conditionally Parameterized Convolutions for Efficient Inference
  14. Wifi网络共享----Win8内置承载网络
  15. 网易严选应用的云原生演进实践
  16. 联想微型计算机设置从u盘启动,联想bios设置u盘启动教程
  17. 第三方软件测试 CNAS软件测试报告
  18. 【Postgresql】pg_show_plans插件
  19. iOS 冰与火之歌 - 利用 XPC 过 App 沙盒
  20. 蓝桥杯真题python B试题 C: 纸张尺寸

热门文章

  1. 圣科鲁兹 计算机专业,加州大学圣克鲁兹分校计算机工程硕士专业 将发明创新融入到工业中!...
  2. k-means用于图像的颜色聚类
  3. [poj1797] Heavy Transportation
  4. 搭建L2TP-***
  5. 读书笔记---阶级逆袭——三代人的卵巢彩票
  6. 从erp入门说到产业互联网络
  7. 九龙证券|二股东迎大变革,这只零售股连收4板
  8. Windows服务器如何防止黑客入侵的安全设置
  9. 三、nginx服务的nginx.conf的参数配置解析
  10. resure挽救笔记本系统和一些相关的操作记录