国标28181:libeXosip2应该如何使用

1059 篇文章 275 订阅

初始化

初始化eXosip并准备传输层

使用eXosip时,第一步是初始化eXosip上下文和libosip库(解析器和状态机)。此外,您需要准备传输层,并选择UDP、TCP、TLS或DTL。

以下是基本的强制设置:

  1. 初始化osip跟踪(使用-DENABLE_trace编译此代码)
#include <eXosip2/eXosip.h>
    eXosip_t *ctx;
    int i;
    int port=5060;
    TRACE_INITIALIZE (6, NULL);
  1. 初始化eXosip
ctx =eXosip_malloc();
if(ctx==NULL)
	return-1;
i=eXosip_init(ctx);
if(i!=0)
	return-1;
  1. 打开UDP套接字发送信号
#include <rpc/types.h>
   i =eXosip_listen_addr(ctx, IPPROTO_UDP, NULL, port, AF_INET, 0);
    if(i!=0)
    {
        eXosip_quit(ctx);
        fprintf (stderr,"could not initialize transport layer\n");
        return-1;
    }

你可以选择其他的传输层协议: 比如

    i =eXosip_listen_addr(ctx, IPPROTO_TCP, NULL, port, AF_INET, 0);  // TCP
    i =eXosip_listen_addr(ctx, IPPROTO_UDP, NULL, port, AF_INET, 0);  // UDP
    i =eXosip_listen_addr(ctx, IPPROTO_TCP, NULL, port, AF_INET, 1);  // TLS
    i =eXosip_listen_addr(ctx, IPPROTO_UDP, NULL, port, AF_INET, 1);  // DTLS

设置TLS

TLS可能需要特定的设置。TLS实际上引入了两个有趣的特性:

  • 使用证书和密钥,有助于信任/验证远程服务器。
  • 它还对数据进行加密,这样中间的人就无法读取SIP流量。

如果不需要服务器验证,TLS非常容易设置。您不需要配置任何证书、密钥或根证书。。。

以下是禁用证书验证的代码:

    int val=0;
    i =eXosip_set_option(ctx,EXOSIP_OPT_SET_TLS_VERIFY_CERTIFICATE, (void*)&val);

如果需要验证,还需要做一些工作。您需要什么取决于您的平台/操作系统。

(1)windows上的证书:

在windows上,exosip内置了对“windows证书存储”的支持。因此,您只需在官方的“Windows证书存储”中添加证书和密钥。

(2)macosx上的证书:

在macosx上,exosip内置了对证书存储的支持。

(3)其他平台上的证书

eXosip_tls_ctx_t tls_info;
memset(&tls_info, 0, sizeof(eXosip_tls_ctx_t));
snprintf(tls_info.client.cert, sizeof(tls_info.client.cert), "user-cert.crt");
snprintf(tls_info.client.priv_key, sizeof(tls_info.client.priv_key), "user-privkey.crt");
snprintf(tls_info.client.priv_key_pw, sizeof(tls_info.client.priv_key_pw), "password");
snprintf(tls_info.root_ca_cert, sizeof(tls_info.root_ca_cert), "cacert.crt");

i = eXosip_set_option (ctx, EXOSIP_OPT_SET_TLS_CERTIFICATES_INFO, (void*)&tls_info);

其他设置

eXosip2中有几个选项可以修改。然而,大多数默认值都是良好的值,如果您的sip服务配置正确,那么除了默认值之外,不需要太多设置。

EXOSIP_OPT_UDP_KEEP_ALIVE 1
EXOSIP_OPT_UDP_LEARN_PORT 2
EXOSIP_OPT_USE_RPORT 7
EXOSIP_OPT_SET_IPV4_FOR_GATEWAY 8
EXOSIP_OPT_ADD_DNS_CACHE 9
EXOSIP_OPT_DELETE_DNS_CACHE 10
EXOSIP_OPT_SET_IPV6_FOR_GATEWAY 12
EXOSIP_OPT_ADD_ACCOUNT_INFO 13
EXOSIP_OPT_DNS_CAPABILITIES 14
EXOSIP_OPT_SET_DSCP 15
EXOSIP_OPT_REGISTER_WITH_DATE 16
EXOSIP_OPT_SET_HEADER_USER_AGENT 17
EXOSIP_OPT_SET_TLS_VERIFY_CERTIFICATE 500
EXOSIP_OPT_SET_TLS_CERTIFICATES_INFO 501
EXOSIP_OPT_SET_TLS_CLIENT_CERTIFICATE_NAME 502
EXOSIP_OPT_SET_TLS_SERVER_CERTIFICATE_NAME 503

