gb28181简单实现sip信令服务器(java版基于springboot):三、sip摄像头注册(代码实现)

6 篇文章 14 订阅
4 篇文章 10 订阅
代码只对海康和大华做了一些适配,仅供学习参考。不同摄像头可能不太一样,可以自行抓包适配。解析消息直接是字符串操作,比较方便,不依赖第三包。
摄像头注册代码
//接收到netty的信息后需要进行gbk解码
String str=packet.content().toString(Charset.forName("gbk"));
//去除空包,公网状态下很多空包 应该是服务器端口被探测
if(str.trim().length()==0){
    return;
}
//打印接收信息
logger.info("-------------");
logger.info(n+str);
logger.info(packet.sender().getAddress().getHostAddress());
logger.info(String.valueOf(packet.sender().getPort()));
logger.info("-------------");

String sendStr=null;

//re(str) 解析收到的字符串
//这里可以验证设备编号 
Map<String,String> map=re(str);
if(!map.get("deviceId").startsWith("3402000000111")){
    return;
}

//getDeviceInfo(map.get("deviceId")) 从redis获取上次保存的最新信息
//获取缓存中得设备信息 里面包含了设备所有保存的信息
DeviceInfo deviceInfo=getDeviceInfo(map.get("deviceId"));
//数据为空就实例化一个
if(deviceInfo==null){
    deviceInfo=new DeviceInfo();
}
//设置最新的ip和端口,公网情况下ip和端口是有时间限制的,所以必须保存最新的ip和端口
deviceInfo.setIp(packet.sender().getAddress().getHostAddress());
deviceInfo.setPort(packet.sender().getPort());
//保存最后通信时间以作过期判断
deviceInfo.setTime(System.currentTimeMillis());

根据上一节收到的数据样式进行简单的解析

private static Map<String,String> re(String str){
   Map<String,String> map=new HashMap<>();
   BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(str.getBytes(Charset.forName("gbk"))), Charset.forName("gbk")));
   //记录读取的行数
   Integer count=0;
   try {
       String line=null;
       //记录是否遇到空行,消息体和消息头中间会有个换行隔开
       boolean flag=false;
       while ((line=br.readLine())!=null){
           count=count+1;
           if("".equals(line)){
               flag=true;
               continue;
           }
           if(flag){
				//获取MESSAGE 对应的CmdType,这里常见的是keeplive(保护心跳)
               if(line.contains("<CmdType>")){
               		//去掉空字符串(测试的情况是海康的有空格,大华没有)
                   line=line.trim();
                   line=line.replace("<CmdType>","");
                   line=line.replace("</CmdType>","");
                   map.put("CmdType",line);
               }

               continue;
           }
           if(count==1){
           		//第一行信息 SIP/2.0结尾的是摄像头请求信息
               if(line.endsWith("SIP/2.0")){
               		//获取消息类型
                   map.put("method",line.split("\\s+")[0].trim());
                   map.put("messageType","REQUEST");
                   continue;
               }
               //SIP/2.0开头的是摄像头响应的信息
               if(line.startsWith("SIP/2.0")){
               		//响应码获取
                   map.put("stateCode",line.split("\\s+")[1]);
                   //消息类型
                   map.put("method",line.split("\\s+")[2].trim());
                   map.put("messageType","RESPONSE");
                   continue;
               }
           }
           //冒号加空格切割请求头属性和值
           String[] s=line.split(": ");
           //第一个是请求头名称,第二个是值
           map.put(s[0].trim(),s[1].trim());
           //From请求头,如果是请求信息,通常设备编号在这里获取
           if("From".equals(s[0])){
               if("REQUEST".equals(map.get("messageType"))){
                   String deviceId=s[1].split(";")[0];
                   deviceId=deviceId.split(":")[1];
                   deviceId=deviceId.replace(">","");
                   deviceId=deviceId.split("@")[0];
                   map.put("deviceId",deviceId);
                   logger.info(deviceId);
               }
           }

           if("To".equals(s[0])){
              	if("RESPONSE".equals(map.get("messageType"))&&!map.get("Via").contains("branchbye")){
                   String deviceId=s[1].split("\\s+")[0];
                   deviceId=deviceId.replaceAll("\"","");
                   map.put("deviceId",deviceId);
                   String deviceLocalIp=s[1].split("\\s+")[1];
                   deviceLocalIp=deviceLocalIp.split(";")[0];
                   deviceLocalIp=deviceLocalIp.split("@")[1];
                   deviceLocalIp=deviceLocalIp.replace(">","");
                   map.put("deviceLocalIp",deviceLocalIp.split(":")[0]);
                   map.put("deviceLocalPort",deviceLocalIp.split(":")[1]);
               }
               //这里区分是否是下达推流结束指令的的响应     
          	if("RESPONSE".equals(map.get("messageType"))&&map.get("Via").contains("branchbye")){
                   String deviceId=s[1].split(";")[0];
                   deviceId=deviceId.split("@")[0];
                   deviceId=deviceId.replace("<sip:","");
                   map.put("deviceId",deviceId);
               }
           }
       }
   }catch (Exception e){
       e.printStackTrace();
   }
	//设备编号处理特殊情况,有时包含空格
   if(map.get("deviceId").split("\\s+").length>1){
       map.put("deviceId",map.get("deviceId").split("\\s+")[1]);
   }
   return  map;

}
定义注册响应模板

