初始化
初始化eXosip并准备传输层
使用eXosip时,第一步是初始化eXosip上下文和libosip库(解析器和状态机)。此外,您需要准备传输层,并选择UDP、TCP、TLS或DTL。
以下是基本的强制设置:
- 初始化osip跟踪(使用-DENABLE_trace编译此代码)
#include <eXosip2/eXosip.h>
eXosip_t *ctx;
int i;
int port=5060;
TRACE_INITIALIZE (6, NULL);
- 初始化eXosip
ctx =eXosip_malloc();
if(ctx==NULL)
return-1;
i=eXosip_init(ctx);
if(i!=0)
return-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, ®);
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, ®);
更新一个注册
您只需重新使用注册标识符:
int i;
eXosip_lock (ctx);
i = eXosip_register_build_register (ctx, rid, 1800, ®);
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, ®);
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);