以下是适用于常规配置的基本设置:

    int val;
    eXosip_set_user_agent(ctx,"exosipdemo/0.0.0");
    val=17000;
    eXosip_set_option(ctx,EXOSIP_OPT_UDP_KEEP_ALIVE, (void*)&val);
    val=2;
    eXosip_set_option(ctx,EXOSIP_OPT_DNS_CAPABILITIES, (void*)&val);
    val=1;
    eXosip_set_option(ctx,EXOSIP_OPT_USE_RPORT, (void*)&val);
    val=26;
    eXosip_set_option(ctx,EXOSIP_OPT_SET_DSCP, (void*)&dscp_value);
    eXosip_set_option(ctx,EXOSIP_OPT_SET_IPV4_FOR_GATEWAY,"sip.antisip.com");

NAT 和Contact header

SIP最重要的功能是能够接收SIP请求。然而,理论上不可能猜测到我们正在创建的Contact header(联系人)应该包含什么。

不管eXosip2或任何SIP应用程序为什么提供了错误的值,大多数代理都会修复我们断开的联系人。SIP规范对于联系人验证的各种客户端和服务器行为不是很清楚。

在没有任何配置(NAT、STUN等)的情况下,您的代理应该能够找到如何联系您(通过现有连接)。如果不能(无论是什么原因),你可以尝试解决方法和选项。您可以选择:

  • 当您有多个localip时,此选项有助于exosip2检测localip(例如VPN和eth0)。这将有助于检测Via和Contact的localip。通常的参数是代理。(注意:由于DNS操作,方法可能会被阻止)
eXosip_set_option(ctx,EXOSIP_OPT_SET_IPV4_FOR_GATEWAY,"sip.antisip.com");
  • 伪装(masquerading):当发送第一个SIP请求(REGISTER? OPTIONS?)时,应答顶部的Via将包含“received”和“rport”参数:这些IP/端口正是您与代理的联系人头所需的IP/端口。STUN将检测到类似的IP/端口,但用于另一个目的地(STUN服务器)。因此,masquerading不是正确的方式。因此,向代理发送请求是需要检查via(received and rport)项并使用masquerading:
eXosip_masquerade_contact(ctx,"91.121.81.212", 10456);
  • EXOSIP_OPT_UDP_LEARN_PORT选项:如果您希望通过UDP自动“接收”和“rport”重复使用。使用以下代码,第二个REGISTER (身份验证后?)或者,第二个outgoing REQUEST将包含masqueraded Contact header。如果masquerade using STUN values,也应该使用它。
val=0;
eXosip_set_option(ctx,EXOSIP_OPT_USE_RPORT, &val);

如果您仍然存在NAT问题,请考虑使用TLS:断开的NAT有时会阻止SIP数据包,但通过加密,断开的NAT什么都做不了!

关于DNS(没用过)

eXosip2应使用C ares进行编译。这一点非常重要,因为c-ares为所有所需的SIP操作提供无阻塞、便携和全面支持。

默认情况下,SIP需要使用特定的DNS功能来发现SIP服务的IP地址。

  • NAPTR将发现SRV记录
  • 然后使用SRV记录接收主机列表。
  • DNS解析提供主机的IP地址。

有关完整信息,请查看rfc3263。txt:查找SIP服务器。

  • 如果要使用NAPTR
val=2;
eXosip_set_option(ctx,EXOSIP_OPT_DNS_CAPABILITIES, &val);

如果没有为您使用的服务设置NAPTR(这种情况在很多情况下都会发生),SRV记录或正常DNS将用作备用。它不应该使程序太慢。然而,在某些情况下禁用NAPTR仍然是有用的,因为在发送NAPTR请求时仍有一些DNS服务器保持沉默。在这种非常特殊的使用情况下,这可能会导致非常缓慢地退回到正常的DNS。。。

  • 如果不想使用NAPTR