{value} 这种格式的字符串用于替换真实的值
realm="3402000000"是填写sip域,这里直接赋值,可根据情况修改

//换行符
private static final String n="\r\n";

//摄像头第一次注册401回复验证
private static final String str_401=
        "SIP/2.0 401 Unauthorized"+"\r\n"+
                "CSeq: 1 REGISTER"+"\r\n"+
                "Call-ID: {Call-ID}"+"\r\n"+
                "From: {From}"+"\r\n"+
                "To: {To}"+"\r\n"+
                "Via: {Via}"+"\r\n"+
                "WWW-Authenticate: Digest realm=\"3402000000\",nonce=\"{nonce}\""+"\r\n"+
                "Content-Length: 0"+"\r\n"+
                "\r\n";

//第二次注册成功回复200 ok
private static final String str_200_ok=
        "SIP/2.0 200 OK"+"\r\n"+
                "CSeq: 2 REGISTER"+"\r\n"+
                "Call-ID: {Call-ID}"+"\r\n"+
                "From: {From}"+"\r\n"+
                "To: {To}"+"\r\n"+
                "Via: {Via}"+"\r\n"+
                "Expires: 3600"+"\r\n"+
                "Date: {Date}"+"\r\n"+
                "Content-Length: 0"+"\r\n"+
                "\r\n";
注册响应
 //摄像头注册处理
if("REGISTER".equals(map.get("method"))){
   //第一次注册,回复401
   if("1 REGISTER".equals(map.get("CSeq"))){
   		//该字符串是用于回复摄像头请求信息
       sendStr=str_401;
       //这里的nonce是一串随机数,随便自己生成
       String nonce = DigestUtils.md5Hex(map.get("Call-ID")+map.get("deviceId"));
       sendStr=sendStr.replace("{nonce}",nonce);
       //大部分的字段回复直接拷贝请求头里面的
       sendStr=sendStr.replace("{Call-ID}",map.get("Call-ID"));
       sendStr=sendStr.replace("{From}",map.get("From"));
       sendStr=sendStr.replace("{To}",map.get("To"));
       sendStr=sendStr.replace("{Via}",map.get("Via"));
       //重置推流状态
       deviceInfo.setLive(false);
   }else if("2 REGISTER".equals(map.get("CSeq"))){//第二次注册,这里可以验证是否是自己的摄像机,我这边直接通过
   		//关于如何验证密码下面再说明,这里直接通过
       sendStr=str_200_ok;
       sendStr=sendStr.replace("{Call-ID}",map.get("Call-ID"));
       sendStr=sendStr.replace("{From}",map.get("From"));
       sendStr=sendStr.replace("{To}",map.get("To"));
       sendStr=sendStr.replace("{Via}",map.get("Via"));
       //生成时间
       sendStr=sendStr.replace("{Date}",getGMT());

       String contact=map.get("Contact");
       contact=contact.split("@")[1];
       contact=contact.replace(">","");
  /* Map<String,String> d=new HashMap<>(2);
   d.put("deviceLocalIp",contact.split(":")[0]);
   d.put("deviceLocalPort",contact.split(":")[1]);*/
       deviceInfo.setLocalIp(contact.split(":")[0]);
       deviceInfo.setLocalPort(contact.split(":")[1]);

       String deviceId=map.get("deviceId");
       //注册成功直接生成ssrc,方便按需推流,这里是取设备编号后四位加前缀,然后取十六进制字符串
       deviceInfo.setSsrc(Utils.getSsrc("010000"+deviceId.substring(deviceId.length()-4)));
		//业务通知事件,可忽略
       Data.putScheduled(new SendTipsTask(deviceId + "注册成功。。。"));
       Data.putScheduled(new SendDataTask(commonService));
       //断流检测
       //bye(map.get("deviceId"));
   }else if("0".equals(map.get("Expires"))){//注销
       String deviceId=map.get("deviceId");
       //业务通知事件,可忽略
       Data.putScheduled(new SendTipsTask(deviceId + "注销。。。"));
       deviceInfo.setLive(false);
       Data.putScheduled(new SendDataTask(commonService));
   }else if(!"0".equals(map.get("Expires"))){//重复注册,直接回复200,如果回复401会造成断流
       sendStr=str_200_ok;
       sendStr=sendStr.replace("{Call-ID}",map.get("Call-ID"));
       sendStr=sendStr.replace("{From}",map.get("From"));
       sendStr=sendStr.replace("{To}",map.get("To"));
       sendStr=sendStr.replace("{Via}",map.get("Via"));
       sendStr=sendStr.replace("{Date}",getGMT());

       String contact=map.get("Contact");
       contact=contact.split("@")[1];
       contact=contact.replace(">","");
  /* Map<String,String> d=new HashMap<>(2);
   d.put("deviceLocalIp",contact.split(":")[0]);
   d.put("deviceLocalPort",contact.split(":")[1]);*/
       deviceInfo.setLocalIp(contact.split(":")[0]);
       deviceInfo.setLocalPort(contact.split(":")[1]);

       String deviceId=map.get("deviceId");
       deviceInfo.setSsrc(Utils.getSsrc("010000"+deviceId.substring(deviceId.length()-4)));
   }
}
注册验证机制

