0


JAIN SIP API详解与GB28181服务器实现

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:[email protected]: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:[email protected]: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:[email protected]:5060>
Expires: 3600
User-Agent: sip-server-yrz
Content-Length: 0

流程分析:

  1. IPC发起REGISTER请求,未携带Authorization认证信息。
  2. SIP服务器回复401与认证加密算法。
  3. IPC重新发起REGISTER并携带Authorization认证信息。
  4. 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

流程分析:

  1. IPC发起MESSAGE请求并携带设备ID。
  2. SIP服务器回复200。

总结

SIP服务器源码私信。

标签: java 音视频

本文转载自: https://blog.csdn.net/qq_27890899/article/details/130968649
版权归原作者 倔强的初学者 所有, 如有侵权,请联系我们删除。

“JAIN SIP API详解与GB28181服务器实现”的评论:

还没有评论