val=0;
eXosip_set_option(ctx,EXOSIP_OPT_DNS_CAPABILITIES, &val);

处理eXosip2 事件 (eXosip_event_t)

eXosip_event 包含了如下信息:

  • rid:
  • tid:
  • cid
  • sid,:
  • nid:
  • request:
  • answer:
  • ack:

这些标识符在相关的eXosip2 API中被重新使用,以简化对上下文的控制。请求、应答和确认被完全复制,因此您可以在不锁定eXosip2上下文的情况下访问它们。

现在你必须处理eXosip事件。下面是一些从eXosip2库获取eXosip_event的代码。

注:*对于高级用户或更实时的应用程序,您也可以使用较低级别的API,以便在事件发生时在选择调用时被唤醒。

eXosip_event_t*evt;
for(;;)
{
evt =eXosip_event_wait(ctx, 0, 50);
eXosip_lock(ctx);
eXosip_automatic_action(ctx);
eXosip_unlock(ctx);
if(evt == NULL)
continue;
if(evt->type== EXOSIP_CALL_NEW)
{
....
....
}
elseif(evt->type==EXOSIP_CALL_ACK)
{
....
....
}
elseif(evt->type==EXOSIP_CALL_ANSWERED)
{
....
....
}
else.....
....
....
eXosip_event_free(evt);
}

每当发送一个SIP消息,都会收到一个事件。每个事件包含受影响事务的原始请求,以及触发有效事件的最后应答,

你可以从这些信息获取所有的头部,并存储起来,以方便后续的操作或者显示。

例如:当收到一个呼叫传输REFER请求时,你可以检索“refer-To”头部:

osip_header_t *referto_head = NULL;
i = osip_message_header_get_byname (evt->sip,"refer-to", 0, &referto_head);
if(referto_head == NULL || referto_head->hvalue == NULL)

下面是一些例子:

  • Answer 180 Ringing to an incoming INVITE:
if(evt->type== EXOSIP_CALL_NEW)
{
	eXosip_lock(ctx);
	eXosip_call_send_answer(ctx, evt->tid, 180, NULL);
	eXosip_unlock(ctx);
}
  • Answer 200 Ok to an incoming MESSAGE: (also check the attachment in evt->request!)
