剛開始學習eXosip,編譯了網上一個大牛的代碼,發現了一個問題。當UAC發送INVITE後,UAS收到後回發一個Ringing,他們的tid都為一,但是如果我把UAS關閉後再打開,UAC發送INVITE,UAS的tid為1,但是UAC收到的tid是2,這是為什麼呢?
UAS代碼:
int main (int argc, char *argv[])
{
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];
int pos = 0;
//初始化sip
i = eXosip_init ();
if (i != 0)
{
printf ("Can't initialize eXosip!\n");
return -1;
}
else
{
printf ("eXosip_init successfully!\n");
}
i = eXosip_listen_addr (IPPROTO_UDP, NULL, 15061, AF_INET, 0);
if (i != 0)
{
eXosip_quit ();
fprintf (stderr, "eXosip_listen_addr error!\nCouldn't initialize transport layer!\n");
}
for(;;)
{
//偵聽是否有消息到來
je = eXosip_event_wait (0,50);
//協議棧帶有此語句,具體作用未知
eXosip_lock ();
eXosip_default_action (je);
//printf("call_id is %d,dialog_id is %d \n", je->cid, je->did);
eXosip_automatic_refresh ();
eXosip_unlock ();
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 (je->tid, 200,&answer);
eXosip_message_send_answer (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 (je->did);
call_id = je->cid;
dialog_id = je->did;
printf("call_id is %d,dialog_id is %d \n", je->cid, je->did);
eXosip_lock ();
eXosip_call_send_answer (je->tid, 180, NULL);
printf("je->tid is %d \n",je->tid);
i = eXosip_call_build_answer (je->tid, 200, &answer);
printf("je->tid is %d \n", je->tid);
if (i != 0)
{
printf ("This request msg is invalid!Cann't response!\n");
eXosip_call_send_answer (je->tid, 400, NULL);
}
else
{
_snprintf_s (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 (je->tid, 200, answer);
printf ("send 200 over!\n");
}
eXosip_unlock ();
//顯示出在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 (je->tid, 200, &answer);
if (i != 0)
{
printf ("This request msg is invalid!Cann't response!\n");
eXosip_call_send_answer (je->tid, 400, NULL);
}
else
{
eXosip_call_send_answer (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 ();
i = eXosip_call_build_answer (je->tid, 200, &answer);
if (i == 0)
{
eXosip_call_send_answer (je->tid, 200, answer);
}
eXosip_unlock ();
{
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");
}
}
}
UAC的代碼:
#include
#include
#include
#include
int main(int argc, char *argv[])
{
struct eXosip_t *context_eXosip;
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 *source_call = "sip:[email protected]";//UAC1,端口是15060
char *dest_call = "sip:[email protected]:15061";//UAS,端口是15061
//在這個例子中,uac和uas通信,則source就是自己的地址,而目的地址就是uac1的地址
char command;
char tmp[4096];
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();
if (i != 0)
{
printf("Couldn't initialize eXosip!\n");
return -1;
}
else
{
printf("eXosip_init successfully!\n");
}
//綁定uac自己的端口15060,並進行端口監聽
i = eXosip_listen_addr(IPPROTO_UDP, NULL, 15060, AF_INET, 0);
if (i != 0)
{
eXosip_quit();
fprintf(stderr, "Couldn't initialize transport layer!\n");
return -1;
}
flag = 1;
while (flag)
{
//輸入命令
printf("Please input the command:\n");
scanf_s("%c", &command,1);
getchar();
switch (command)
{
case 'i'://INVITE,發起呼叫請求
i = eXosip_call_build_initial_invite(&invite, dest_call, source_call, NULL, "This is a call for conversation");
if (i != 0)
{
printf("Initial INVITE failed!\n");
break;
}
//格式vot必不可少,原因未知,估計是協議棧在傳輸時需要檢查的
_snprintf_s(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:htc\r\n"
"a=password:070011\r\n");
osip_message_set_body(invite, tmp, strlen(tmp));
osip_message_set_content_type(invite, "application/sdp");
eXosip_lock();
i = eXosip_call_send_initial_invite(invite); //invite SIP INVITE message to send
eXosip_unlock();
//發送了INVITE消息,等待應答
flag1 = 1;
while (flag1)
{
je = eXosip_event_wait(0, 200); //Wait for an eXosip event
//(超時時間秒,超時時間毫秒)
printf("je->tid is %d \n", je->tid);
if (je == NULL)
{
printf("No response or the time is over!\n");
break;
}
switch (je->type) //可能會到來的事件類型
{
case EXOSIP_CALL_PROCEEDING: //收到100 trying消息,表示請求正在處理中
printf("proceeding!\n");
break;
case EXOSIP_CALL_RINGING: //收到180 Ringing應答,表示接收到INVITE請求的UAS正在向被叫用戶振鈴
printf("ringing!\n");
printf("je->tid is %d \n", je->tid);
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(je->did, &ack);
eXosip_call_send_ack(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();
eXosip_call_terminate(call_id, dialog_id);
eXosip_unlock();
break;
case 's': //傳輸INFO方法
eXosip_call_build_info(dialog_id, &info);
_snprintf_s(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(dialog_id, info);
break;
case 'm':
//傳輸MESSAGE方法,也就是即時消息,和INFO方法相比,我認為主要區別是:
//MESSAGE不用建立連接,直接傳輸信息,而INFO消息必須在建立INVITE的基礎上傳輸
printf("the method : MESSAGE\n");
eXosip_message_build_request(&message, "MESSAGE", dest_call, source_call, NULL);
//內容,方法, to ,from ,route
_snprintf_s(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(message);
break;
case 'q':
eXosip_quit();
printf("Exit the setup!\n");
flag = 0;
break;
}
}
return(0);
}
呼叫過程未結束時異常斷開情況下,心跳和會話保活機制都保證不了100%的雙向會話狀態都是OK的。
我以前做的時候注冊注銷這塊也出現過類似的問題,當時是比較兩次收到的請求頭域中的callid是不是一樣的,
不一樣代表是兩個transaction,就得告訴底層將之前異常的transaction銷毀。保證網路兩端的回話是一一對應的