參考文章:
https://www.cnblogs.com/baiduboy/p/6089197.html
http://blog.csdn.net/bikeytang/article/details/53448735
http://blog.csdn.net/dgyanyong/article/details/50818101
http://www.360doc.com/content/16/0510/09/9408846_557774783.shtml
一、平臺的搭建
1.下載庫文件
在http://ftp.gnu.org/gnu/osip/下載osip的5.0版本:libosip2-5.0.0.tar.gz
在http://www.antisip.com/download/exosip2/下載exosip的5.0版本:libexosip2-5.0.0.tar.gz
從連接https://c-ares.haxx.se/處下載:c-ares(我下載的是最新版本)
然後解壓統一放到一個文件夾下,修改文件夾的名字:
libexosip2-xxx 修改爲exosip
libosip2-xxx 修改爲osip
c-ares-xxx 修改爲c-ares
2.進入exosip文件夾目錄:exosip\platform\vsnet中,打開eXosip.sln,會自動進行加載
打開libcares.vcxproj,在裏面加入三個文件選項
ares_platform.h
ares_platform.c
ares_create_query.c
否則在後面例子中編譯時調用生成庫時會出現錯誤
error LNK2019:無法解析的外部符號_ares_getplatform,該符號在函數_get_DNS_Registry中被引用。
error LNK2019:無法解析的外部符號_ares_create_query,該符號在函數_ares_query中被引用。
3.點擊右側工程exosip,右鍵選擇屬性,配置屬性-> C/C++ -> 預處理器 ->預處理器定義中,去掉一些預定義(HAVE_OPENSSL_SSL_H,TSC_SUPPORT),不然在編譯的時候出現
error C1083: 無法打開包括文件: “openssl/opensslv.h”: No such file or directory
IntelliSense: 無法打開 源 文件 “tsc_socket_api.h”
IntelliSense: 未定義標識符 “tsc_config”
4.這個版本少eXrefer_api.c文件,從之前的版本中找到eXrefer_api.c,並添加到相應的位置
5.修改\osip\platform\vsnet目錄下面的osip2.def、osipparser2.def兩個文件,在文件的末尾導出這些函數,具體如下: 打開osip2.def文件,在最末尾加一行:
osip_transaction_set_naptr_record @138
打開osipparser2.def文件,在文件最後添加:
osip_realloc @416
osip_strcasestr @417
__osip_uri_escape_userinfo @418
6.Win7系統可能會出現如下編譯警告:
osip\include\osipparser2/internal.h(71): warning C4067: 預處理器指令後有意外標記 - 應輸入換行符
osip\include\osip2/internal.h(163): warning C4067: 預處理器指令後有意外標記 - 應輸入換行符
修改如下:
osip/include/osip2/internal.h和osip/include/osipparser2/internal.h
add start和add end中間部分爲添加代碼
#if (_MSC_VER >= 1700) && !defined(_USING_V110_SDK71_)
#include <winapifamily.h>
// add start 增加WINAPI_FAMILY_ONE_PARTITION定義
/* Macro to determine if only one partition is enabled from a set */
#define WINAPI_FAMILY_ONE_PARTITION(PartitionSet, Partition) ((WINAPI_FAMILY & PartitionSet) == Partition)
// add end
#endif
7.配置屬性->C/C++->代碼生成->運行庫->多線程調試 DLL (/MDd) 改爲 多線程調試 (/MTd)
否則出現錯誤 LIBCMTD.lib(dbgheap.obj) : error LNK2005: __CrtSetCheckCount 已經在 MSVCRTD.lib(MSVCR120D.dll) 中定義
8.點擊生成->生成解決方案進行編譯,編譯成功!由於eXosip依賴於libcares、osip2、osipparser2這3個庫。所以直接編譯eXosip即可
二、簡單例子的實現
1.首先編寫代碼UAS,建立工程UAS,源代碼UASmain.c
2.工程名-->右擊-->屬性-->配置屬性-->鏈接器 --> 輸入 -->附加依賴項:增加靜態庫引用:Dnsapi.lib;Iphlpapi.lib;ws2_32.lib;eXosip.lib;osip2.lib;osipparser2.lib;Qwave.lib;libcares.lib;delayimp.lib;
3、 工程名-->右擊-->屬性-->配置屬性-->C/C++ -->常規 -->附加包含目錄: 將osip和eXosip的頭文件include包含進來
4、 工程名-->右擊-->屬性-->配置屬性-->鏈接器 --> 常規 --> 加附庫目錄:將eXosip的庫包含進來,exosip\platform\vsnet\v120\Win32\Debug
5.工程名-->右擊-->屬性-->配置屬性-->C/C++ -->預處理器-->預處理器定義:添加_CRT_SECURE_NO_DEPRECATE,可以防止scanf的錯誤
代碼如下:
# include <eXosip2/eXosip.h>
# include <stdio.h>
# include <stdlib.h>
# include <Winsock2.h>
int main(int argc, char *argv[])
{
struct eXosip_t *context_eXosip = eXosip_malloc();
eXosip_event_t *je = NULL;
osip_message_t *ack = NULL;
osip_message_t *invite = NULL;
osip_message_t *answer = NULL;
sdp_message_t *remote_sdp = NULL;
int call_id, dialog_id;
int i, j;
//int id;
char *sour_call = "sip:[email protected]";
char *dest_call = "sip:[email protected]:15060";//client ip
//char command;
char tmp[4096];
//char localip[128];
int pos = 0;
//初始化sip
i = eXosip_init(context_eXosip);
if (i != 0)
{
printf("Can't initialize eXosip!\n");
return -1;
}
else
{
printf("eXosip_init successfully!\n");
}
i = eXosip_listen_addr(context_eXosip, IPPROTO_UDP, NULL, 15061, AF_INET, 0);
if (i != 0)
{
eXosip_quit(context_eXosip);
fprintf(stderr, "eXosip_listen_addr error!\nCouldn't initialize transport layer!\n");
}
for (;;)
{
//偵聽是否有消息到來
je = eXosip_event_wait(context_eXosip, 0, 50);
//協議棧帶有此語句,具體作用未知
eXosip_lock(context_eXosip);
eXosip_default_action(context_eXosip, je);
//eXosip_automatic_refresh(context_eXosip);
eXosip_unlock(context_eXosip);
if (je == NULL)//沒有接收到消息
continue;
// printf ("the cid is %s, did is %s/n", je->did, je->cid);
switch (je->type)
{
case EXOSIP_MESSAGE_NEW://新的消息到來
printf(" EXOSIP_MESSAGE_NEW!\n");
if (MSG_IS_MESSAGE(je->request))//如果接受到的消息類型是MESSAGE
{
{
osip_body_t *body;
osip_message_get_body(je->request, 0, &body);
printf("I get the msg is: %s\n", body->body);
//printf ("the cid is %s, did is %s/n", je->did, je->cid);
}
//按照規則,需要回復OK信息
eXosip_message_build_answer(context_eXosip, je->tid, 200, &answer);
eXosip_message_send_answer(context_eXosip, je->tid, 200, answer);
}
break;
case EXOSIP_CALL_INVITE:
//得到接收到消息的具體信息
printf("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n", je->request->req_uri->host,
je->request->req_uri->port, je->request->req_uri->username, je->request->req_uri->password);
//得到消息體,認爲該消息就是SDP格式.
remote_sdp = eXosip_get_remote_sdp(context_eXosip, je->did);
call_id = je->cid;
dialog_id = je->did;
eXosip_lock(context_eXosip);
eXosip_call_send_answer(context_eXosip, je->tid, 180, NULL);
i = eXosip_call_build_answer(context_eXosip, je->tid, 200, &answer);
if (i != 0)
{
printf("This request msg is invalid!Cann't response!\n");
eXosip_call_send_answer(context_eXosip, je->tid, 400, NULL);
}
else
{
/*snprintf(tmp, 4096,
"v=0\r\n"
"o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
"t=1 10\r\n"
"a=username:rainfish\r\n"
"a=password:123\r\n");
*/
//設置回覆的SDP消息體,下一步計劃分析消息體
//沒有分析消息體,直接回復原來的消息,這一塊做的不好。
osip_message_set_body(answer, tmp, strlen(tmp));
osip_message_set_content_type(answer, "application/sdp");
eXosip_call_send_answer(context_eXosip, je->tid, 200, answer);
printf("send 200 over!\n");
}
eXosip_unlock(context_eXosip);
//顯示出在sdp消息體中的attribute 的內容,裏面計劃存放我們的信息
printf("the INFO is :\n");
while (!osip_list_eol(&(remote_sdp->a_attributes), pos))
{
sdp_attribute_t *at;
at = (sdp_attribute_t *)osip_list_get(&remote_sdp->a_attributes, pos);
printf("%s : %s\n", at->a_att_field, at->a_att_value);//這裏解釋了爲什麼在SDP消息體中屬性a裏面存放必須是兩列
pos++;
}
break;
case EXOSIP_CALL_ACK:
printf("ACK recieved!\n");
// printf ("the cid is %s, did is %s/n", je->did, je->cid);
break;
case EXOSIP_CALL_CLOSED:
printf("the remote hold the session!\n");
// eXosip_call_build_ack(dialog_id, &ack);
//eXosip_call_send_ack(dialog_id, ack);
i = eXosip_call_build_answer(context_eXosip, je->tid, 200, &answer);
if (i != 0)
{
printf("This request msg is invalid!Cann't response!\n");
eXosip_call_send_answer(context_eXosip, je->tid, 400, NULL);
}
else
{
eXosip_call_send_answer(context_eXosip, je->tid, 200, answer);
printf("bye send 200 over!\n");
}
break;
case EXOSIP_CALL_MESSAGE_NEW://至於該類型和EXOSIP_MESSAGE_NEW的區別,源代碼這麼解釋的
/*
// request related events within calls (except INVITE)
EXOSIP_CALL_MESSAGE_NEW, < announce new incoming request.
// response received for request outside calls
EXOSIP_MESSAGE_NEW, < announce new incoming request.
我也不是很明白,理解是:EXOSIP_CALL_MESSAGE_NEW是一個呼叫中的新的消息到來,比如ring trying都算,所以在接受到後必須判斷
該消息類型,EXOSIP_MESSAGE_NEW而是表示不是呼叫內的消息到來。
該解釋有不妥地方,僅供參考。
*/
printf(" EXOSIP_CALL_MESSAGE_NEW\n");
if (MSG_IS_INFO(je->request)) //如果傳輸的是INFO方法
{
eXosip_lock(context_eXosip);
i = eXosip_call_build_answer(context_eXosip, je->tid, 200, &answer);
if (i == 0)
{
eXosip_call_send_answer(context_eXosip, je->tid, 200, answer);
}
eXosip_unlock(context_eXosip);
{
osip_body_t *body;
osip_message_get_body(je->request, 0, &body);
printf("the body is %s\n", body->body);
}
}
break;
default:
printf("Could not parse the msg!\n");
}
}
}
6.編寫UAC,建立工程UAC,源代碼UACmian.c
設置同上面的UAS一樣
代碼如下:
#include <eXosip2/eXosip.h>
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
int main(int argc, char *argv[])
{
struct eXosip_t *context_eXosip = eXosip_malloc();
eXosip_event_t *je;
osip_message_t *reg = NULL;
osip_message_t *invite = NULL;
osip_message_t *ack = NULL;
osip_message_t *info = NULL;
osip_message_t *message = NULL;
int call_id, dialog_id;
int i, flag;
int flag1 = 1;
char *identity = "sip:[email protected]"; //UAC1,端口是15060
char *registar = "sip:[email protected]:15061"; //UAS,端口是15061
char *source_call = "sip:[email protected]";
char *dest_call = "sip:[email protected]:15061";
//identify和register這一組地址是和source和destination地址相同的
//在這個例子中,uac和uas通信,則source就是自己的地址,而目的地址就是uac1的地址
char tmp[4096];
printf("r 向服務器註冊\n\n");
printf("c 取消註冊\n\n");
printf("i 發起呼叫請求\n\n");
printf("h 掛斷\n\n");
printf("q 推出程序\n\n");
printf("s 執行方法INFO\n\n");
printf("m 執行方法MESSAGE\n\n");
//初始化
i = eXosip_init(context_eXosip);
if (i != 0)
{
printf("Couldn't initialize eXosip!\n");
return -1;
}
else
{
printf("eXosip_init successfully!\n");
}
//綁定uac自己的端口15060,並進行端口監聽
i = eXosip_listen_addr(context_eXosip, IPPROTO_UDP, NULL, 15060, AF_INET, 0);
if (i != 0)
{
eXosip_quit(context_eXosip);
return -1;
}
flag = 1;
char command;
while (flag)
{
//輸入命令
printf("Please input the command:\n");
scanf("%c", &command);
getchar();
printf("command = %c\n", command);
switch (command)
{
case 'r':
printf("This modal is not completed!\n");
break;
case 'i'://INVITE,發起呼叫請求
i = eXosip_call_build_initial_invite(context_eXosip, &invite, dest_call, source_call, NULL, "This is a call for conversation");
if (i != 0)
{
printf("Initial INVITE failed!\n");
break;
}
//符合SDP格式,其中屬性a是自定義格式,也就是說可以存放自己的信息,
//但是只能有兩列,比如帳戶信息
//但是經過測試,格式vot必不可少,原因未知,估計是協議棧在傳輸時需要檢查的
_snprintf(tmp, 4096,
"v=0\r\n"
"o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
"t=1 10\r\n"
"a=username:rainfish\r\n"
"a=password:123\r\n");
osip_message_set_body(invite, tmp, strlen(tmp));
osip_message_set_content_type(invite, "application/sdp");
eXosip_lock(context_eXosip);
i = eXosip_call_send_initial_invite(context_eXosip, invite); //invite SIP INVITE message to send
eXosip_unlock(context_eXosip);
//發送了INVITE消息,等待應答
flag1 = 1;
while (flag1)
{
je = eXosip_event_wait(context_eXosip, 0, 200); //Wait for an eXosip event
//(超時時間秒,超時時間毫秒)
if (je == NULL)
{
printf("No response or the time is over!\n");
break;
}
switch (je->type) //可能會到來的事件類型
{
case EXOSIP_CALL_INVITE: //收到一個INVITE請求
printf("a new invite received!\n");
break;
case EXOSIP_CALL_PROCEEDING: //收到100 trying消息,表示請求正在處理中
printf("proceeding!\n");
break;
case EXOSIP_CALL_RINGING: //收到180 Ringing應答,表示接收到INVITE請求的UAS正在向被叫用戶振鈴
printf("ringing!\n");
printf("call_id is %d,dialog_id is %d \n", je->cid, je->did);
break;
case EXOSIP_CALL_ANSWERED: //收到200 OK,表示請求已經被成功接受,用戶應答
printf("ok!connected!\n");
call_id = je->cid;
dialog_id = je->did;
printf("call_id is %d,dialog_id is %d \n", je->cid, je->did);
//回送ack應答消息
eXosip_call_build_ack(context_eXosip, je->did, &ack);
eXosip_call_send_ack(context_eXosip, je->did, ack);
flag1 = 0; //推出While循環
break;
case EXOSIP_CALL_CLOSED: //a BYE was received for this call
printf("the other sid closed!\n");
break;
case EXOSIP_CALL_ACK: //ACK received for 200ok to INVITE
printf("ACK received!\n");
break;
default: //收到其他應答
printf("other response!\n");
break;
}
eXosip_event_free(je); //Free ressource in an eXosip event
}
break;
case 'h': //掛斷
printf("Holded!\n");
eXosip_lock(context_eXosip);
eXosip_call_terminate(context_eXosip, call_id, dialog_id);
eXosip_unlock(context_eXosip);
break;
case 'c':
printf("This modal is not commpleted!\n");
break;
case 's': //傳輸INFO方法
eXosip_call_build_info(context_eXosip, dialog_id, &info);
_snprintf(tmp, 4096, "\nThis is a sip message(Method:INFO)");
osip_message_set_body(info, tmp, strlen(tmp));
//格式可以任意設定,text/plain代表文本信息;
osip_message_set_content_type(info, "text/plain");
eXosip_call_send_request(context_eXosip, dialog_id, info);
break;
case 'm':
//傳輸MESSAGE方法,也就是即時消息,和INFO方法相比,我認爲主要區別是:
//MESSAGE不用建立連接,直接傳輸信息,而INFO消息必須在建立INVITE的基礎上傳輸
printf("the method : MESSAGE\n");
eXosip_message_build_request(context_eXosip, &message, "MESSAGE", dest_call, source_call, NULL);
//內容,方法, to ,from ,route
_snprintf(tmp, 4096, "This is a sip message(Method:MESSAGE)");
osip_message_set_body(message, tmp, strlen(tmp));
//假設格式是xml
osip_message_set_content_type(message, "text/xml");
eXosip_message_send_request(context_eXosip, message);
break;
case 'q':
eXosip_quit(context_eXosip);
printf("Exit the setup!\n");
flag = 0;
break;
}
}
return(0);
}
兩個工程同時運行,點擊調試-->開始調試,會出現下面現象: