UAC(User Agent Client) 和 UAS(User Agent Server)
SIP協議采用Client/Server模型。每一個請求(Request)觸發服務器的一個操作;每個操作被稱為方法(Method);每個請求除了指明具體方法外,還攜帶了一系列的頭域(Header field),這些頭域攜帶了各種業務信息;除了頭域外,消息中還可以攜帶任意類型的消息體(Body),呼叫中最常使用的SDP信息就是通過消息體在Client和Server之間傳遞的。Server從接收到請求到處理完畢,要回復多個臨時響應(Response),和一個終結響應(Response),終結響應有且只有一個。
請求和他的所有響應構成一個事務(Transaction),一個完整的呼叫過程包含多個事務,比如呼叫建立和呼叫釋放就是
兩個相互獨立的事務。
用戶代理(User Agent)是發起或者接收呼叫的邏輯實體。
用戶代理客戶端-
UAC(User Agent Client),用於發起請求;
用戶代理服務器- UAS(User Agent Server),用於接收請求。 UAC/ UAS的划分是針對一個事務的。在一個呼叫中的多個事務里, UAC和 UAS的角色是可以互換的。例如在A和B的呼叫中,A向B發起呼叫,在呼叫建立的事務中,A是 UAC,B是 UAS;呼叫結束時,B先掛機,在呼叫釋放的事務中,B是 UAC,A是 UAS。換句話說,每個一般的UA都是 UAS和 UAC的結合體。
UA的實際物理形態有:IP Phone,SoftPhone,GateWay......
Proxy Server作為一個網絡邏輯實體代理客戶端轉發請求或者響應;同Proxy Server類似的還有一種設備是B2BUA,
顧名思義,就是背背的兩個UA組成的一個邏輯實體,它作為 UAS終結一個事務,同時作為 UAC發起另外一個事務。Proxy Server同B2BUA相比,Proxy Server是一個事務傳遞過程中的中間節點,而B2BUA將一個事務轉變成另一個事務。
在SIP組網中還包括Location Server、Registrar、Redirect Server,分別負責維護地址映射表,注冊管理,呼叫重定向。他們和Proxy Server 可以在同一台設備上也可以運行於不同的設備上。SIP Server是Proxy Server、Location Server、Registrar、Redirect Server的總稱。
SIP Server采用B2BUA模型。接受請求端為 UAS端,代理轉發或主動發起請求端為 UAC端,整個SIPServr為 UAC/ UAS的組合體。通過 UAC/ UAS之間消息交互完成會話的建立、改變、結束的階段。SIP Server協助網關交換媒體信息,
但不參與會話建立后媒體流傳輸。
用戶代理服務器- UAS(User Agent Server),用於接收請求。 UAC/ UAS的划分是針對一個事務的。在一個呼叫中的多個事務里, UAC和 UAS的角色是可以互換的。例如在A和B的呼叫中,A向B發起呼叫,在呼叫建立的事務中,A是 UAC,B是 UAS;呼叫結束時,B先掛機,在呼叫釋放的事務中,B是 UAC,A是 UAS。換句話說,每個一般的UA都是 UAS和 UAC的結合體。
UA的實際物理形態有:IP Phone,SoftPhone,GateWay......
Proxy Server作為一個網絡邏輯實體代理客戶端轉發請求或者響應;同Proxy Server類似的還有一種設備是B2BUA,
顧名思義,就是背背的兩個UA組成的一個邏輯實體,它作為 UAS終結一個事務,同時作為 UAC發起另外一個事務。Proxy Server同B2BUA相比,Proxy Server是一個事務傳遞過程中的中間節點,而B2BUA將一個事務轉變成另一個事務。
在SIP組網中還包括Location Server、Registrar、Redirect Server,分別負責維護地址映射表,注冊管理,呼叫重定向。他們和Proxy Server 可以在同一台設備上也可以運行於不同的設備上。SIP Server是Proxy Server、Location Server、Registrar、Redirect Server的總稱。
SIP Server采用B2BUA模型。接受請求端為 UAS端,代理轉發或主動發起請求端為 UAC端,整個SIPServr為 UAC/ UAS的組合體。通過 UAC/ UAS之間消息交互完成會話的建立、改變、結束的階段。SIP Server協助網關交換媒體信息,
但不參與會話建立后媒體流傳輸。
代碼 uac.c
#include <eXosip2/eXosip.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <netinet/in.h> //#include <winsock2.h> 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 *identity="sip:140@127.0.0.1"; //UAC1,端口是15060 char *registar="sip:133@127.0.0.1:15061"; //UAS,端口是15061 char *source_call="sip:140@127.0.0.1"; char *dest_call="sip:133@127.0.0.1:15061"; //identify和register這一組地址是和source和destination地址相同的 //在這個例子中,uac和uas通信,則source就是自己的地址,而目的地址就是uac1的地址 char command; 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(); 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("%c",&command); getchar(); switch(command) { case 'r': printf("This modal is not completed!\n"); break; 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; } //符合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(); 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 //(超時時間秒,超時時間毫秒) 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(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 with 200ok to INVITE (就是發送INVITE后,收到了對該INVITE的ACK) 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 'c': printf("This modal is not commpleted!\n"); break; case 's': //傳輸INFO方法 eXosip_call_build_info(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(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(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); }
代碼 uas.c
# include <eXosip2/eXosip.h> # include <stdio.h> # include <stdlib.h> # include <stdarg.h> # include <netinet/in.h> //# include <Winsock2.h> 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:140@127.0.0.1"; char *dest_call = "sip:133@127.0.0.1:15060";//client ip char command; char tmp[4096]; char localip[128]; 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); 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; eXosip_lock (); eXosip_call_send_answer (je->tid, 180, NULL); 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 { 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 (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"); } } }