目录
JAIN SIP API
摘要
关于JAIN SIP API
API概述
maven坐标
类/接口
Message接口
Request接口
Response接口
即时通讯程序
TextClient代码概述
Message Processor
SIP协议栈
发送SIP请求
发送会话消息
接收SIP响应
接收SIP请求
处理错误
总结
GB28181SIP服务器
注册
保活
sip-server-demo代码概述
创建springboot项目
SIP协议栈
接收SIP请求响应
处理SIP请求
发送SIP请求
IPC接入
抓包分析
总结
JAIN SIP API
摘要
这篇文章展示了基于Java SE如何创建客户端侧的SIP应用。JAIN SIP API是一个强大的“SIP协议栈”。本文将通过一个简单的即时通讯程序以及一个GB28181协议的简单应用程序,详细的分析该技术。
关于JAIN SIP API
Java api for Integrated Networks (JAIN)是一个JCP工作组所管理的电信标准,Session Initiation Protocol(SIP)是一种标准的通信协议,将Java和SIP结合在一起,就得到了JAIN SIP API,这是一个标准的、功能强大的电信API。这个API通常用于客户端应用程序开发。其他基于容器的技术,如SIP Servlet API(参见BEA WebLogic SIP Server的例子),更适合于服务器端开发,但是在GB28181协议应用程序中我们也采用该API用作SIP服务器的开发实现IPC与联网平台的信令交互。
API概述
maven坐标
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId>
<version>1.3.0-91</version>
</dependency>
类/接口
下面概述了JAIN SIP API实现中的主要类和接口。
Class / Interface | 描述 |
---|---|
SipFactory / AddressFactory / HeaderFactory / MessageFactory | 工厂类来创建系统的各种对象。它们返回声明了标准接口的对象。 |
SipStack | 您需要的第一个接口,用于创建ListeningPoints和SipProviders。 |
ListeningPoint | 这个接口封装了一个传输/端口对(例如UDP/5060)。 |
SipProvider | 这个接口用来发送SIP消息。您还可以使用此接口为传入的SIP消息注册一个监听器。参见下面的SipListener。 |
SipListener | 您必须实现此接口以允许接收传入的SIP消息。 |
RequestEvent / ResponseEvent | 表示传入的SIP请求、响应。传递给SipListener进行处理。分别包含一个Request或Response对象。 |
TimeoutEvent | 表示传出请求没有回复时的失败条件。传递给SipListener进行处理。 |
IOExceptionEvent | 表示在发送外发请求时出现输入/输出问题时的失败条件。传递给SipListener进行处理。 |
Request / Response | 表示SIP请求、响应。两者都是Message接口的子接口。它们提供对报头、内容和SIP消息的其他部分的访问。 |
Dialog | 此接口的对象封装了一个SIP对话框。(提醒:在对话框中,所有消息都与同一个调用相关;对话通常以INVITE开始,以BYE结束。 |
ClientTransaction / ServerTransaction | 封装SIP事务。(提醒:事务以请求开始,以最终响应结束。事务通常存在于对话框中。) |
Message接口
Message接口是SIP消息的基本接口,下面是可用方法的概述。
Method | 描述 |
---|---|
void addHeader(Header) void setHeader(Header) | 将报头字段设置为SIP消息。第一种方法可用于可重复或具有多个值的标头,如Contact标头。第二个方法删除该类型的现有头,然后添加单个头值。 |
void removeHeader(Header) | 删除此类型的现有标头。 |
ListIterator getHeaderNames() | 返回所有头文件名称。 |
ListIterator getUnrecognizedHeaders() | 返回非标准报头类型的报头名称。 |
Header getHeader(String) ListIterator getHeaders(String) | ListIterator getHeaders(字符串) 特定头的getter。第二种形式返回可重复标头的所有值,或具有多个值的标头,如Contact标头。 |
void setContent(Object, ContentTypeHeader) | 设置消息的有效负载以及Content-Type报头。如果类型是字符串,Content-Length也被设置,否则使用void setContentLength(ContentLengthHeader)。 |
byte [] getRawContent() Object getContent() | 检索消息的有效负载。 |
void removeContent() | 清空有效负载。 |
void setContentLength(ContentLengthHeader) ContentLengthHeader getContentLength() void setContentLanguage(ContentLanguageHeader) ContentLanguageHeader getContentLanguage() void setContentEncoding(ContentEncodingHeader) ContentEncodingHeader getContentEncoding() void setContentDisposition(ContentDispositionHeader) ContentDispositionHeader getContentDisposition() | 与有效负载相关的特殊头访问器。很少使用。 |
void setExpires(ExpiresHeader) ExpiresHeader getExpires() | 管理Expires报头。 |
void setSipVersion(String) String getSipVersion() | 字符串getSipVersion () SIP版本元素的访问器。很少使用,默认为SIP/2.0。 |
Object clone() | 创建消息的副本。很少使用。 |
Request接口
Message接口的子接口
Method | 描述 |
---|---|
String getMethod() void setMethod(String) | 方法元素的访问器。可以是任何SIP方法,包括请求接口常量中的方法:ACK、BYE、CANCEL、INVITE、OPTIONS、REGISTER、NOTIFY、SUBSCRIBE、MESSAGE、REFER、INFO、PRACK和UPDATE。 |
URI getRequestURI() void setRequestURI(URI) | 请求URI的访问器,这是SIP请求的第一行。通常,这是SipURI的一个实例。 |
Response接口
Message接口的子接口。
Method | 描述 |
---|---|
void setStatusCode() int getStatusCode() | 状态代码的访问器。这可以是任何SIP状态码,包括Response接口的常量成员中的状态码。这里有一些:RINGING (180), OK (200), BAD_REQUEST(400),等等。 |
void setReasonPhrase(String) String getReasonPhrase() | 访问器,用于人类可读的状态代码解释。 |
即时通讯程序
TextClient是一个即时消息传递应用程序,可以通过SIP协议发送和接收文本消息。此应用程序的一个实例可以向另一个实例发送消息,但从理论上讲,此客户机可用于向其他类型的SIP即时消息传递客户机,甚至SIP服务器应用程序发送消息。如下图所示,SIP客户端yrz向另一个SIP客户端yz发送了一条”我是yrz2023年4月18日13:46:22“的消息,随后SIP客户端yz回复了一条”yz收到2023年4月18日13:46:22“的消息。
TextClient代码概述
两个类和一个接口组成了整个TextClient代码。下表介绍:
Class / Interface | 描述 |
---|---|
TextClient | 主类,包含应用程序小部件的Swing窗口。 |
SipLayer | 它负责所有SIP通信。它由TextClient类实例化,并通过MessageProcessor接口回调它。 |
MessageProcessor | 回调接口(观察者模式),用于将SipLayer与其容器解耦。 |
Message Processor
创建MessageProcessor接口,将SIP层与GUI层分离。TextClient类实现该接口,其构造函数将SipLayer对象作为参数,您将能够使用SipLayer对象将信息发送回GUI。
public interface MessageProcessor
{
// 请求回调方法
void processMessage(String sender, String message);
// 请求错误回调方法
void processError(String errorMessage);
// 响应回调方法
void processInfo(String infoMessage);
}
SIP协议栈
让我们开始编写SipLayer类。TextClient必须能够接收来自其他SIP端点的异步消息。这个类实现了SipListener接口来处理传入的消息:
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()。接下来是存储稍后需要的对象的两个字段:username和messageProcessor,这些与SIP API没有直接关系,但是在本例中需要它们。第一个是前面讨论过的MessageProcessor对象,用于回调方法将消息发回给GUI,username用于随时保留用户名,这两个字段有getter和setter方法。
private MessageProcessor messageProcessor;
private String username;
接下来是构造函数,一种启动JAIN SIP API的经典方法——建立一堆以后会有用的对象(工厂和SIP协议栈实例),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用于实例化SipStack实现,但由于可能有多个实现,因此必须通过setPathName()方法命名您想要的那个实现。名称“gov.nist”表示您获得的SIP堆栈。
SipStack对象具有许多属性。至少,您必须设置堆栈名称。所有其他属性都是可选的。在这里,我设置了一个由堆栈使用的IP地址,用于一台计算机有多个IP地址的情况。注意,这里有标准属性(所有SIP API实现都必须支持)和非标准属性(依赖于实现)。
下一步是创建一对ListeningPoint和SipProvider对象。这些对象提供了发送和接收消息的通信功能。TCP有一组,UDP有一组。这也是你选择SipLayer作为传入SIP消息的监听器的地方:
...
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);
}
构造函数就是这样结束的。您已经使用JAIN SIP API创建了一个SipStack实例、一堆工厂、两个listeningpoint和一个SipProvider。这些对象将在接下来的方法中用于发送和接收消息。
发送SIP请求
现在让我们编写一个使用JAIN SIP API发送SIP消息的方法,在此之前你必须非常了解SIP协议。SIP API是相当低级的抽象,在大多数情况下,不使用默认值或隐藏头、请求uri或SIP消息的内容。这种设计的优点是您可以完全控制SIP消息所包含的内容。
发送一个SIP请求大致分为四个部分:
-
创建主要元素
-
创建消息
-
完整的消息
-
发送消息
使用JAIN SIP API构造消息最少需要以下主要SIP元素:
-
请求URI
-
方法
-
通话身份头
-
CSeq头
-
从标题
-
Via报头数组
- Max-forwards头
下面的代码片段创建了所有这些元素:
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);
...
我使用在构造函数HeaderFactory和AddressFactory中创建的工厂来实例化这些元素。接下来让我们实例化实际的SIP消息本身,传入之前创建的所有元素:
Request request = messageFactory.createRequest(
requestURI, Request.MESSAGE, callIdHeader, cSeqHeader,
fromHeader, toHeader, viaHeaders, maxForwards);
...
注意,这一步使用了MessageFactory。然后,让我们向消息添加其他元素:联系人标头和消息的内容(有效负载),也可以添加自定义标题。
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实例发送消息:
sipProvider.sendRequest(request); }
发送会话消息
你在会话外发送我们的信息,这意味着消息之间没有关联,这对于TextClient这样的简单即时消息传递应用程序来说效果很好。另一种方法是使用INVITE消息创建一个会话,然后在该会话内发送消息。TextClient不使用这种技术,但是是值得学习的东西,本小节描述了如何做到这一点。
在会话中发送消息需要创建Dialog和Transaction对象。在初始消息(即创建会话的消息)上,不使用提供程序发送消息,而是实例化一个Transaction,然后从中获取Dialog。您保留Dialog引用以供以后使用。然后使用事务发送消息:
ClientTransaction trans = sipProvider.getNewClientTransaction(invite);
dialog = trans.getDialog();
trans.sendRequest();
稍后,当您希望在同一个会话中发送新消息时,您可以使用前面的Dialog对象来创建一个新请求。然后,您可以对请求进行消息处理,最后,使用Transaction发送消息。
request = dialog.createRequest(Request.MESSAGE);
request.setHeader(contactHeader);
request.setContent(message, contentTypeHeader);
ClientTransaction trans = sipProvider.getNewClientTransaction(request);
trans.sendRequest();
从本质上讲,在现有会话中发送消息时,您跳过了“创建主要元素”步骤。当您使用INVITE创建对话框时,不要忘记在对话框结束时发送一个BYE消息来清理它。此技术还用于刷新注册和订阅。
在前面,您已经看到了SipListener接口,其中包含processDialogTerminated()和processTransactionTerminated()方法。它们分别在对话框和事务结束时自动调用。通常,实现这些方法是为了清理(例如,丢弃Dialog和Transaction实例)。您将把这两个方法留空,因为在TextClient中不需要它们。
接收SIP响应
前面,您注册了传入消息的监听器。监听器接口SipListener包含方法processResponse(),当SIP响应消息到达时,由SIP协议栈调用该方法。processResponse()接受一个ResponseEvent类型的参数,它封装了一个Response对象。
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消息的响应是否表示成功(2xx范围的状态码)或错误(否则)。然后通过回调接口将此信息转发给用户。
通常,您只读取processResponse()方法中的Response对象。唯一的例外是对INVITE消息的成功响应;在这种情况下,你必须发送一个ACK请求,就像这样:
Dialog dialog = evt.getClientTransaction().getDialog();
Request ack = dialog.createAck();
dialog.sendAck(ack);
接收SIP请求
接收SIP请求消息与接收响应一样简单。您只需实现SipListener接口的另一个方法processRequest(), SIP堆栈将自动调用它。该方法的唯一参数是RequestEvent对象,其中包含Request对象。这是你之前见过的相同类型,它有相同的方法。但是,您不应该在传入请求上设置任何字段,因为这没有多大意义。
processRequest()的典型实现就是分析请求,然后创建并发回适当的响应:
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.");
}
}
在这种情况下,您总是用一个成功响应(200)来回复,但是您也可以发回任何错误响应(通常是4xx范围)。
处理错误
SipListener接口中还有其他尚未实现的方法。当由于特定原因无法发送请求时,由SIP协议调用它们。例如,当接收消息的端点没有及时应答时,将调用processTimeout()。这是一种没有响应的特殊情况,因此没有可用的response对象。TimeoutEvent参数包含超时请求的ClientTransaction,如果需要,可以使用该参数链接回原始请求。在这个实现中,你只需使用回调接口通知用户:
public void processTimeout(TimeoutEvent evt) {
messageProcessor.processError("Previous message not sent: " + "timeout");
}
类似地,Input/Output (IO)错误的处理方法如下:
public void processIOException(IOExceptionEvent evt) {
messageProcessor.processError("Previous message not sent: " + "I/O Exception");
}
总结
本文概述了JAIN SIP API,并展示了如何编写一个简单的应用程序来使用这项技术。现在,您应该对可用的api有了很好的了解,并且知道如何使用SIP编写自己的IM客户机。
以上内容主要来自ORACLE官网《An Introduction to the JAIN SIP API》文章,TextClient源码下载地址也在文章提供,感兴趣的同学可以阅读原文,文章地址:An Introduction to the JAIN SIP API。
下面将该API应用到安防领域实现一个能够满足GB28181协议的SIP服务器。
GB28181SIP服务器
在GB28181-2022协议规范中“9控制、传输流程和协议接口”中规定了IPC注册、注销、点播、状态信息报送等控制的命令流程与协议接口,下面我们将按照GB28181流程,采用JAIN SIP API实现IPC的向SIP服务器的注册与状态信息报送(保活)。
注册
命令流程
命令流程截图自GB28181-2022,见下图:
保活
命令流程
命令流程截图自GB28181-2022,见下图:
协议接口
协议接口描述截图自GB28181-2022,见下图:
sip-server-demo代码概述
创建springboot项目
创建一个springboot项目并引入JAIN SIP API依赖。
SIP协议栈
创建SipLayer声明CommandLineRunner接口,项目启动时会建立一堆以后会有用的对象:SipFactory、SipStack、ListeningPoint,同时创建TCP与UDP监听器用来兼容IPC的TCP/UDP接入。
SipLayer类注入SipConfig对象,该对象配置了SIP服务器的ip、端口、域名、id和密码。
SipLayer类注入SipServerListener,SipServerListener接口继承于SipListener,SipServerListener的子类为SipServerListenerImpl,SipServerListenerImpl为实现SIP请求响应的处理。
接收SIP请求响应
SipServerListenerImpl类实现了SipListener接口,重写processRequest()与processResponse()方法,来接收SIP请求与响应。该类采用了类似观察者模式的设计思路,声明了两个线程安全的容器reqHandlerMap与respHandlerMap用来存放不同的SIP请求响应的真实处理对象,例如processRequest()接收到一个REGISTER请求,利用java继承与多态的特性,processRequest()方法根据SIP方法类型为key获取到真实处理对象,最后由真实处理对象处理REGISTER请求。
在真实的平台与IPC进行信令交互时,会面临并发处理多种SIP请求响应的场景,所以在processRequest()与processResponse()方法上使用@Aync()注解,实现异步处理SIP信令。
处理SIP请求
SipReqHandler接口的实现类有两个RegisterReqHandler和KeepaliveReqHandler,分别实现IPC的注册与保活,代码实现流程请参照该小节的命令流程部分,最后我们将进行抓包分析整个信令的交互流程。
我们发现GB28181中,有很多控制传输流程都是通过MESSAGE方法+MANSCDP命令集实现的,所以我们要在接收到IPC的MESSAGE方法时,解析MANSCDP命令集,解析到cmdType = "Keepalive"的请求,才是保活请求,然后回复给IPC200,其他的MESSAGE请求这里暂时不处理。
发送SIP请求
SipSender类实现了SIP消息报文的封装,通过sendResponse()方法回复IPC消息。
IPC接入
首先启动SIP服务器,查看SIP服务器的配置信息:
#SIP
# SIP服务器IP
sip.ip=10.192.33.34
# SIP服务监听的端口
sip.port=5060
# SIP域
sip.domain=34020000
# SIP服务器国标ID
sip.id=34020000001320000010
# SIP服务器密码
sip.password=admin123
IPC平台接入配置需要配置SIP服务器信息:
IPC平台接入的密码为SIP服务器密码,用于服务器校验,校验正确才能实现IPC的注册。IPC每间隔60秒发送一次心跳信息,观察SIP服务器日志,满足GB28181规定命令流程,下面抓包分析信令交互流程。
SIP服务器日志:
抓包分析
注册
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发起REGISTER请求,未携带Authorization认证信息。
-
SIP服务器回复401与认证加密算法。
-
IPC重新发起REGISTER并携带Authorization认证信息。
-
SIP服务器认证成功后回复200,IPC注册成功。
保活
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发起MESSAGE请求并携带设备ID。
-
SIP服务器回复200。
总结
SIP服务器源码私信。