目錄
一 JAIN SIP API
1 摘要
2 關(guān)于JAIN SIP API
3 API概述
3.1 maven坐標(biāo)
3.2 類(lèi)/接口
3.3 Message接口
3.4 Request接口
3.5 Response接口
4 即時(shí)通訊程序
4.1 TextClient代碼概述
4.2 Message Processor
4.3 SIP協(xié)議棧
4.4 發(fā)送SIP請(qǐng)求
4.5 發(fā)送會(huì)話消息
4.6 接收SIP響應(yīng)
4.7 接收SIP請(qǐng)求
4.8 處理錯(cuò)誤
4.9 小節(jié)
二 GB28181SIP服務(wù)器——MSH
1 簡(jiǎn)介
2 GB28181
2.1 注冊(cè)
2.2 保活
2.2.1 命令流程
2.2.2 協(xié)議接口
2.3 MSH代碼概述
2.3.1 創(chuàng)建springboot項(xiàng)目
2.3.2 SIP協(xié)議棧
2.3.3 接收SIP請(qǐng)求響應(yīng)
2.3.4 處理SIP請(qǐng)求
2.3.5 發(fā)送SIP請(qǐng)求
2.3.6 IPC接入
2.3.7 抓包與流程分析
2.3.7.1 注冊(cè)
2.3.7.2 ?;?/p>
3 小結(jié)
3.1 SIP服務(wù)器
3.2 WEB服務(wù)器
一 JAIN SIP API
1 摘要
這篇文章展示了基于Java SE如何創(chuàng)建客戶(hù)端側(cè)的SIP應(yīng)用。JAIN SIP API是一個(gè)強(qiáng)大的“SIP協(xié)議棧”。本文將通過(guò)一個(gè)簡(jiǎn)單的即時(shí)通訊程序以及一個(gè)GB28181協(xié)議的簡(jiǎn)單應(yīng)用程序,詳細(xì)的分析該技術(shù)。
2 關(guān)于JAIN SIP API
Java api for Integrated Networks (JAIN)是一個(gè)JCP工作組所管理的電信標(biāo)準(zhǔn),Session Initiation Protocol(SIP)是一種標(biāo)準(zhǔn)的通信協(xié)議,將Java和SIP結(jié)合在一起,就得到了JAIN SIP API,這是一個(gè)標(biāo)準(zhǔn)的、功能強(qiáng)大的電信API。這個(gè)API通常用于客戶(hù)端應(yīng)用程序開(kāi)發(fā)。其他基于容器的技術(shù),如SIP Servlet API(參見(jiàn)BEA WebLogic SIP Server的例子),更適合于服務(wù)器端開(kāi)發(fā),但是在GB28181協(xié)議應(yīng)用程序中我們也采用該API用作SIP服務(wù)器的開(kāi)發(fā)實(shí)現(xiàn)IPC與聯(lián)網(wǎng)平臺(tái)的信令交互。
3 API概述
3.1 maven坐標(biāo)
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId>
<version>1.3.0-91</version>
</dependency>
3.2 類(lèi)/接口
下面概述了JAIN SIP API實(shí)現(xiàn)中的主要類(lèi)和接口。
Class / Interface | 描述 |
---|---|
SipFactory / AddressFactory / HeaderFactory / MessageFactory | 工廠類(lèi)來(lái)創(chuàng)建系統(tǒng)的各種對(duì)象。它們返回聲明了標(biāo)準(zhǔn)接口的對(duì)象。 |
SipStack | 您需要的第一個(gè)接口,用于創(chuàng)建ListeningPoints和SipProviders。 |
ListeningPoint | 這個(gè)接口封裝了一個(gè)傳輸/端口對(duì)(例如UDP/5060)。 |
SipProvider | 這個(gè)接口用來(lái)發(fā)送SIP消息。您還可以使用此接口為傳入的SIP消息注冊(cè)一個(gè)監(jiān)聽(tīng)器。參見(jiàn)下面的SipListener。 |
SipListener | 您必須實(shí)現(xiàn)此接口以允許接收傳入的SIP消息。 |
RequestEvent / ResponseEvent | 表示傳入的SIP請(qǐng)求、響應(yīng)。傳遞給SipListener進(jìn)行處理。分別包含一個(gè)Request或Response對(duì)象。 |
TimeoutEvent | 表示傳出請(qǐng)求沒(méi)有回復(fù)時(shí)的失敗條件。傳遞給SipListener進(jìn)行處理。 |
IOExceptionEvent | 表示在發(fā)送外發(fā)請(qǐng)求時(shí)出現(xiàn)輸入/輸出問(wèn)題時(shí)的失敗條件。傳遞給SipListener進(jìn)行處理。 |
Request / Response | 表示SIP請(qǐng)求、響應(yīng)。兩者都是Message接口的子接口。它們提供對(duì)報(bào)頭、內(nèi)容和SIP消息的其他部分的訪問(wèn)。 |
Dialog | 此接口的對(duì)象封裝了一個(gè)SIP對(duì)話框。(提醒:在對(duì)話框中,所有消息都與同一個(gè)調(diào)用相關(guān);對(duì)話通常以INVITE開(kāi)始,以BYE結(jié)束。 |
ClientTransaction / ServerTransaction | 封裝SIP事務(wù)。(提醒:事務(wù)以請(qǐng)求開(kāi)始,以最終響應(yīng)結(jié)束。事務(wù)通常存在于對(duì)話框中。) |
3.3 Message接口
Message接口是SIP消息的基本接口,下面是可用方法的概述。
Method | 描述 |
---|---|
void addHeader(Header) void setHeader(Header) | 將報(bào)頭字段設(shè)置為SIP消息。第一種方法可用于可重復(fù)或具有多個(gè)值的標(biāo)頭,如Contact標(biāo)頭。第二個(gè)方法刪除該類(lèi)型的現(xiàn)有頭,然后添加單個(gè)頭值。 |
void removeHeader(Header) | 刪除此類(lèi)型的現(xiàn)有標(biāo)頭。 |
ListIterator getHeaderNames() | 返回所有頭文件名稱(chēng)。 |
ListIterator getUnrecognizedHeaders() | 返回非標(biāo)準(zhǔn)報(bào)頭類(lèi)型的報(bào)頭名稱(chēng)。 |
Header getHeader(String) ListIterator getHeaders(String) | ListIterator getHeaders(字符串) 特定頭的getter。第二種形式返回可重復(fù)標(biāo)頭的所有值,或具有多個(gè)值的標(biāo)頭,如Contact標(biāo)頭。 |
void setContent(Object, ContentTypeHeader) | 設(shè)置消息的有效負(fù)載以及Content-Type報(bào)頭。如果類(lèi)型是字符串,Content-Length也被設(shè)置,否則使用void setContentLength(ContentLengthHeader)。 |
byte [] getRawContent() Object getContent() | 檢索消息的有效負(fù)載。 |
void removeContent() | 清空有效負(fù)載。 |
void setContentLength(ContentLengthHeader) ContentLengthHeader getContentLength() void setContentLanguage(ContentLanguageHeader) ContentLanguageHeader getContentLanguage() void setContentEncoding(ContentEncodingHeader) ContentEncodingHeader getContentEncoding() void setContentDisposition(ContentDispositionHeader) ContentDispositionHeader getContentDisposition() | 與有效負(fù)載相關(guān)的特殊頭訪問(wèn)器。很少使用。 |
void setExpires(ExpiresHeader) ExpiresHeader getExpires() | 管理Expires報(bào)頭。 |
void setSipVersion(String) String getSipVersion() | 字符串getSipVersion () SIP版本元素的訪問(wèn)器。很少使用,默認(rèn)為SIP/2.0。 |
Object clone() | 創(chuàng)建消息的副本。很少使用。 |
3.4 Request接口
Message接口的子接口
Method | 描述 |
---|---|
String getMethod() void setMethod(String) | 方法元素的訪問(wèn)器。可以是任何SIP方法,包括請(qǐng)求接口常量中的方法:ACK、BYE、CANCEL、INVITE、OPTIONS、REGISTER、NOTIFY、SUBSCRIBE、MESSAGE、REFER、INFO、PRACK和UPDATE。 |
URI getRequestURI() void setRequestURI(URI) | 請(qǐng)求URI的訪問(wèn)器,這是SIP請(qǐng)求的第一行。通常,這是SipURI的一個(gè)實(shí)例。 |
3.5 Response接口
Message接口的子接口。
Method | 描述 |
---|---|
void setStatusCode() int getStatusCode() | 狀態(tài)代碼的訪問(wèn)器。這可以是任何SIP狀態(tài)碼,包括Response接口的常量成員中的狀態(tài)碼。這里有一些:RINGING (180), OK (200), BAD_REQUEST(400),等等。 |
void setReasonPhrase(String) String getReasonPhrase() | 訪問(wèn)器,用于人類(lèi)可讀的狀態(tài)代碼解釋。 |
4 即時(shí)通訊程序
TextClient是一個(gè)即時(shí)消息傳遞應(yīng)用程序,可以通過(guò)SIP協(xié)議發(fā)送和接收文本消息。此應(yīng)用程序的一個(gè)實(shí)例可以向另一個(gè)實(shí)例發(fā)送消息,但從理論上講,此客戶(hù)機(jī)可用于向其他類(lèi)型的SIP即時(shí)消息傳遞客戶(hù)機(jī),甚至SIP服務(wù)器應(yīng)用程序發(fā)送消息。如下圖所示,SIP客戶(hù)端yrz向另一個(gè)SIP客戶(hù)端yz發(fā)送了一條”我是yrz2023年4月18日13:46:22“的消息,隨后SIP客戶(hù)端yz回復(fù)了一條”yz收到2023年4月18日13:46:22“的消息。
4.1 TextClient代碼概述
兩個(gè)類(lèi)和一個(gè)接口組成了整個(gè)TextClient代碼。下表介紹:
Class / Interface | 描述 |
---|---|
TextClient | 主類(lèi),包含應(yīng)用程序小部件的Swing窗口。 |
SipLayer | 它負(fù)責(zé)所有SIP通信。它由TextClient類(lèi)實(shí)例化,并通過(guò)MessageProcessor接口回調(diào)它。 |
MessageProcessor | 回調(diào)接口(觀察者模式),用于將SipLayer與其容器解耦。 |
4.2 Message Processor
創(chuàng)建MessageProcessor接口,將SIP層與GUI層分離。TextClient類(lèi)實(shí)現(xiàn)該接口,其構(gòu)造函數(shù)將SipLayer對(duì)象作為參數(shù),您將能夠使用SipLayer對(duì)象將信息發(fā)送回GUI。
public interface MessageProcessor
{
// 請(qǐng)求回調(diào)方法
void processMessage(String sender, String message);
// 請(qǐng)求錯(cuò)誤回調(diào)方法
void processError(String errorMessage);
// 響應(yīng)回調(diào)方法
void processInfo(String infoMessage);
}
4.3 SIP協(xié)議棧
讓我們開(kāi)始編寫(xiě)SipLayer類(lèi)。TextClient必須能夠接收來(lái)自其他SIP端點(diǎn)的異步消息。這個(gè)類(lèi)實(shí)現(xiàn)了SipListener接口來(lái)處理傳入的消息:
public class SipLayer implements SipListener {
...
}
SipListener接口方法如下:
public interface SipListener extends EventListener {
void processRequest(RequestEvent var1);
void processResponse(ResponseEvent var1);
void processTimeout(TimeoutEvent var1);
void processIOException(IOExceptionEvent var1);
void processTransactionTerminated(TransactionTerminatedEvent var1);
void processDialogTerminated(DialogTerminatedEvent var1);
}
在本例中,用于處理傳入消息的最重要的方法顯然是processRequest()和processResponse()。接下來(lái)是存儲(chǔ)稍后需要的對(duì)象的兩個(gè)字段:username和messageProcessor,這些與SIP API沒(méi)有直接關(guān)系,但是在本例中需要它們。第一個(gè)是前面討論過(guò)的MessageProcessor對(duì)象,用于回調(diào)方法將消息發(fā)回給GUI,username用于隨時(shí)保留用戶(hù)名,這兩個(gè)字段有g(shù)etter和setter方法。
private MessageProcessor messageProcessor;
private String username;
接下來(lái)是構(gòu)造函數(shù),一種啟動(dòng)JAIN SIP API的經(jīng)典方法——建立一堆以后會(huì)有用的對(duì)象(工廠和SIP協(xié)議棧實(shí)例),TextClient就是采用的這種方法。
private SipStack sipStack;
private SipFactory sipFactory;
private AddressFactory addressFactory;
private HeaderFactory headerFactory;
private MessageFactory messageFactory;
private SipProvider sipProvider;
public SipLayer(String username, String ip, int port) throws PeerUnavailableException,
TransportNotSupportedException,InvalidArgumentException, ObjectInUseException, TooManyListenersException {
setUsername(username);
sipFactory = SipFactory.getInstance();
sipFactory.setPathName("gov.nist");
Properties properties = new Properties();
properties.setProperty("javax.sip.STACK_NAME",
"TextClient");
properties.setProperty("javax.sip.IP_ADDRESS",
ip);
sipStack = sipFactory.createSipStack(properties);
headerFactory = sipFactory.createHeaderFactory();
addressFactory = sipFactory.createAddressFactory();
messageFactory = sipFactory.createMessageFactory();
...
SipFactory用于實(shí)例化SipStack實(shí)現(xiàn),但由于可能有多個(gè)實(shí)現(xiàn),因此必須通過(guò)setPathName()方法命名您想要的那個(gè)實(shí)現(xiàn)。名稱(chēng)“gov.nist”表示您獲得的SIP堆棧。
SipStack對(duì)象具有許多屬性。至少,您必須設(shè)置堆棧名稱(chēng)。所有其他屬性都是可選的。在這里,我設(shè)置了一個(gè)由堆棧使用的IP地址,用于一臺(tái)計(jì)算機(jī)有多個(gè)IP地址的情況。注意,這里有標(biāo)準(zhǔn)屬性(所有SIP API實(shí)現(xiàn)都必須支持)和非標(biāo)準(zhǔn)屬性(依賴(lài)于實(shí)現(xiàn))。
下一步是創(chuàng)建一對(duì)ListeningPoint和SipProvider對(duì)象。這些對(duì)象提供了發(fā)送和接收消息的通信功能。TCP有一組,UDP有一組。這也是你選擇SipLayer作為傳入SIP消息的監(jiān)聽(tīng)器的地方:
...
ListeningPoint tcp = sipStack.createListeningPoint(port, "tcp");
ListeningPoint udp = sipStack.createListeningPoint(port, "udp");
sipProvider = sipStack.createSipProvider(tcp);
sipProvider.addSipListener(this);
sipProvider = sipStack.createSipProvider(udp);
sipProvider.addSipListener(this);
}
構(gòu)造函數(shù)就是這樣結(jié)束的。您已經(jīng)使用JAIN SIP API創(chuàng)建了一個(gè)SipStack實(shí)例、一堆工廠、兩個(gè)listeningpoint和一個(gè)SipProvider。這些對(duì)象將在接下來(lái)的方法中用于發(fā)送和接收消息。
4.4 發(fā)送SIP請(qǐng)求
現(xiàn)在讓我們編寫(xiě)一個(gè)使用JAIN SIP API發(fā)送SIP消息的方法,在此之前你必須非常了解SIP協(xié)議。SIP API是相當(dāng)?shù)图?jí)的抽象,在大多數(shù)情況下,不使用默認(rèn)值或隱藏頭、請(qǐng)求uri或SIP消息的內(nèi)容。這種設(shè)計(jì)的優(yōu)點(diǎn)是您可以完全控制SIP消息所包含的內(nèi)容。
發(fā)送一個(gè)SIP請(qǐng)求大致分為四個(gè)部分:
-
創(chuàng)建主要元素
-
創(chuàng)建消息
-
完整的消息
-
發(fā)送消息
使用JAIN SIP API構(gòu)造消息最少需要以下主要SIP元素:
-
請(qǐng)求URI
-
方法
-
通話身份頭
-
CSeq頭
-
從標(biāo)題
-
Via報(bào)頭數(shù)組
- Max-forwards頭
下面的代碼片段創(chuàng)建了所有這些元素:
public void sendMessage(String to, String message) throws
ParseException, InvalidArgumentException, SipException {
SipURI from = addressFactory.createSipURI(getUsername(),
getHost() + ":" + getPort());
Address fromNameAddress = addressFactory.createAddress(from);
fromNameAddress.setDisplayName(getUsername());
FromHeader fromHeader =
headerFactory.createFromHeader(fromNameAddress,
"textclientv1.0");
String username = to.substring(to.indexOf(":")+1, to.indexOf("@"));
String address = to.substring(to.indexOf("@")+1);
SipURI toAddress =
addressFactory.createSipURI(username, address);
Address toNameAddress = addressFactory.createAddress(toAddress);
toNameAddress.setDisplayName(username);
ToHeader toHeader =
headerFactory.createToHeader(toNameAddress, null);
SipURI requestURI =
addressFactory.createSipURI(username, address);
requestURI.setTransportParam("udp");
ArrayList viaHeaders = new ArrayList();
ViaHeader viaHeader =
headerFactory.createViaHeader(
getHost(),
getPort(),
"udp",
null);
viaHeaders.add(viaHeader);
CallIdHeader callIdHeader = sipProvider.getNewCallId();
CSeqHeader cSeqHeader =
headerFactory.createCSeqHeader(1, Request.MESSAGE);
MaxForwardsHeader maxForwards =
headerFactory.createMaxForwardsHeader(70);
...
我使用在構(gòu)造函數(shù)HeaderFactory和AddressFactory中創(chuàng)建的工廠來(lái)實(shí)例化這些元素。接下來(lái)讓我們實(shí)例化實(shí)際的SIP消息本身,傳入之前創(chuàng)建的所有元素:
Request request = messageFactory.createRequest(
requestURI, Request.MESSAGE, callIdHeader, cSeqHeader,
fromHeader, toHeader, viaHeaders, maxForwards);
...
注意,這一步使用了MessageFactory。然后,讓我們向消息添加其他元素:聯(lián)系人標(biāo)頭和消息的內(nèi)容(有效負(fù)載),也可以添加自定義標(biāo)題。
SipURI contactURI = addressFactory.createSipURI(getUsername(),
getHost());
contactURI.setPort(getPort());
Address contactAddress = addressFactory.createAddress(contactURI);
contactAddress.setDisplayName(getUsername());
ContactHeader contactHeader =
headerFactory.createContactHeader(contactAddress);
request.addHeader(contactHeader);
ContentTypeHeader contentTypeHeader =
headerFactory.createContentTypeHeader("text", "plain");
request.setContent(message, contentTypeHeader);
...
最后,使用SipProvider實(shí)例發(fā)送消息:
sipProvider.sendRequest(request); ? }
4.5 發(fā)送會(huì)話消息
你在會(huì)話外發(fā)送我們的信息,這意味著消息之間沒(méi)有關(guān)聯(lián),這對(duì)于TextClient這樣的簡(jiǎn)單即時(shí)消息傳遞應(yīng)用程序來(lái)說(shuō)效果很好。另一種方法是使用INVITE消息創(chuàng)建一個(gè)會(huì)話,然后在該會(huì)話內(nèi)發(fā)送消息。TextClient不使用這種技術(shù),但是是值得學(xué)習(xí)的東西,本小節(jié)描述了如何做到這一點(diǎn)。
在會(huì)話中發(fā)送消息需要?jiǎng)?chuàng)建Dialog和Transaction對(duì)象。在初始消息(即創(chuàng)建會(huì)話的消息)上,不使用提供程序發(fā)送消息,而是實(shí)例化一個(gè)Transaction,然后從中獲取Dialog。您保留Dialog引用以供以后使用。然后使用事務(wù)發(fā)送消息:
ClientTransaction trans = sipProvider.getNewClientTransaction(invite);
dialog = trans.getDialog();
trans.sendRequest();
稍后,當(dāng)您希望在同一個(gè)會(huì)話中發(fā)送新消息時(shí),您可以使用前面的Dialog對(duì)象來(lái)創(chuàng)建一個(gè)新請(qǐng)求。然后,您可以對(duì)請(qǐng)求進(jìn)行消息處理,最后,使用Transaction發(fā)送消息。
request = dialog.createRequest(Request.MESSAGE);
request.setHeader(contactHeader);
request.setContent(message, contentTypeHeader);
ClientTransaction trans = sipProvider.getNewClientTransaction(request);
trans.sendRequest();
從本質(zhì)上講,在現(xiàn)有會(huì)話中發(fā)送消息時(shí),您跳過(guò)了“創(chuàng)建主要元素”步驟。當(dāng)您使用INVITE創(chuàng)建對(duì)話框時(shí),不要忘記在對(duì)話框結(jié)束時(shí)發(fā)送一個(gè)BYE消息來(lái)清理它。此技術(shù)還用于刷新注冊(cè)和訂閱。
在前面,您已經(jīng)看到了SipListener接口,其中包含processDialogTerminated()和processTransactionTerminated()方法。它們分別在對(duì)話框和事務(wù)結(jié)束時(shí)自動(dòng)調(diào)用。通常,實(shí)現(xiàn)這些方法是為了清理(例如,丟棄Dialog和Transaction實(shí)例)。您將把這兩個(gè)方法留空,因?yàn)樵赥extClient中不需要它們。
4.6 接收SIP響應(yīng)
前面,您注冊(cè)了傳入消息的監(jiān)聽(tīng)器。監(jiān)聽(tīng)器接口SipListener包含方法processResponse(),當(dāng)SIP響應(yīng)消息到達(dá)時(shí),由SIP協(xié)議棧調(diào)用該方法。processResponse()接受一個(gè)ResponseEvent類(lèi)型的參數(shù),它封裝了一個(gè)Response對(duì)象。
public void processResponse(ResponseEvent evt) {
Response response = evt.getResponse();
int status = response.getStatusCode();
if( (status >= 200) && (status < 300) ) { //Success!
messageProcessor.processInfo("--Sent");
return;
}
messageProcessor.processError("Previous message not sent: " + status);
}
在此方法中,您將檢查先前MESSAGE消息的響應(yīng)是否表示成功(2xx范圍的狀態(tài)碼)或錯(cuò)誤(否則)。然后通過(guò)回調(diào)接口將此信息轉(zhuǎn)發(fā)給用戶(hù)。
通常,您只讀取processResponse()方法中的Response對(duì)象。唯一的例外是對(duì)INVITE消息的成功響應(yīng);在這種情況下,你必須發(fā)送一個(gè)ACK請(qǐng)求,就像這樣:
Dialog dialog = evt.getClientTransaction().getDialog();
Request ack = dialog.createAck();
dialog.sendAck(ack);
4.7 接收SIP請(qǐng)求
接收SIP請(qǐng)求消息與接收響應(yīng)一樣簡(jiǎn)單。您只需實(shí)現(xiàn)SipListener接口的另一個(gè)方法processRequest(), SIP堆棧將自動(dòng)調(diào)用它。該方法的唯一參數(shù)是RequestEvent對(duì)象,其中包含Request對(duì)象。這是你之前見(jiàn)過(guò)的相同類(lèi)型,它有相同的方法。但是,您不應(yīng)該在傳入請(qǐng)求上設(shè)置任何字段,因?yàn)檫@沒(méi)有多大意義。
processRequest()的典型實(shí)現(xiàn)就是分析請(qǐng)求,然后創(chuàng)建并發(fā)回適當(dāng)?shù)捻憫?yīng):
public void processRequest(RequestEvent evt) {
Request req = evt.getRequest();
String method = req.getMethod();
if( ! method.equals("MESSAGE")) { //bad request type.
messageProcessor.processError("Bad request type: " + method);
return;
}
FromHeader from = (FromHeader)req.getHeader("From");
messageProcessor.processMessage(from.getAddress().toString(), new String(req.getRawContent())); Response response=null;
try { //Reply with OK
response = messageFactory.createResponse(200, req);
ToHeader toHeader = (ToHeader)response.getHeader(ToHeader.NAME);
toHeader.setTag("888"); //Identifier, specific to your application
ServerTransaction st = sipProvider.getNewServerTransaction(req);
st.sendResponse(response);
} catch (Throwable e) {
e.printStackTrace();
messageProcessor.processError("Can't send OK reply.");
}
}
在這種情況下,您總是用一個(gè)成功響應(yīng)(200)來(lái)回復(fù),但是您也可以發(fā)回任何錯(cuò)誤響應(yīng)(通常是4xx范圍)。
4.8 處理錯(cuò)誤
SipListener接口中還有其他尚未實(shí)現(xiàn)的方法。當(dāng)由于特定原因無(wú)法發(fā)送請(qǐng)求時(shí),由SIP協(xié)議調(diào)用它們。例如,當(dāng)接收消息的端點(diǎn)沒(méi)有及時(shí)應(yīng)答時(shí),將調(diào)用processTimeout()。這是一種沒(méi)有響應(yīng)的特殊情況,因此沒(méi)有可用的response對(duì)象。TimeoutEvent參數(shù)包含超時(shí)請(qǐng)求的ClientTransaction,如果需要,可以使用該參數(shù)鏈接回原始請(qǐng)求。在這個(gè)實(shí)現(xiàn)中,你只需使用回調(diào)接口通知用戶(hù):
public void processTimeout(TimeoutEvent evt) {
messageProcessor.processError("Previous message not sent: " + "timeout");
}
類(lèi)似地,Input/Output (IO)錯(cuò)誤的處理方法如下:
public void processIOException(IOExceptionEvent evt) {
messageProcessor.processError("Previous message not sent: " + "I/O Exception");
}
4.9 小節(jié)
本文概述了JAIN SIP API,并展示了如何編寫(xiě)一個(gè)簡(jiǎn)單的應(yīng)用程序來(lái)使用這項(xiàng)技術(shù)?,F(xiàn)在,您應(yīng)該對(duì)可用的api有了很好的了解,并且知道如何使用SIP編寫(xiě)自己的IM客戶(hù)機(jī)。
以上內(nèi)容主要來(lái)自O(shè)RACLE官網(wǎng)《An Introduction to the JAIN SIP API》文章,TextClient源碼下載地址也在文章提供,感興趣的同學(xué)可以閱讀原文,文章地址:An Introduction to the JAIN SIP API。
下面將該API應(yīng)用到安防領(lǐng)域?qū)崿F(xiàn)一個(gè)能夠滿(mǎn)足GB28181協(xié)議的SIP服務(wù)器。
二 GB28181SIP服務(wù)器——MSH
1 簡(jiǎn)介
SIP(Session Initiation Protocol,會(huì)話發(fā)起協(xié)議)是一種基于文本的網(wǎng)絡(luò)通信協(xié)議,主要用于實(shí)現(xiàn)語(yǔ)音、視頻和數(shù)據(jù)等多種媒體資源的實(shí)時(shí)傳輸。SIP信令在以下應(yīng)用領(lǐng)域和行業(yè)中發(fā)揮著重要作用:
1.語(yǔ)音通信:SIP協(xié)議可用于固定電話、移動(dòng)電話和網(wǎng)絡(luò)電話之間的通話,實(shí)現(xiàn)電話撥號(hào)、通話建立、通話保持和通話結(jié)束等功能。
2.視頻通信:通過(guò)SIP協(xié)議,用戶(hù)可以實(shí)現(xiàn)音視頻通話、視頻會(huì)議和協(xié)同工作等應(yīng)用,滿(mǎn)足企業(yè)和個(gè)人之間的溝通需求。
3.即時(shí)通訊:SIP協(xié)議可應(yīng)用于即時(shí)通訊領(lǐng)域,提供文本、圖片、語(yǔ)音和視頻等豐富的通信方式,如微信、WhatsApp等。
4.網(wǎng)絡(luò)電視和多媒體廣播:SIP協(xié)議可用于實(shí)現(xiàn)點(diǎn)播、直播和時(shí)移電視等多媒體服務(wù),滿(mǎn)足用戶(hù)對(duì)多媒體內(nèi)容的需求。
5.智能家居:通過(guò)SIP協(xié)議,可以實(shí)現(xiàn)家庭設(shè)備之間的互聯(lián)互通,如智能音響、智能攝像頭、智能照明等,提升家居生活的便捷性和舒適度。
6.企業(yè)通信:SIP協(xié)議可應(yīng)用于企業(yè)內(nèi)部通信系統(tǒng),實(shí)現(xiàn)電話交換、電話會(huì)議、呼叫中心等功能,提高企業(yè)通信效率。
7.物聯(lián)網(wǎng):SIP協(xié)議可用于物聯(lián)網(wǎng)設(shè)備之間的通信,實(shí)現(xiàn)智能控制、遠(yuǎn)程監(jiān)控和數(shù)據(jù)分析等應(yīng)用。
公共服務(wù):SIP協(xié)議在公共安全、緊急救援、交通管理等領(lǐng)域具有廣泛應(yīng)用,提高公共服務(wù)的質(zhì)量和效率。
8.教育:SIP協(xié)議可以實(shí)現(xiàn)遠(yuǎn)程教學(xué)、在線課堂和視頻講座等功能,拓展教育領(lǐng)域的發(fā)展空間。
9.醫(yī)療:通過(guò)SIP協(xié)議,可以實(shí)現(xiàn)遠(yuǎn)程診斷、視頻咨詢(xún)和在線掛號(hào)等醫(yī)療服務(wù),提高醫(yī)療資源的利用效率。
本demo(項(xiàng)目)是JAIN SIP API的應(yīng)用,適用于安防領(lǐng)域,當(dāng)然如果你是一位其他領(lǐng)域的從業(yè)者,該項(xiàng)目代碼也會(huì)起到舉一反三、拋磚引玉的作用。
本項(xiàng)目不僅僅包含了SIP服務(wù)器,還有流媒體服務(wù)器和WEB服務(wù)器的實(shí)現(xiàn),是一個(gè)完整的程序,方便實(shí)現(xiàn)二次開(kāi)發(fā)與功能拓展。下文僅僅對(duì)SIP服務(wù)器的實(shí)現(xiàn)做簡(jiǎn)單的介紹。
2 GB28181
在GB28181-2022協(xié)議規(guī)范中“9控制、傳輸流程和協(xié)議接口”中規(guī)定了IPC注冊(cè)、注銷(xiāo)、點(diǎn)播、狀態(tài)信息報(bào)送等控制的命令流程與協(xié)議接口,下面我們將按照GB28181流程,采用JAIN SIP API實(shí)現(xiàn)IPC的向SIP服務(wù)器的注冊(cè)與狀態(tài)信息報(bào)送(?;睿?。
2.1 注冊(cè)
2.2 ?;?/h4>
2.2.1 命令流程
2.2.2 協(xié)議接口
2.3 MSH代碼概述
2.3.1 創(chuàng)建springboot項(xiàng)目
創(chuàng)建一個(gè)springboot項(xiàng)目并引入JAIN SIP API依賴(lài)。
2.3.2 SIP協(xié)議棧
創(chuàng)建SipLayer聲明CommandLineRunner接口,項(xiàng)目啟動(dòng)時(shí)會(huì)建立一堆以后會(huì)有用的對(duì)象:SipFactory、SipStack、ListeningPoint,同時(shí)創(chuàng)建TCP與UDP監(jiān)聽(tīng)器用來(lái)兼容IPC的TCP/UDP接入。
SipLayer類(lèi)注入SipConfig對(duì)象,該對(duì)象配置了SIP服務(wù)器的ip、端口、域名、id和密碼。
SipLayer類(lèi)注入SipServerListener,SipServerListener接口繼承于SipListener,SipServerListener的子類(lèi)為SipServerListenerImpl,SipServerListenerImpl為實(shí)現(xiàn)SIP請(qǐng)求響應(yīng)的處理。
2.3.3 接收SIP請(qǐng)求響應(yīng)
SipServerListenerImpl類(lèi)實(shí)現(xiàn)了SipListener接口,重寫(xiě)processRequest()與processResponse()方法,來(lái)接收SIP請(qǐng)求與響應(yīng)。該類(lèi)采用了類(lèi)似觀察者模式的設(shè)計(jì)思路,聲明了兩個(gè)線程安全的容器reqHandlerMap與respHandlerMap用來(lái)存放不同的SIP請(qǐng)求響應(yīng)的真實(shí)處理對(duì)象,例如processRequest()接收到一個(gè)REGISTER請(qǐng)求,利用java繼承與多態(tài)的特性,processRequest()方法根據(jù)SIP方法類(lèi)型為key獲取到真實(shí)處理對(duì)象,最后由真實(shí)處理對(duì)象處理REGISTER請(qǐng)求。
在真實(shí)的平臺(tái)與IPC進(jìn)行信令交互時(shí),會(huì)面臨并發(fā)處理多種SIP請(qǐng)求響應(yīng)的場(chǎng)景,所以在processRequest()與processResponse()方法上使用@Aync()注解,實(shí)現(xiàn)異步處理SIP信令。
2.3.4 處理SIP請(qǐng)求
SipReqHandler接口的實(shí)現(xiàn)類(lèi)有兩個(gè)RegisterReqHandler和KeepaliveReqHandler,分別實(shí)現(xiàn)IPC的注冊(cè)與?;?,代碼實(shí)現(xiàn)流程請(qǐng)參照該小節(jié)的命令流程部分,最后我們將進(jìn)行抓包分析整個(gè)信令的交互流程。
我們發(fā)現(xiàn)GB28181中,有很多控制傳輸流程都是通過(guò)MESSAGE方法+MANSCDP命令集實(shí)現(xiàn)的,所以我們要在接收到IPC的MESSAGE方法時(shí),解析MANSCDP命令集,解析到cmdType = "Keepalive"的請(qǐng)求,才是?;钫?qǐng)求,然后回復(fù)給IPC200,其他的MESSAGE請(qǐng)求這里暫時(shí)不處理。
2.3.5 發(fā)送SIP請(qǐng)求
SipSender類(lèi)實(shí)現(xiàn)了SIP消息報(bào)文的封裝,通過(guò)sendResponse()方法回復(fù)IPC消息。
2.3.6 IPC接入
1.首先啟動(dòng)SIP服務(wù)器,查看SIP服務(wù)器的配置信息:
#SIP
# SIP服務(wù)器IP
sip.ip=10.192.33.34
# SIP服務(wù)監(jiān)聽(tīng)的端口
sip.port=5060
# SIP域
sip.domain=34020000
# SIP服務(wù)器國(guó)標(biāo)ID
sip.id=34020000001320000010
# SIP服務(wù)器密碼
sip.password=admin123
2.IPC平臺(tái)接入配置需要配置SIP服務(wù)器信息:
?IPC平臺(tái)接入的密碼為SIP服務(wù)器密碼,用于服務(wù)器校驗(yàn),校驗(yàn)正確才能實(shí)現(xiàn)IPC的注冊(cè)。IPC每間隔60秒發(fā)送一次心跳信息,觀察SIP服務(wù)器日志,滿(mǎn)足GB28181規(guī)定命令流程,下面抓包分析信令交互流程。
3.SIP服務(wù)器日志:
?
?
2.3.7 抓包與流程分析
2.3.7.1 注冊(cè)
REGISTER sip:34020000001320000010@34020000 SIP/2.0
Via: SIP/2.0/UDP 10.192.33.95:5060;rport;branch=z9hG4bK863117711
From: <sip:34020000001320000002@34020000>;tag=294565749
To: <sip:34020000001320000002@34020000>
Call-ID: 1973051184
CSeq: 1 REGISTER
Contact: <sip:34020000001320000002@10.192.33.95:5060>
Max-Forwards: 70
User-Agent: IP Camera
Expires: 3600
Content-Length: 0
SIP/2.0 401 Unauthorized
CSeq: 1 REGISTER
Call-ID: 1973051184
From: <sip:34020000001320000002@34020000>;tag=294565749
To: <sip:34020000001320000002@34020000>
Via: SIP/2.0/UDP 10.192.33.95:5060;rport=5060;branch=z9hG4bK863117711;received=10.192.33.95
WWW-Authenticate: Digest realm="34020000",qop="auth",nonce="1ba00522b15b098aa2c05150cdb0df31",algorithm=MD5
User-Agent: sip-server-yrz
Content-Length: 0
REGISTER sip:34020000001320000010@34020000 SIP/2.0
Via: SIP/2.0/UDP 10.192.33.95:5060;rport;branch=z9hG4bK713030866
From: <sip:34020000001320000002@34020000>;tag=294565749
To: <sip:34020000001320000002@34020000>
Call-ID: 1973051184
CSeq: 2 REGISTER
Contact: <sip:34020000001320000002@10.192.33.95:5060>
Authorization: Digest username="34020000001320000002", realm="34020000", nonce="1ba00522b15b098aa2c05150cdb0df31", uri="sip:34020000001320000010@34020000", response="ff34c4434d132ad9b956c729aa229194", algorithm=MD5, cnonce="0a4f113b", qop=auth, nc=00000001
Max-Forwards: 70
User-Agent: IP Camera
Expires: 3600
Content-Length: 0
SIP/2.0 200 OK
CSeq: 2 REGISTER
Call-ID: 1973051184
From: <sip:34020000001320000002@34020000>;tag=294565749
To: <sip:34020000001320000002@34020000>
Via: SIP/2.0/UDP 10.192.33.95:5060;rport=5060;branch=z9hG4bK713030866;received=10.192.33.95
Date: 2023-04-19T11:29:33.703
Contact: <sip:34020000001320000002@10.192.33.95:5060>
Expires: 3600
User-Agent: sip-server-yrz
Content-Length: 0
-
IPC發(fā)起REGISTER請(qǐng)求,未攜帶Authorization認(rèn)證信息。
-
SIP服務(wù)器回復(fù)401與認(rèn)證加密算法。
-
IPC重新發(fā)起REGISTER并攜帶Authorization認(rèn)證信息。
-
SIP服務(wù)器認(rèn)證成功后回復(fù)200,IPC注冊(cè)成功。
2.3.7.2 保活
MESSAGE sip:34020000001320000010@34020000 SIP/2.0
Via: SIP/2.0/UDP 10.192.33.95:5060;rport;branch=z9hG4bK1171736073
From: <sip:34020000001320000002@34020000>;tag=699092543
To: <sip:34020000001320000010@34020000>
Call-ID: 776784695
CSeq: 20 MESSAGE
Content-Type: Application/MANSCDP+xml
Max-Forwards: 70
User-Agent: IP Camera
Content-Length: 182
<?xml version="1.0" encoding="GB2312"?>
<Notify>
<CmdType>Keepalive</CmdType>
<SN>3634867</SN>
<DeviceID>34020000001320000002</DeviceID>
<Status>OK</Status>
<Info>
</Info>
</Notify>
SIP/2.0 200 OK
CSeq: 20 MESSAGE
Call-ID: 776784695
From: <sip:34020000001320000002@34020000>;tag=699092543
To: <sip:34020000001320000010@34020000>;tag=1681876509157
Via: SIP/2.0/UDP 10.192.33.95:5060;rport=5060;branch=z9hG4bK1171736073;received=10.192.33.95
User-Agent: sip-server-yrz
Content-Length: 0
-
IPC發(fā)起MESSAGE請(qǐng)求并攜帶設(shè)備ID。
-
SIP服務(wù)器回復(fù)200。
3 小結(jié)
3.1 SIP服務(wù)器
目前實(shí)現(xiàn)的為注冊(cè)、保活、設(shè)備信息查詢(xún)、目錄查詢(xún)、點(diǎn)播。下圖為VLC播放取流播放截圖。
3.2 WEB服務(wù)器
目前實(shí)現(xiàn)點(diǎn)播、通道同步、flv/hls/webrtc(zlm和webrtc-streame兩種流媒體)格式流web預(yù)覽。
支持人臉識(shí)別,人臉識(shí)別部分請(qǐng)查看文章
代碼持續(xù)更新中...
需要SIP服務(wù)器源碼請(qǐng)私信我^-^文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-698381.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-698381.html
到了這里,關(guān)于JAIN SIP API詳解與GB28181服務(wù)器實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!