if(evt->type==EXOSIP_MESSAGE_NEW&& osip_strcasecmp (minfo.method,"MESSAGE") == 0) {
{
	osip_message_t *answer=NULL;
	int	i;
	eXosip_lock(ctx);
	i =eXosip_message_build_answer(ctx, evt->tid, 200, &answer);
	i =eXosip_message_send_answer(ctx, evt->tid, 200, answer);
	eXosip_unlock(ctx);
}
  • Handle incoming REFER within dialog (call transfer): fg
if(evt->type==EXOSIP_CALL_MESSAGE_NEW&& osip_strcasecmp (minfo.method,"REFER") == 0) {
	osip_header_t *refer_to = NULL;
	eXosip_lock(ctx);
	i =eXosip_call_build_answer(ctx, evt->tid, 202, &answer);
	i =eXosip_call_send_answer(ctx, evt->tid, 202, answer);
	i = osip_message_header_get_byname (evt->request,"refer-to", 0, &refer_to);
	if(i >= 0) {
		printf ("you must start call to: %s\n", refer_to->hvalue);
		...
	}
	else{
	}
	eXosip_call_terminate(ctx, evt->cid, evt->did, 486);
	eXosip_unlock(ctx);
}

如何发起、修改或终止通话

发起一个会话(Initiate a call)

要发起一个会话,通常需要几个头,eXosip2将使用这些头来构建默认的SIP INVITE请求。以下代码用于启动通话:

osip_message_t *invite;
    int cid;
    int i;

    i = eXosip_call_build_initial_invite (ctx, &invite, "<sip:to@antisip.com>",
                                          "<sip:from@antisip.com>",
                                          NULL, // optional route header
                                          "This is a call for a conversation");
    if (i != 0)
    {
        return -1;
    }

    osip_message_set_supported (invite, "100rel");

    {
        char tmp[4096];
        char localip[128];

        eXosip_guess_localip (ctx, AF_INET, localip, 128);
        snprintf (tmp, 4096,
                  "v=0\r\n"
                  "o=jack 0 0 IN IP4 %s\r\n"
                  "s=conversation\r\n"
                  "c=IN IP4 %s\r\n"
                  "t=0 0\r\n"
                  "m=audio %s RTP/AVP 0 8 101\r\n"
                  "a=rtpmap:0 PCMU/8000\r\n"
                  "a=rtpmap:8 PCMA/8000\r\n"
                  "a=rtpmap:101 telephone-event/8000\r\n"
                  "a=fmtp:101 0-11\r\n", localip, localip, port);
        osip_message_set_body (invite, tmp, strlen (tmp));
        osip_message_set_content_type (invite, "application/sdp");
    }


    eXosip_lock (ctx);
    cid = eXosip_call_send_initial_invite (ctx, invite);
    if (cid > 0)
    {
        eXosip_call_set_reference (ctx, i, reference);
    }
    eXosip_unlock (ctx);
    return i;
  • eXosip_call_build_initial_invite 为新call生成默认的SIP invite请求。必须接收一个SDP body告知RTP流的音频参数
  • eXosip2 API非常灵活,它允许您插入额外的头文件,例如“Supported:100rel”(宣布支持SIP扩展)。因此,您可以直接控制SIP请求的创建。
  • eXosip_call_send_initial_invite返回的元素是可用于发送取消的cid(调用标识符);如果是100 Trying,那么将会得到did,该标识符也将用于控制已建立的调用。
  • eXosip_call_set_reference也是一种将自己的一个上下文附加到调用的方法,以便在eXosip_事件中返回指针。

应答一个会话(answer a call)

当接收到一个call时应该怎么应答呢?

当你接收到一个SIP INVITE时你应该发送一个180 Ringing回应

eXosip_lock (ctx);
eXosip_call_send_answer (ctx, evt->tid, 180, NULL);
eXosip_unlock (ctx);

然后,当用户想要接听电话时,您需要发送200 ok并在SIP应答中插入SDP正文:

osip_message_t *answer = NULL;
 
eXosip_lock (ctx);
i = eXosip_call_build_answer (ctx, evt->tid, 200, &answer);
if (i != 0)
{
   eXosip_call_send_answer (ctx, evt->tid, 400, NULL);
}
else
{
   i = sdp_complete_200ok (evt->did, answer);
   if (i != 0)
   {
      osip_message_free (answer);
      eXosip_call_send_answer (ctx, evt->tid, 415, NULL);
   }
   else
      eXosip_call_send_answer (ctx, evt->tid, 200, answer);
}
eXosip_unlock (ctx);

注意:在上面的代码中,可以注意到,要发送对请求的响应,必须使用tid(事务标识符),而不是cid(调用标识符)或did(对话标识符)。

注2:要发送200ok,通常需要在答案中插入SDP正文,在此之前,需要协商支持的参数和编解码器(上面没有做)。创建SDP后,使用以下代码将其添加到答案中:

osip_message_set_body (answer, tmp, strlen (tmp));
osip_message_set_content_type (answer, "application/sdp");

结束一个会话(Terminate a Call)

简单的API,没什么好说的!您可以在需要时使用它:它会根据通话状态发送取消、否定应答或再见。

eXosip_lock (ctx);
eXosip_call_terminate (ctx, cid, did);
eXosip_unlock (ctx);

注意:如果没有收到100 Trying ,你不能停止通话。在这种情况下,你需要等待发送CANCEL 或BYE。。。这是根据rfc3261。

发送INFO, REFER, UPDATE, NOTIFY, OPTIONS请求

举个例子,下面是发送一个INFO 请求的代码,用于在信令层内发送带外dtmf。(不标准,但仍在某些系统上使用!)

osip_message_t *info;
char dtmf_body[1000];
int i;
 
eXosip_lock (ctx);
i = eXosip_call_build_info (ctx, evt->did, &info);
if (i == 0)
{
   snprintf (dtmf_body, 999, "Signal=%c\r\nDuration=250\r\n", c);
   osip_message_set_content_type (info, "application/dtmf-relay");
   osip_message_set_body (info, dtmf_body, strlen (dtmf_body));
   i = eXosip_call_send_request (ctx, evt->did, info);
}
eXosip_unlock (ctx);

发送其他请求

实际上,您可以使用eXosip2 API发送任何类型的其他请求。您会发现许多其他API可以构建任何类型的sip消息。使用osip API,您可以在这些消息中添加任何header或正文。

osip_message_t *message;
char body[1000];
int i;
 
eXosip_lock (ctx);
i = eXosip_call_build_request (ctx, evt->did, "PRIVATECOMMAND", &message);
if (i == 0)
{
   snprintf (body, 999, "room=1;light=on\r\nroom=2;light=off\r\n");
   osip_message_set_content_type (message, "application/antisip-domotic");
   osip_message_set_body (message, body, strlen (body));
 
   osip_message_set_header (invite, "P-MyCommand", "option=value");
 
   i = eXosip_call_send_request (ctx, evt->did, message);
}
eXosip_unlock (ctx);

如何发送或更新注册

发起一个注册

要开始注册,需要通过提供几个必需的头来构建默认注册请求。

即使在一个外部环境中,也可以启动任意多个注册。

osip_message_t *reg = NULL;
int rid;
int i;
 
eXosip_lock (ctx);
rid = eXosip_register_build_initial_register (ctx, "sip:me@sip.antisip.com", "sip.antisip.com", NULL, 1800, &reg);
if (rid < 0)
  {
    eXosip_unlock (ctx);
    return -1;
  }
 
osip_message_set_supported (reg, "100rel");
osip_message_set_supported (reg, "path");
 
i = eXosip_register_send_register (ctx, rid, reg);
eXosip_unlock (ctx);
return i;

eXosip_register_build_initial_register返回的元素是可用于更新注册的注册标识符。

在REGISTER中的Contact headers

设置密码

通常,您需要登录/密码才能访问服务!

eXosip可用于同时访问多个服务,因此,如果在同一实例中使用多个服务,则需要提供领域(标识服务)。

当您使用一个用户名与登录名相同的服务时,最简单的方法是:

eXosip_lock (ctx);
eXosip_add_authentication_info (ctx, login, login, passwd, NULL, NULL);
eXosip_unlock (ctx);

或者另外的方法:

eXosip_lock (ctx);
eXosip_add_authentication_info (ctx, login, login, passwd, NULL, "sip.antisip.com");
eXosip_add_authentication_info (ctx, from_username, login, passwd, NULL, "otherservice.com");
eXosip_unlock (ctx);

删除所有注册

sip规范不建议使用此功能,但它的存在是出于某些目的和原因!您可以在注册的联系人标题中发送“*”,要求立即删除特定SIP代理的所有活动注册:

rid = eXosip_register_build_initial_register (ctx, "sip:me@sip.antisip.com", "sip.antisip.com", "*", 1800, &reg);

更新一个注册

您只需重新使用注册标识符:

int i;
 
eXosip_lock (ctx);
i = eXosip_register_build_register (ctx, rid, 1800, &reg);
if (i < 0)
  {
    eXosip_unlock (ctx);
    return -1;
  }
 
eXosip_register_send_register (ctx, rid, reg);
eXosip_unlock (ctx);

结束注册

软电话在终止时应删除其在SIP服务器上的注册。为此,您必须发送一个注册请求,将expires头设置为值“0”。

与更新注册相同的代码用于0,而不是1800,用于过期延迟。

int i;
 
eXosip_lock (ctx);
i = eXosip_register_build_register (ctx, rid, 0, &reg);
if (i < 0)
  {
    eXosip_unlock (ctx);
    return -1;
  }
 
eXosip_register_send_register (ctx, rid, reg);
eXosip_unlock (ctx);

放弃注册上下文

如果需要在不发送过期时间为0的REGISTER 的情况下删除上下文,可以使用eXosip_REGISTER_remove释放内存。

int i;
 
eXosip_lock (ctx);
i = eXosip_register_remove (ctx, rid);
if (i == 0) {
}
eXosip_unlock (ctx);
  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值