java ice开发_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;
}
java ice开发_ice开发流程(一)相关推荐
- Java项目(一)--MyBatis实现OA系统项目实战(7)--开发多级审批流程
开发多级审批流程 请假流程 设计约束 每一个请假单对应一个审批流程. 请假单创建后,按业务规则生成部门经理.总经理审批任务. 审批任务的经办人只能审批自己辖区内的请假申请. 所有审批任务"通 ...
- 【Java】ios应用开发证书申请流程
[Java]ios应用开发证书申请流程 步骤 下面是生成钥匙串文件流程 打开mac电脑的启动台,找到钥匙串 这个就是在mac电脑上得到的钥匙串文件 下面是开发者平台申请流程 登录iOS开发者平台:[i ...
- python编写程序的一般步骤-Python:开发_基本流程
Python开发--基本流程 开发: 开发运行在操作系统之上的软件 操作系统是运行在硬件上的另一种"软件" -编码 ASCII码 是最早美国用的标准信息交换码,把所有的字母的大小写 ...
- java ee web高级,Java EE Web高级开发案例
核心提示:Java EE Web高级开发案例 内容简介:<Java EE Web高级开发案例>充分体现了高等职业教育的特点,突出了理论和实践的紧密结合,以充分掌握基本技术技能和必要的基本知 ...
- Java微信公众平台开发--番外篇,对GlobalConstants文件的补充
转自:http://www.cuiyongzhi.com/post/63.html 之前发过一个[微信开发]系列性的文章,也引来了不少朋友观看和点评交流,可能我在写文章时有所疏忽,对部分文件给出的不是 ...
- camera (19)---Android 相机开发的基本流程
[Android 相机]Android 相机开发的基本流程 https://blog.csdn.net/bluewindtalker/article/details/54563910 相机开发现在有2 ...
- java高级框架应用开发案例教程_Java高级框架应用开发案例教程:struts2+spring+hibernate PDF...
资源名称:Java高级框架应用开发案例教程:struts2+spring+hibernate PDF 第1章 struts+spring+hibernate概述 1.1 框架概述 1.2 struts ...
- 广州牵引力教育详细分析学习Java能从事前端开发吗?
相信很多从事互联网的人都知道,Java开发属于后端开发,那么也会有人问:学习Java能够从事前端开发吗?其实一般来说,只要你具备了一两年的后端开发经验转前端开发,相比那些只是通过编写页面进入前端的人会 ...
- APP开发的详细流程
随着互联网的发展越来越多的企业要开发独立的APP进行业务拓展,那么APP开发的流程是怎么样的呢?需要注意什么呢,今天我们来探讨一下.北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作. 1 ...
最新文章
- python代码块所属关系的语法-天元高校邦数据科学通识课【Python基础语法】答案...
- JS获取鼠标位置,兼容IE FF
- Unchecked call to ‘mapoPair(PairFunction<T,K2,V2>)‘ as a member of raw type
- mysql支持事务的存储引擎_MySQL基础(三)【MySQL事务与存储引擎】
- Python3网络爬虫开发实战分析Ajax爬取今日头条街拍美图
- 常用的后端命令 【笔记】
- C#读取文本数据(按行读取)
- 学习笔记之ulimit
- 19年春第十五周学习
- sql附加服务器数据库文件,批量附加sql数据库
- log4j日志整合输出(slf4j+commonslog+log4j+jdklogger)
- ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon runn
- 【论文笔记】CondConv: Conditionally Parameterized Convolutions for Efficient Inference
- Wifi网络共享----Win8内置承载网络
- 网易严选应用的云原生演进实践
- 联想微型计算机设置从u盘启动,联想bios设置u盘启动教程
- 第三方软件测试 CNAS软件测试报告
- 【Postgresql】pg_show_plans插件
- iOS 冰与火之歌 - 利用 XPC 过 App 沙盒
- 蓝桥杯真题python B试题 C: 纸张尺寸
热门文章
- 圣科鲁兹 计算机专业,加州大学圣克鲁兹分校计算机工程硕士专业 将发明创新融入到工业中!...
- k-means用于图像的颜色聚类
- [poj1797] Heavy Transportation
- 搭建L2TP-***
- 读书笔记---阶级逆袭——三代人的卵巢彩票
- 从erp入门说到产业互联网络
- 九龙证券|二股东迎大变革,这只零售股连收4板
- Windows服务器如何防止黑客入侵的安全设置
- 三、nginx服务的nginx.conf的参数配置解析
- resure挽救笔记本系统和一些相关的操作记录