发送过去验证的参数系要SIP域(realm)和一串随机数(nonce)

public static void main(String[] args) {
    //ha1=md5(username:realm:password)
    //ha2=md5(Method:Uri)
    //RESPONSE=md5(HA1:nonce:HA2)
    
    //摄像头id 第二次注册参数会携带
    String username="34020000001110000003";
    //SIP域,要跟摄像头里填写的一致
    String realm="3402000000";
    //存在服务器和摄像头填写的注册密码,验证时必须要一致
    String password="123456";
    //发送给摄像头的随机数,第二次注册时摄像头回复也会携带该参数
    String nonce="962535b552b6e29883ff988c0065ddc2";
    //请求方法,这里直接就是注册
    String Method="REGISTER";
    //uri是第二次注册是的链接,第二次注册时摄像头回复也会携带该参数
    String Uri="sip:34020000002000000001@192.168.1.201:5060";
    //先生成ha1
    String ha1= DigestUtils.md5Hex(username+":"+realm+":"+password);
    //ha2
    String ha2= DigestUtils.md5Hex(Method+":"+Uri);
    //生成最终结果,然后对比摄像头传来的response属性,具体情形可参考上一节的注册信令解读
    //如果md5一致就是说明密码正确,然后验证通过,否则返回404给摄像头,或者不理会,再或者继续回复401要求验证密码等信息
    System.out.println(DigestUtils.md5Hex(ha1+":"+nonce+":"+ha2));
}
下一节心跳保活回复
  • 9
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用! 2.主要针对各个计算机相关专业,包括计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师或企业员工使用。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时私信沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip 基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip 基于PHP语言+GB28181标准实现的网络视频平台源码(实现SIP协议+信令服务器).zip
【资源介绍】 基于C++开发的国标GB28181流媒体信令服务器源码+使用说明.zip 1. 一个基于C++开发的国标GB28181流媒体信令服务器。 2. 采用BXC_SipServer+ZLMediaKit。可以搭建一个接收摄像头国标协议推流的国标流媒体服务,然后实现RTSP/RTMP/HTTP-FVL/HLS/WS/SRT等协议分发视频流。 3. BXC_SipServer负责信令模块,ZLMediaKit负责流媒体模块。 4. BXC_SipServer作为国标流媒体服务器信令模块。用于接收摄像头信令注册注册完成后, 主动向摄像头发送Invite请求,摄像头收到Invite请求后, 返回Invite的确认。 服务端收到确认后,发送ACK请求, 摄像头收到ACK请求后,开始通过RTP传输ps流推流至ZLMediaKit的国标RTP Server。 ZLMediaKit作为国标流媒体服务器的流媒体模块,主要用于接收摄像头国标推流和其他协议的分发。 5. 补充说明一下,这只是我为了讲课而开发的demo级的信令服务器,部分信令功能并没有实现。但是基本功能是没问题了,对接摄像头是完全没问题的, 对于学习国标流媒体信令服务器的新手,完全够用了,可以快速体验国标协议,通过wireshark抓包快速感受国标对接的流程。 linxu系统编译运行 ~~~ 一,首先安装osip和exosip,建议按照上面的BXC_SipServer介绍下载我推荐的 osip2-5.1.2和exosip2-5.1.2 1. 编译安装 osip2-5.1.2 cd osip2-5.1.2 ./configure make sudo make install 2. 编译安装 exosip2-5.1.2 cd exosip2-5.1.2 ./configure make sudo make install 二,开始 1. csdn下载代码 解压重命名 2. 编译BXC_SipServer cd BXC_SipServer mkdir build cd build cmake .. make 3. 运行 ./BXC_SipServer indows系统编译运行 一,osip和exosip编译到windows平台比较麻烦,我也是在编译过程中解决了多个报错,用了大半天时间,才编译出可用的本 如果你在windows平台自行编译osip和exosip,还需要编译c-ares库。推荐使用 c-ares-1.16.0 配合 osip2-5.1.2 和 exosip2-5.1.2 我已经将上面3个库编译好放在了3rdparty,并提供了 vs2019/x64/Debug 和 vs2019/x64/Release 二,只需要使用vs2019打开 BXC_SipServer.sln 选择 x64/Debug 或 x64/Release就能直接运行,依赖库都配置了相对路径 【备注】 该项目是个人毕设/课设/大作业项目,代码都经过本地调试测试,功能ok才上传,高分作品,可快速上手运行!欢迎下载使用,可用于小白学习、进阶。 该资源主要针对计算机、通信、人工智能、自动化等相关专业的学生、老师或从业者下载使用,亦可作为期末课程设计、课程大作业、毕业设计等。 项目整体具有较高的学习借鉴价值!基础能力强的可以在此基础上修改调整,以实现不同的功能。 欢迎下载使用,也欢迎交流学习!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值