路由协议 OSPF
最后更新于:2022-04-01 14:19:37
OSPF(Open Shortest Path First)是一个内部网关协议(Interior Gateway Protocol,简称IGP),一个链路状态路由选择协议,用于在单一自治系统(autonomous system,AS)内决策路由。
OSPF通过路由器之间通告网络接口的状态来建立链路状态数据库,生成最短路径树,每个OSPF路由器使用这些最短路径构造路由表。文档见RFC2178。
1、OSPF网络的特点是什么?ospf是一种链路状态路由协议,与距离矢量路由协议相对,它使用区域边界路由器和一个骨干区域,ospf定义的网络类型有:点到点、广播、非广播、点到多点等。
2、什么是区域边界路由器(ABR)?一个自治系统划分为多个区域,一个区域边界路由器连接同一个自治系统中的两个或者多个区域。
3、什么是骨干区域?骨干区域是一个与区域边界路由器相连接的区域,通常一个区域到另一个区域只能经过骨干区域。
4、ospf网络中有什么类型的路由器:骨干路由器、区域边界路由器、内部路由器、自治系统边界路由器(它连接两个自治系统)。
5 路由汇总:由区域边界路由器和自治系统边界路由器产生的路由的集合,它将向邻接的路由器通告。如果一个区域内的网络编号是连续的,那么区域边界路有器和自治系统边界路由器就能够被配置成通告路由,汇总路由指定了网络编号的范围。路由汇总减少了链接状态数据库的大小。
6 区域的类型:
- 短秃区域(stub):一种外部路由不流进的区域。所谓外部路由是指任何非ospf发起的路由,例如一条由其他路由协议发布的路由就是外部路由,外部路由通常在一个ospf互联网上泛洪式流过。如果一个区域只有一个出口,就几乎没有理由将大量路由流进该区域,只送一条缺省LSA路由到这个区域。通过该路由。短秃区域可以到达本自治区域以外的终端。
- 完全短秃区域。除了不将外部路由泛洪进该区域外,甚至连ospf概要路由也不进该区域。
7.有关csico路由器命令
全局设置
<table border="1" cellspacing="0" cellpadding="0"><tbody><tr><td valign="top"><p><span style="font-size:13px">任务 </span></p></td><td valign="top"><p align="left"><span style="font-size:13px">命令 </span></p></td></tr><tr><td valign="top"><p><span style="font-size:13px">指定使用OSPF协议 </span></p></td><td valign="top"><p align="left"><span style="font-size:13px">router ospf <em>process-id</em><sup>1</sup></span></p></td></tr><tr><td valign="top"><p><span style="font-size:13px">指定与该路由器相连的网络 </span></p></td><td valign="top"><p><span style="font-size:13px">network <em>address wildcard-mask</em> area <em>area-id</em><sup>2</sup></span></p></td></tr><tr><td valign="top"><p><span style="font-size:13px">指定与该路由器相邻的节点地址 </span></p></td><td valign="top"><p><span style="font-size:13px">neighbor <em>ip-address</em> </span></p></td></tr></tbody></table>
注:1、OSPF路由进程*process-id*必须指定范围在1-65535,多个OSPF进程可以在同一个路由器上配置,但最好不这样做。多个OSPF进程需要多个OSPF数据库的副本,必须运行多个最短路径算法的副本。*process-id*只在路由器内部起作用,不同路由器的*process-id*可以不同*。*
2、wildcard-mask是子网掩码的反码, 网络区域IDarea-id在0-4294967295内的十进制数,也可以是带有IP地址格式的x.x.x.x。当网络区域ID为0或0.0.0.0时为主干域。不同网络区域的路由器通过主干域学习路由信息。
8.基本配置举例:
Router1:
interface ethernet 0
ip address 192.1.0.129 255.255.255.192
!
interface serial 0
ip address 192.200.10.5 255.255.255.252
!
router ospf 100
network 192.200.10.4 0.0.0.3 area 0
network 192.1.0.128 0.0.0.63 area 1
!
Router2:
interface ethernet 0
ip address 192.1.0.65 255.255.255.192
!
interface serial 0
ip address 192.200.10.6 255.255.255.252
!
router ospf 200
network 192.200.10.4 0.0.0.3 area 0
network 192.1.0.64 0.0.0.63 area 2
!
Router3:
interface ethernet 0
ip address 192.1.0.130 255.255.255.192
!
router ospf 300
network 192.1.0.128 0.0.0.63 area 1
!
Router4:
interface ethernet 0
ip address 192.1.0.66 255.255.255.192
!
router ospf 400
network 192.1.0.64 0.0.0.63 area 1
!
**相关调试命令**:
debug ip ospf events
debug ip ospf packet
show ip ospf
show ip ospf database
show ip ospf interface
show ip ospf neighbor
show ip route
9. 使用身份验证
为了安全的原因,我们可以在相同OSPF区域的路由器上启用身份验证的功能,只有经过身份验证的同一区域的路由器才能互相通告路由信息。
在默认情况下OSPF不使用区域验证。通过两种方法可启用身份验证功能,纯文本身份验证和消息摘要(md5)身份验证。纯文本身份验证传送的身份验证口令为纯文本,它会被网络探测器确定,所以不安全,不建议使用。而消息摘要(md5)身份验证在传输身份验证口令前,要对口令进行加密,所以一般建议使用此种方法进行身份验证。
使用身份验证时,区域内所有的路由器接口必须使用相同的身份验证方法。为起用身份验证,必须在路由器接口配置模式下,为区域的每个路由器接口配置口令。
<table border="1" cellspacing="0" cellpadding="0"><tbody><tr><td valign="top"><p><span style="font-size:13px">任务 </span></p></td><td valign="top"><p align="left"><span style="font-size:13px">命令 </span></p></td></tr><tr><td valign="top"><p><span style="font-size:13px">指定身份验证 </span></p></td><td valign="top"><p align="left"><span style="font-size:13px">area <em>area-id</em> authentication [message-digest]</span></p></td></tr><tr><td valign="top"><p><span style="font-size:13px">使用纯文本身份验证 </span></p></td><td valign="top"><p><span style="font-size:13px">ip ospf authentication-key <em>password</em> </span></p></td></tr><tr><td valign="top"><p><span style="font-size:13px">使用消息摘要(md5)身份验证 </span></p></td><td valign="top"><p><span style="font-size:13px">ip ospf message-digest-key <em>keyid</em> md5 <em>key</em></span></p></td></tr></tbody></table>
以下列举两种验证设置的示例,示例的网络分布及地址分配环境与以上基本配置举例相同,只是在Router1和Router2的区域0上使用了身份验证的功能。:
例1.使用纯文本身份验证
Router1:
interface ethernet 0
ip address 192.1.0.129 255.255.255.192
!
interface serial 0
ip address 192.200.10.5 255.255.255.252
ip ospf authentication-key cisco
!
router ospf 100
network 192.200.10.4 0.0.0.3 area 0
network 192.1.0.128 0.0.0.63 area 1
area 0 authentication
!
Router2:
interface ethernet 0
ip address 192.1.0.65 255.255.255.192
!
interface serial 0
ip address 192.200.10.6 255.255.255.252
ip ospf authentication-key cisco
!
router ospf 200
network 192.200.10.4 0.0.0.3 area 0
network 192.1.0.64 0.0.0.63 area 2
area 0 authentication
!
例2.消息摘要(md5)身份验证:
Router1:
interface ethernet 0
ip address 192.1.0.129 255.255.255.192
!
interface serial 0
ip address 192.200.10.5 255.255.255.252
ip ospf message-digest-key 1 md5 cisco
!
router ospf 100
network 192.200.10.4 0.0.0.3 area 0
network 192.1.0.128 0.0.0.63 area 1
area 0 authentication message-digest
!
Router2:
interface ethernet 0
ip address 192.1.0.65 255.255.255.192
!
interface serial 0
ip address 192.200.10.6 255.255.255.252
ip ospf message-digest-key 1 md5 cisco
!
router ospf 200
network 192.200.10.4 0.0.0.3 area 0
network 192.1.0.64 0.0.0.63 area 2
area 0 authentication message-digest
!
**相关调试命令**:
debug ip ospf adj
debug ip ospf events
tcp/ip协议栈实现机制
最后更新于:2022-04-01 14:19:34
CSDN lidp [http://blog.csdn.net/perfectpdl](http://blog.csdn.net/perfectpdl)
tcp/ip协议栈属于操作系统内核层,通过提供系统调用供用户空间访问,从数据报到达最底层的网卡到最终传递给上层软件有一个过程,
当一个数据报到达时网络驱动程序把数据报放到一个队列中,同时发送一个消息给ip进程,这里ip进程是一个独立的程序,专门处理ip数据报,tcp/ip协议栈中,根据协议的功能及复杂程度,一般通过进程方式实现,而协议间的数据传递则借助于操作系统提供的进程间通讯机制,当ip进程接受了一个传入的数据报,他必须决定将其发往何处作进一步处理,如果数据报中的内容是一个报文段,则必须将其交付给TCP模块,如果他携带的是用户数据报(UDP),则必须将其交付给udp模块,以此类推。
由于TCP比较复杂,因而在许多设计方案中,有一个独立的进程来处理传入的TCP报文段,由于IP和TCP有各自独立的进程执行,因而IP和TCP必须借助进程间的通信机制来通信。
一旦tcp模块收到ip进程传送过来的报文段,就利用tcp协议端口号来寻找该报文段所属的连接,如果报文段中含有数据,TCP将把数据添加到与该连接相关的一个缓冲区中,并给发送方返回一个确认,如果输入的报文段中含有对放送出去的数据的确认,tcp输入进程还必须与tcp定时器管理进程通信,取消超时重发事件。
而处理udp数据报的进程结构与处理tcp进程采用的结构不同,由于udp比tcp要简单,udp模块不作为独立进程存在,事实上,它是由一些常规过程组成。ip进程通过调用来处理传入的udp数据报,这些过程检查udp目的站的协议端口号,根据端口号为udp数据报选择一个操作系统队列,ip进程把udp数据报放在响应的端口中,是应用程序可从这些端口中提取数据报。
TCP/UDP数据输入流程图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57393e861aa70.gif)
TCP数据报输出过程
与输入一样,tcp的输出也是很复杂的,必须先建立连接,所传送的数据必须放到报文段中,在对方发来确认之前,报文段必须不断重发,tcp把报文交给ip,由ip来处理选路和传送,系统协议栈利用两个tcp进程来处理这一复杂过程,一个为tcpout,由他来处理报文段和数据传输细节,另一个进程是tcptimer,他管理一个定时器,为超时重发事件定时,通知tcpout进程重发。下图为tcp, udp数据输出结构图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57393e87af91f.gif)
总结:tcp/ip协议栈是计算机操作系统中的一部分,它使用进程这一抽象概念使各个协议软件互相独立,每一个进程独立执行,并提供明确的并行机制,这种系统中有ip进程,tcp输入进程,tcp输出进程和tcp定时器管理进程,同时每个应用程序还各有一个进程。
上层应用程序作为独立进程通过系统调用向tcp传递数据流或者想udp数据报,对于udp输出处理,运行某个应用程序的进程调用一个系统调用,执行一段udp过程,申请一个ip数据报,填写相应的目的地址,将udp数据封装在Ip数据报中,然后将ip数据报传递给ip进程,由ip进程发送出去。
对于tcp输出,运行应用程序的进程调用一个系统调用(socket,send..),将数据传入操作系统中,并保存在一个缓冲区内,应用进程随后通知tcp输出进程,有新的数据等待发送,当tcp输出进程执行时,他将数据流划分成报文段,然后将这些数据封装在ip数据报中,最后,tcp输出进程将ip数据报放入某一端口中,ip进程将从该端口中提取并发送这些数据报。
dhcp和bootp协议
最后更新于:2022-04-01 14:19:32
csdn lidp [http://blog.csdn.net/perfectpdl](http://blog.csdn.net/perfectpdl)
与RARP类似,bootp和dhcp都是用于获取主机ip地址的协议,三者都是基于客户服务器模式,但由于RARP在低层操作,使用它就必须访问网络硬件,因此应用编程人员很难或者不太可能构造一个服务器。bootp及dhcp应运而生,bootp协议早与dhcp,但而这都是构建在传输层udp之上的协议,dhcp对bootp协议做了一些扩展及改进而成为事实上的标准而被广泛使用。
bootp协议使用udp传递报文,bootp获取自身地址的方式是构建在ip有限广播基础上的,即一个应用程序在发现本地网络活机器的ip地址前,可使用一个ip广播在本地广播数据报,在同一物理网络上的服务器将响应请求。
由于bootp构建在udp协议上,所以不可避免存在丢包,乱序现象,bootp使用传统的超时和重传机制解决此问题。
bootp协议设计用于相对静态的环境,其中每台机器都有一个永久连接,管理人员建立一个bootp配置问价,该文件定义了每台机器的异步bootp参数,由于配置通常保存不变,典型情况下配置将保持数星期不变,很明显这种静态配置方式有其局限性,比如计算机经常移动。
所以为处理自动地址分配,ietf 设计了一个新协议,即动态主机配置协议DHCP.
DHCP可以使主机使用一个报文获取所需的全部配置信息,如除了获取ip地址外,还能获取子网掩码,dhcp同样为客户服务其架构,所以为了使用dhcp的动态地址分配机制,管理员必须配置dhcp服务器,使其能提供一组ip地址,任何时候一旦有新的计算机连到网络上,该计算机就与服务器联系,并申请一个地址,服务器从管理员指定的地址中哦给选择一个地址,并将它分配给计算机。
dhcp客户端工作过程:
一台主机通过把报文广播给本地网上服务器而称为客户端,然后该主机收集服务器提供的地址,从中选择一个地址并验证服务器是否接受。
虽然dhcp可以获取主机的ip地址,但hdcp不与域名系统dns交互,因此,主机名与分配给主机的ip地址必须单独处理,如果要支持永久主机名,每当主机获取新的ip地址时dns就要动态更新名字到地址的绑定,这需要hdcp与dns交互,目前还没有动态更新dns的协议,因此还不会有dhcp在更新主机ip地址时维持永久主机名的协议。
ICMP协议笔记
最后更新于:2022-04-01 14:19:30
CSDN lidp [http://blog.csdn.net/perfectpdl](http://blog.csdn.net/perfectpdl)
前面文章了解了ip路由是怎样安排路由器转发数据报来提供可靠的无连接数据报交付服务,数据报从一个路由器传到另一个路由器,直到数据报到达某个能直接交付到最终目的的路由器,但问题是如果路由器不能选择路由或交付数据报,或者他检测到影响他发送的异常条件,则需要通知最初的源站采取措施避免或纠正问题,ICMP协议应运而生。
当目的机器临时或永久断链,寿命计数器超时或者中间路由器堵塞得无法处理传入的通信量时,ip都无法交付数据报,为了让互联网中的路由器报告错误或提供有关意外情况的信息,设计人员在TCP/IP中加入了网际控制报文协议ICMP,是IP的一部分,并且在每个IP实现中都必须包含他。
ICMP报文是放在IP数据报的数据部分中通过互联网传递的,icmp报文的最终处理着不是应用程序,而是该机器上处理他的网际协议的软件模块,即ICMP协议提供了两台机器上TCP/IP 协议软件间通信的方法。
需要注意的是,当数据报产生差错时,ICMP只能向数据报的最初源站回报差错情况。
总结:
icmp 协议为路由器和主机提供了正常情况以外的通信,他是ip的一个完整的必要组成部分,icmp包框降低传输速率的源站抑制,请求主机改变选路表的重定向报文以及主机可用来决定目的站是否可达的回送请求/应答报文(Ping),ICMP报文是在ip数据报的数据段中传输的,并且在报文开头有三个固定长度的字段:ICMP类型(type)字段,代码(code)字段及ICMP校验和字段。
tcp 协议TIME_WAIT状态详解
最后更新于:2022-04-01 14:19:28
上一篇文章提到执行主动关闭的一端进入 tcp TIME_WAIT状态,关于原因,unix网络编程卷一中给出两点:
1. 实现终止TCP全双工链接的可靠性,即保证tcp连接可靠断开。
2. 让老的重复分节在网络中消失
第一个原因的解释,执行主动关闭的一端最终会发ACK给对端,如果这个ACK最终丢失,那么发FIN的对端将由于在等待ACK超时后重发FIN分节,而如果主动关闭的一端在发完最终的ACK后清除此连接上状态信息,当再次收到对端的FIN分节时由于找不到对应的连接而报错,所以,如果TCP想彻底终止某个连接上两个方向的数据流(即全双工关闭),那么它必须处理连接终止序列四个分节中任何一个分节丢失的情况,这就是为何执行主动关闭的一端进入TIME_WAIT状态的第一个原因,因为它可能不得不重发最终的ACK分节给对端。
第二个原因,假设在一对ip及port上有一个连接,关闭这个连接后立即又建立起新的连接(同ip port),后一个连接为前一个的化身,因为ip port相同,tcp 必须防止来自某个连接的老分组在连接终止后再现,从而影响新连接的数据交互,所以tcp不能给处于TIME_WAIT状态的连接启动新的化身,从而被误解成属于同一连接。
TIME_WAIT状态的持续时间是最长分节生命期MSL(max segment lifetime)的2倍,即2MSL,既然tcp规定一个分节在网络中最大生存时间是 MSL,这足够让某个方向上的分节最多存活MSL秒即被丢弃,另一个方向的应答最多存活MSL秒也被丢弃,通过这个规则,就能保证当成功建立一个tcp连接时,来自该链接的之前所有连接的老的重复分组在网络中已消失。
每个TCP/IP 协议栈必须选择一个 MSL值,rfc 1122建议值为2分钟,而源自berkely的实现是30秒,需要注意的是如果此值过大,则在高并发服务器中tcp/ip协议栈必然要维护大量的TIME_WAIT状态的连接而消耗资源,所以一般作网络服务器优化时通常经过改小此值来减少维护此状态的资源。
详细设置及解决如下:
timeout_timewait 参数
描述:确定 TCP/IP 在释放已关闭的连接并再次使用其资源前必须经过的时间。关闭与释放之间的这段时间称为 TIME_WAIT 状态或者两倍最大段生存期(2MSL)状态。此时间期间,重新打开到客户机和服务器的连接的成本少于建立新连接。通过减少此条目的值,TCP/IP 可以更快地释放关闭的连接,并为新连接提供更多资源。如果正在运行的应用程序需要快速释放连接、创建新的连接,并且因为许多连接处于 TIME_WAIT 状态而导致低吞吐量,那么调整此参数。
如何查看或设置:
在我的ubuntu系统上此值为:
#cat /proc/sys/net/ipv4/tcp_fin_timeout
60
发出以下命令,将 timeout_timewait 参数设置为 30 秒:
#echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
tcp 链接终止过程
最后更新于:2022-04-01 14:19:25
TCP 用三个分节建立一个连接,终止一个连接则需要四个分节。
1. 某个应用进程首先调用 close,我们称这一端为执行主动关闭的一端,这一端TCP 发送一个FIN分节 FIN K,标是数据发送完毕。
2. 接收 到FIN的另一端执行被动关闭,这个FIN 由 tcp 确认 ack k + 1,他的接收也作为文件结束符传输给接收方应用进程,因为FIN的接收意味着应用进程在相应连接上再也接收不到额外数据。
3. 一段时间后,接收到文件结束符的应用进程将调用close关闭他的套接口,这导致它的tcp 也发送一个FIN, FIN J.
4. 接收到这个FIN的原发送方 TCP对他进程确认 ack j+1.
因为每个方向都要有一个FIN 和一个ACK, 所以一般需要四个分节,我们用限定词一般是因为有时步骤1的FIN 随数据一起发送,另外,执行被动关闭的那一端tcp在步骤2和3发出的ACK与FIN也可以合并成一个分节。
FIN占据一个字节的序列号空间,这与SYN相同,所以每个FIN的ACK是这个FIN的序列号加1.
在步骤2和3之间可以有从执行被动关闭端到执行主动关闭端的数据流,这称为半关闭(half-close),套接口关闭时每一端都要发送一个FIN,这种情况在应用进程调用close时会发生,但在进程终止时所有打开的描述符句柄将自动关闭,此时仍然打开的tcp链接也会发出一个FIN.
在连接关闭前,两端进程的tcp都处于ESTABLISHED状态,如果这时进程主动调用close(主动发起端),则发出FIN 分节,自身转换到FIN_WAIT_!状态,接收到FIN分节的对端则从ESTABLISH状态转换到 CLOSE_WAIT状态并发出ACK 给发出FIN端确认,然后从CLOSE_WAIT状态转换到 LAST_ACK,执行主动关闭的一端在收到对端的ACK时从FIN_WAIT_1 转换到FIN_WAIT_2等待对端发FIN分节,收到FIN分节后则从FIN_WAIT_2状态转换到TIME_WAIT状态并发送FIN 的ACK给对端,执行被动关闭的一端在收到ACK后从LAST_ACK状态转换到CLOSED状态,我们发现,主动发起关闭的一端进入TIME_WAIT状态,此状态是关于TCP网络编程中最难理解的部分,下一篇继续分析此部分。
不论是建立tcp 链接还是终止连接,都是在各个状态间的转换,一个tcp链接有11种状态,并且规定了如何基于当前状态及在该状态下所接收的分节从一个状态转换到另一个状态,比如当进程在 closed状态下执行主动打开时(connect),tcp将发送一个SYN分节,并从closed状态转换到 SYN_SEND状态,如果该tcp 接收到一个带有ACK 的SYN,他将发送一个ACK并转换成ESTABLISHED状态,这个状态是数据传送状态。tcp协议栈在实现过程中是用一个有限状态机(FSM)来管理这些状态的流转。
一个工具,查看系统中处于各个状态的个数:
#netstat -an|awk '/tcp/ {print $6}'|sort|uniq -c
xmpp 核心协议 rfc 6120
最后更新于:2022-04-01 14:19:23
rfc 6120 为ietf提出的替换原有xmpp/jabber 核心协议frc 3920
### 概述
可扩展的消息和出席信息协议(XMPP)是一个可扩展标记语言[XML](http://wiki.jabbercn.org/RFC6120#XML "RFC6120")应用,让任何两个或多个网络实体之间进行结构化和可扩展的准实时信息交流. 本文定义了XMPP的核心协议方法: XML流的配置和解除, 通道加密, 验证, 错误处理, 以及消息通讯基础, 网络可用性 ("presence"), 和 请求-应答 交互.
### 历史
XMPP的基本语法和语义最开始是由Jabber开源社区开发的, 主要是在1999年. 2002年, 根据 [IMP‑REQS](http://wiki.jabbercn.org/RFC6120#IMP.E2.80.91REQS "RFC6120") ,XMPP工作组被允许基于Jabber协议开发一个适合IETF的即时消息和出席信息技术. 到了2004年10月, 发布了 [RFC3920](http://wiki.jabbercn.org/RFC3920 "RFC3920") 和 [RFC3921](http://wiki.jabbercn.org/RFC3921 "RFC3921") , 意味着那时候XMPP的主要定义完成了.
从2004年开始,互联网社区已经获得了广泛的XMPP实现和布署经验, 包括XMPP标准基金会(XSF)主持下开展的正式的互操作性测试. 本文全面整合了从软件开发者和XMPP服务提供者得到的反馈, 包含了一系列向后兼容的修改, 结果是, 本文反映了互联网社区对于XMPP1.0核心功能的初步共识, 因此废止了[RFC 3920](http://tools.ietf.org/html/rfc3920 "http://tools.ietf.org/html/rfc3920").
### 功能汇总
这个不规范的章节提供了一个方便开发者的XMPP功能汇总; 接下来的其他章节则是XMPP的规范定义.
XMPP的目标是允许两个(或多个)实体通过网络来交换相关的小件结构化数据(所谓"XML节"). XMPP典型地使用分布式的 客户端-服务器 体系结构来实现, 这里客户端需要连接到一个服务器以获得对网络的访问,从而被允许和其他实体(可能在其他服务器上)交换XML节. 一个客户端连接到一个服务器,交换XML节,以及结束连接,这样的流程如下:
1. 确定要连接的IP地址和端口号, 典型的做法是对一个合格的域名做出解析( [3.2](http://wiki.jabbercn.org/RFC6120#.E5.90.88.E6.A0.BC.E5.9F.9F.E5.90.8D.E7.9A.84.E8.A7.A3.E6.9E.90 "RFC6120") )
1. 打开一个传输控制协议 [TCP](http://wiki.jabbercn.org/RFC6120#TCP "RFC6120") 连接
1. 通过TCP打开一个XML流 [4.2](http://wiki.jabbercn.org/RFC6120#.E6.89.93.E5.BC.80.E4.B8.80.E4.B8.AA.E6.B5.81 "RFC6120")
1. 握手最好使用传输层安全性 [TLS](http://wiki.jabbercn.org/RFC6120#TLS "RFC6120") 来进行通道加密( [5](http://wiki.jabbercn.org/RFC6120#STARTTLS.E6.8F.A1.E6.89.8B "RFC6120") )
1. 使用简单验证和安全层 [SASL](http://wiki.jabbercn.org/RFC6120#SASL "RFC6120") 机制来验证 ( [6](http://wiki.jabbercn.org/RFC6120#SASL.E6.8F.A1.E6.89.8B "RFC6120") )
1. 绑定一个资源到这个留上 ( [7](http://wiki.jabbercn.org/RFC6120#.E8.B5.84.E6.BA.90.E7.BB.91.E5.AE.9A "RFC6120") )
1. 和其他网络上的实体交换不限数量的XML节( [8](http://wiki.jabbercn.org/RFC6120#XML.E8.8A.82 "RFC6120") )
1. 关闭XML流 ( [4.4](http://wiki.jabbercn.org/RFC6120#.E5.85.B3.E9.97.AD.E4.B8.80.E4.B8.AA.E6.B5.81 "RFC6120") )
1. 关闭TCP连接
在XMPP中, 一个服务器可以选择性地连接到另一个服务器以激活域间或服务器间的通讯. 这种情形下, 两个服务器需要在他们自身之间建立一个连接然后交换XML节; 这个过程所做的事情如下:
1. 确定要连接的IP地址和端口号, 典型的做法是对一个合格的域名做出解析( [3.2](http://wiki.jabbercn.org/RFC6120#.E5.90.88.E6.A0.BC.E5.9F.9F.E5.90.8D.E7.9A.84.E8.A7.A3.E6.9E.90 "RFC6120") )
1. 打开一个TCP连接
1. 打开一个XML流 [4.2](http://wiki.jabbercn.org/RFC6120#.E6.89.93.E5.BC.80.E4.B8.80.E4.B8.AA.E6.B5.81 "RFC6120")
1. 握手最好使用TLS来进行通道加密( [5](http://wiki.jabbercn.org/RFC6120#STARTTLS.E6.8F.A1.E6.89.8B "RFC6120") )
1. 使用简单验证和安全层 [SASL](http://wiki.jabbercn.org/RFC6120#SASL "RFC6120") 机制来验证 ( [6](http://wiki.jabbercn.org/RFC6120#SASL.E6.8F.A1.E6.89.8B "RFC6120") ) *
1. 交换不限数量的XML节,可以服务器之间直接交换,也可以代表每台服务器上的相关实体来交换,例如那些连到服务器上的客户端 ( [8](http://wiki.jabbercn.org/RFC6120#XML.E8.8A.82 "RFC6120") )
1. 关闭XML流 ( [4.4](http://wiki.jabbercn.org/RFC6120#.E5.85.B3.E9.97.AD.E4.B8.80.E4.B8.AA.E6.B5.81 "RFC6120") )
1. 关闭TCP连接
- 互操作性提示: 在本文写就的时候, 大多数已布署的服务器仍使用服务器回拨协议 [XEP‑0220](http://wiki.jabbercn.org/RFC6120#XEP.E2.80.910220 "RFC6120") 来提供弱身份验证,而不是使用SASL的 PKIX证书来提供强验证, 特别在这些情况下,SASL握手无论如何将不会得到强验证 (例如, 因为TLS握手没有被对方服务器强制要求, 或因为当TLS握手时对方服务器提供的PKIX证书是自签名的并且之前没有被接受过); 细节请见 [XEP‑0220](http://wiki.jabbercn.org/RFC6120#XEP.E2.80.910220 "RFC6120") . 本文的解决方案显然提供了一个更高级别的安全性 (参见 [13.6](http://wiki.jabbercn.org/RFC6120#.E9.AB.98.E5.AE.89.E5.85.A8.E6.80.A7 "RFC6120") ).
本文指定了客户端如何连接到服务器以及基本的XML节语义. 然而, 本文不定义一个连接成功建立之后可能用来交换的XML节的"载荷"; 反之, 那些载荷被定义在各种XMPP扩展之中. 例如, [XMPP‑IM](http://wiki.jabbercn.org/RFC6120#XMPP.E2.80.91IM "RFC6120") 定义了基本的即时消息和出席信息功能的扩展. 另外, XSF创造了各种扩展协议,即XEP系列 [XEP‑0001](http://wiki.jabbercn.org/RFC6120#XEP.E2.80.910001 "RFC6120") ,也为广泛的应用程序定义了扩展.
### 术语
本文中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", 和 "OPTIONAL" 的解释参见[RFC 2119](http://tools.ietf.org/html/rfc2119 "http://tools.ietf.org/html/rfc2119") [关键字](http://wiki.jabbercn.org/RFC6120#.E5.85.B3.E9.94.AE.E5.AD.97 "RFC6120") .
特定的安全相关的术语的含义参见 [安全术语](http://wiki.jabbercn.org/RFC6120#.E5.AE.89.E5.85.A8.E6.9C.AF.E8.AF.AD "RFC6120") ; 这些术语包括但不限于, "assurance", "attack", "authentication", "authorization", "certificate", "certification authority", "certification path", "confidentiality", "credential", "downgrade", "encryption", "hash value", "identity", "integrity", "signature", "self-signed certificate", "sign", "spoof", "tamper", "trust", "trust anchor", "validate", and "verify".
特定的和证书,域名,应用服务身份相关的术语参见 [TLS‑证书](http://wiki.jabbercn.org/RFC6120#TLS.E2.80.91.E8.AF.81.E4.B9.A6 "RFC6120") ; 这包括但不限于, "PKIX certificate", "source domain", "derived domain", 以及身份类型 "CN-ID", "DNS-ID", 和 "SRV-ID".
其他安全相关的术语定义于参考协议中 (例如, "denial of service" (拒绝服务)定义于 [DOS](http://wiki.jabbercn.org/RFC6120#DOS "RFC6120") 或 "end entity certificate" (终端实体证书)定义于 [PKIX](http://wiki.jabbercn.org/RFC6120#PKIX "RFC6120") ).
术语 "whitespace" (空格) 用于指代 [XML](http://wiki.jabbercn.org/RFC6120#XML "RFC6120") 中任何匹配"S"的字符或字符串, 也就是说, 一个或多个满足 [ABNF](http://wiki.jabbercn.org/RFC6120#ABNF "RFC6120") 定义的SP, HTAB, CR, 或 LF 规则的实例.
术语 "localpart" (本地部分), "domainpart" (域部分), 以及 "resourcepart" (资源部分)定义于 [XMPP地址](http://wiki.jabbercn.org/RFC6120#XMPP.E5.9C.B0.E5.9D.80 "RFC6120") .
术语 "bare JID" (纯JID) 指代一个格式为 <localpart@domainpart> (对于一个位于某个服务器上的帐户而言) 或 <domainpart> (对于一个服务器而言) 的XMPP地址.
术语 "full JID" (全JID) 指代一个格式为 <localpart@domainpart/resourcepart> (对一个典型的已授权客户端或和某个帐号相关的设备而言) 或 <domainpart/resourcepart> (对于一个典型的资源或和某个服务器相关的文字)的XMPP地址.
术语 "XML stream" (也称为 "stream" (流)) 定义于 [4.1](http://wiki.jabbercn.org/RFC6120#.E6.B5.81.E7.9A.84.E5.9F.BA.E6.9C.AC.E5.8E.9F.E7.90.86 "RFC6120") .
术语 "XML stanza" (也称为 "stanza" (节)) 定义于 [4.1](http://wiki.jabbercn.org/RFC6120#.E6.B5.81.E7.9A.84.E5.9F.BA.E6.9C.AC.E5.8E.9F.E7.90.86 "RFC6120") . 有三种 stanzas(节): message, presence, 和 IQ ("Info/Query"的简称). 这些通讯原语分别定义于 [8.2.1](http://wiki.jabbercn.org/RFC6120#Message.E8.AF.AD.E4.B9.89 "RFC6120") , [8.2.2](http://wiki.jabbercn.org/RFC6120#Presence.E8.AF.AD.E4.B9.89 "RFC6120") , 和 [8.2.3](http://wiki.jabbercn.org/RFC6120#IQ.E8.AF.AD.E4.B9.89 "RFC6120") .
术语 "originating entity" (原实体)指的是第一次生成一个发送到XMPP网络的stanza(节)的实体(例如, 一个已连接的客户端, 一个附加的服务, 或一个服务器). 术语 "generated stanza" (生成的节)值的是生成的节那个节.
术语 "input stream" (输入流)指定这样一个XML流,服务器通过这个流从一个已连接的客户端或远端服务器接收数据, 而术语 "output stream" (输出流)指定这样一个流,服务器通过这个流发送数据到一个已连接的客户端或远程服务器. 以下术语指定一些动作,处理从输入流收到的数据时服务器可以执行这些动作:
route(路由):
传递数据到一个远端服务器让它自行处理或最终递送到一个和远端服务器关联的客户端
deliver(递送):
传递数据到一个已连接的客户端
ignore(忽略):
丢弃数据不做任何处理或返回一个错误给发送者sender
当术语 "ignore" (忽略)用于客户端处理收到的数据时, 短语 "without acting upon it" (不做任何处理)明确的包括不展示任何数据给使用者(人).
接下来的 "XML符号" 被 [IRI](http://wiki.jabbercn.org/RFC6120#IRI "RFC6120") 用于展示无法用仅用ASCII码呈现的字符, 本文的一些例子使用了类似 "&#x...." 的格式来表现 [UNICODE](http://wiki.jabbercn.org/RFC6120#UNICODE "RFC6120") 字符串 (例如, 字符串 "ř" 表示Unicode字符 LATIN SMALL LETTER R WITH CARON); 这种形式是绝对不会在XMPP系统将通过网络发送的.
和 [URI](http://wiki.jabbercn.org/RFC6120#URI "RFC6120") 展现统一资源定位符的规则一样, XMPP地址文本也是用 '<' 和 '>' 括起来的(尽管基本上它们不属于 URIs).
例如, 被括起来的行是用来提高可读性的, "[...]" 表示省略, 并且还是用了以下预定义字符串 (这些预定义的字符串不会通过网络发送出去):
- C: = 客户端
- E: = 任何XMPP实体
- I: = 发起实体
- P: = 对端服务器
- R: = 接收实体
- S: = 服务器
- S1: = 服务器1
- S2: = 服务器2
读者需要注意这些例子不包括细节, 并且例子里的一些协议流程中, 展示的备用步骤不一定是由前一个步骤发送的确切的数据触发的; 本文或常用参考文档中的协议规范所用到的所有用例里面提供的例子都遵从上述规则. 所有例子都是虚构的并且交换的信息 (例如, 用户名和密码) 不代表任何现存的用户和服务器.
### 体系结构
XMPP提供一种异步的端到端的结构化数据交换技术,在一个分布式的可全球寻址和出席信息感知的客户端和服务器的网络中使用直接的持久XML流。这种体系结构形式包含了普遍的网络可用性的知识,以及在给定的客户端-服务器和服务器-服务器会话的时候,不限数量的并发信息交易的概念,所以我们把它称为 "并发交易可用性" ("Availability for Concurrent Transactions") (简称ACT) 来把它和来自WWW的 "Representational State Transfer" [REST](http://wiki.jabbercn.org/RFC6120#REST "RFC6120") 体系结构形式区别开. 尽管XMPP的体系结构很大程度上类似于 email (参见 [EMAIL‑ARCH](http://wiki.jabbercn.org/RFC6120#EMAIL.E2.80.91ARCH "RFC6120"), 它引入了一些变化以便于准实时通讯. ACT体系结构形式的独特特性如下.
### 全局地址
和email一样, 为了通过网络路由和递送消息,XMPP使用全球唯一地址(基于DNS). 所有XMPP实体可以在网络上被寻址, 大部分客户端和服务器以及很多外部服务可以被客户端和服务器访问. 通常, 服务器地址的格式为 <域部分> (例如, <im.example.com>), 属于某台服务器的帐号的格式为 <本地部分@域部分> (例如, <juliet@im.example.com>, 称为 "纯JID"), 而连接到一个特定的设备或资源并且已经被(服务器)授权可以和外部交互的客户端的格式为 <本地部分@域部分/资源部分> (例如, <juliet@im.example.com/balcony>, 称为 "全JID"). 因为历史原因, XMPP地址常被称为Jabber IDs 或 JIDs. 因为XMPP地址格式的正式规范依赖于国际化技术(本文撰写时正在制定中),这个格式定义于 [XMPP‑ADDR](http://wiki.jabbercn.org/RFC6120#XMPP.E2.80.91ADDR "RFC6120") 而非本文之中. 术语 "localpart"(本地部分), "domainpart"(域部分), 和 "resourcepart"(资源部分) 正式定义于 [XMPP‑ADDR](http://wiki.jabbercn.org/RFC6120#XMPP.E2.80.91ADDR "RFC6120") .
### 出席信息
XMPP让一个实体能够向其他实体声明它的网络可用性或者 "presence"(出席信息) . 在XMPP中, 这种可通讯状态是用端到端的专用通讯元素来标识的: 即 <presence/> 节. 尽管网络可用性对于XMPP消息交换并不是必需的, 它还是可以促进实时交互,因为消息发起者可以在发消息之前知道接收者在线并处于可通讯状态. 端到端的出席信息定义于 [XMPP‑IM](http://wiki.jabbercn.org/RFC6120#XMPP.E2.80.91IM "RFC6120") .
### 持久流
每个点对点的一"跳"都建立了基于TCP长连接的持久XML流来保持可通讯状态. 这些 "always-on" 客户端-服务器 和 服务器-服务器 流使得任何时间每方都能够推送数据到另一方并且立即路由和递送. XML流定义于 [4](http://wiki.jabbercn.org/RFC6120#XML.E6.B5.81 "RFC6120") .
### 结构化数据
XMPP中基本的协议数据单元不是一个XML流 (它只是为点对点通讯提供传输层) 而是一个 XML 节("stanza"), 它是一个通过流发送的XML片段. 一个节的根元素包括路由属性 (类似 "from" 和 "to" 地址), 而节的子元素包含了递送给目标接收者的载荷. XML节定义于 [8](http://wiki.jabbercn.org/RFC6120#XML.E8.8A.82 "RFC6120") .
### 客户端和服务器的分布式网络
在实践之中, XMPP是一个包含了很多互相通讯的客户端和服务器的网络(当然, 任何两个给定的布署服务器之间的通讯都是严格谨慎的并且也和本地服务策略有关). 因此, 例如, 与服务器 <im.example.com> 关联的用户 <juliet@im.example.com> 能够和服务器 <example.net> 关联的用户 <romeo@example.net> 交换消息,出席信息和其他结构化数据. 这个模式对使用全局地址的消息协议是很常见的, 例如email网络 (见 [SMTP](http://wiki.jabbercn.org/RFC6120#SMTP "RFC6120") 和 [EMAIL‑ARCH](http://wiki.jabbercn.org/RFC6120#EMAIL.E2.80.91ARCH "RFC6120") . 结果, 在XMPP中端到端的通讯是逻辑上的点对点,而物理结构则是 客户端-服务器-服务器-客户端, 如下图所示.
**图1: 分布式客户端-服务器 体系结构**
~~~
example.net <--------------> im.example.com
^ ^
| |
v v
romeo@example.net juliet@im.example.com
~~~
sip info 方法 发起 h264关键帧请求
最后更新于:2022-04-01 14:19:21
csdn lidp [http://blog.csdn.net/perfectpdl](http://blog.csdn.net/perfectpdl)
有些视频终端只在呼叫刚建立时发几个关键帧,如果关键帧丢失,对方解码会会失败或出现马赛克现象,对此通过发送关键帧请求对方发送关键帧,
呼叫建立后,通过SIP INFO 扩展 在 dialog 内发送 关键帧请求,SIP INFO 构造参考 rfc 2976 http://www.ietf.org/rfc/rfc2976.txt,
info 消息体 带 xml 数据。具体参考 rfc http://www.rfc-editor.org/rfc/rfc5168.txt.
Doubango ims 框架 分析之 多媒体部分
最后更新于:2022-04-01 14:19:19
csdn lidp [http://blog.csdn.net/perfectpdl](http://blog.csdn.net/perfectpdl)
序言
RTP提供带有实时特性的端对端数据传输服务,传输的数据如:交互式的音频和视频。那些服务包括有效载荷类型定义,序列号,时间戳和传输监测控制。应用程序在UDP上运行RTP来使用它的多路技术和checksum服务。2种协议都提供传输协议的部分功能。
RTP本身没有提供任何的机制来确保实时的传输或其他的服务质量保证,而是由低层的服务来完成。它不保证传输或防止乱序传输,它不假定下层网络是否可靠,是否按顺序传送数据包。RTP包含的序列号允许接受方重构发送方的数据包顺序,但序列号也用来确定一个数据包的正确位置,例如,在视频解码的时候不用按顺序的对数据包进行解码。
1.介绍
doubango框架中tinyRTP文件夹实现RTP/RTCP/RTSP协议栈,目前只实现了
RTP,RTCP;RTSP还没实现。Rtp用来在网络上传输音频视频,协议栈实现时主要在音视频包的封装,拆包。
2.rtp包由消息头及消息体组成,消息头的结构封装
~~~
typedefstruct trtp_rtp_header_s文件trtp_rtp_header.h
{
TSK_DECLARE_OBJECT;
/*RFC 3550 section 5.1 - RTP Fixed Header Fields
0 1 2 3
01 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
unsignedversion:2;
//版本(V):2比特 此域定义了RTP的版本。此协议定义的版本是2。(值1被RTP草案版本使用,值0用在最初"vat"语音工具使用的协议中。)
unsignedpadding:1;
//填充(P):1比特 若填料比特被设置,则此包包含一到多个附加在末端的填充比特,填充比特不算作负载的一部分。填充的最后一个字节指明可以忽略多少个填充比特。填充可能用于某些具有固定长度的加密算法,或者用于在底层数据单元中传输多个RTP包。
unsignedextension:1;
//扩展
//扩展(X):1比特 若设置扩展比特,固定头(仅)后面跟随一个头扩展。
unsignedcsrc_count:4;
unsignedmarker:1;
//标志位
unsignedpayload_type:7;
//负载类型,即承载的语音编码类型
//负载类型(PT):7比特 此域定义了负载的格式,由具体应用决定其解释。协议可以规定负载类型码和负载格式之间一个默认的匹配。其他的负载类型码可以通过非RTP方法动态定义。RTP发送端在任意给定时间发出一个单独的RTP负载类型;此域不用来复用不同的媒体流。
uint16_tseq_num;
//序列号,重新组包
//序列号(sequencenumber):16比特 每发送一个RTP数据包,序列号加1,接收端可以据此检测丢包和重建包序列。序列号的初始值是随机的(不可预测),以使即便在源本身不加密时(有时包要通过翻译器,它会这样做),对加密算法泛知的普通文本攻击也会更加困难。
uint32_ttimestamp;
//时间戳,负责流同步
uint32_tssrc;
//同步源标识,32比特 用以识别同步源。标识符被随机生成,以使在同一个RTP会话期中没有任何两个同步源有相同的SSRC识别符。尽管多个源选择同一个SSRC识别符的概率很低,所有RTP实现工具都必须准备检测和解决冲突。若一个源改变本身的源传输地址,必须选择新的SSRC识别符,以避免被当作一个环路源。
uint32_tcsrc[15];
//贡献源标识
}
trtp_rtp_header_t;
~~~
3.rtp包结构,文件trtp_rtp_packet.h
~~~
typedefstruct trtp_rtp_packet_s
{
TSK_DECLARE_OBJECT;
trtp_rtp_header_t*header; //包头
struct{
void*data;
constvoid* data_const;
tsk_size_tsize;
}payload; //负载,即承载内容
/*extension header as per RFC 3550 section 5.3.1 */
struct{
void*data;
tsk_size_tsize; /* contains the first two 16-bit fields */
}extension;
}
trtp_rtp_packet_t;
~~~
rtp包的控制
上面两个结构用来标示一个rtp包,同时提供了包的解析,创建等函数。
结构trtp_manager_s负责rtp.rtcp包的管理,是更高层的抽象,上层应用直接通过trtp_manager_s提供的api控制
rtp包,比如在网络上发送音频数据,在音频session结构中包含trtp_manager_s用来管理经过封装的rtp包。
~~~
/**RTP/RTCP manager */
typedefstruct trtp_manager_s
{
TSK_DECLARE_OBJECT;
struct{
uint16_tseq_num;
uint32_ttimestamp;
uint32_tssrc;
uint8_tpayload_type;
char*remote_ip;
tnet_port_tremote_port;
structsockaddr_storage remote_addr;
char*public_ip;
tnet_port_tpublic_port;
constvoid* callback_data;
trtp_manager_rtp_cb_fcallback;
}rtp;
struct{
char*remote_ip;
tnet_port_tremote_port;
structsockaddr_storage remote_addr;
tnet_socket_t*local_socket;
char*public_ip;
tnet_port_tpublic_port;
constvoid* callback_data;
trtp_manager_rtcp_cb_fcallback;
}rtcp;
char*local_ip;
tsk_bool_tipv6;
tsk_bool_tstarted;
tsk_bool_tenable_rtcp;
tsk_bool_tsocket_disabled;
tnet_transport_t*transport;
}
trtp_manager_t;
~~~
4.tdav是音视频会话的抽象层,负责传输层的启动,音频会话,视频会话,各种编码的注册。
对于音频/视频会话(session)被tmedia_session_mgr_t管理,而tmedia_session_mgr_t则具体由sip信令控制会话的状态。比如sip客户端请求时通过tmedia_session_mgr_t构造自己的sdp信息要借助此结构,当客户端对invite作ACK应答时同样要指定自己的媒体信息。整个rtp流的启动入口都由tmedia_session_mgr_t控制。
各种媒体会话以插件的形式注册,如音频会话在启动时注册到tmedia_session_mgr_t的插件链表,并绑定start,stop,prepare回调。tmedia_session_mgr_t为sip信令控制媒体流的接口。
tmedia_session_plugin_def_t为音频视频抽象接口,指定回调。如音频会话,内部会实现相应的回调函数。
~~~
/**Virtual table used to define a session plugin */
typedefstruct tmedia_session_plugin_def_s
{
//!object definition used to create an instance of the session
consttsk_object_def_t* objdef;
//!the type of the session
tmedia_type_ttype;
//!the media name. e.g. "audio", "video", "message","image" etc.
constchar* media;
int(*set) (tmedia_session_t* , const tmedia_param_t*);
int(* prepare) (tmedia_session_t* );
int(* start) (tmedia_session_t* );
int(* pause) (tmedia_session_t* );
int(* stop) (tmedia_session_t* );
struct{/* Special case */
int(* send_dtmf) (tmedia_session_t*, uint8_t );
}audio;
consttsdp_header_M_t* (* get_local_offer) (tmedia_session_t* );
/*return zero if can handle the ro and non-zero otherwise */
int(* set_remote_offer) (tmedia_session_t* , const tsdp_header_M_t* );
}
tmedia_session_plugin_def_t;
tmedia_session_t为会话的抽象层,包含tmedia_session_plugin_def_t,
/**Base objct used for all media sessions */
typedefstruct tmedia_session_s
{
TSK_DECLARE_OBJECT;
//!unique id. If you want to modifiy this field then you must use @reftmedia_session_get_unique_id()
uint64_tid;
//!session type
tmedia_type_ttype;
//!list of codecs managed by this session
tmedia_codecs_L_t*codecs;
//!negociated codec
tmedia_codecs_L_t*neg_codecs;
//!whether the ro have been prepared (up to the manager to update thevalue)
tsk_bool_tro_changed;
//!whether the session have been initialized (up to the manager toupdate the value)
tsk_bool_tinitialized;
//!whether the session have been prepared (up to the manager to updatethe value)
tsk_bool_tprepared;
//!QoS
tmedia_qos_tline_t*qos;
//!bandwidth level
tmedia_bandwidth_level_tbl;
struct{
tsdp_header_M_t*lo;
tsdp_header_M_t*ro;
}M;
//!plugin used to create the session
conststruct tmedia_session_plugin_def_s* plugin;
}
tmedia_session_t;
sipsession
trtp_manager_t
tmedia_session_t tmedia_session_plugin_def_s
~~~
使用过程:
tdav_init注册音频,视频,多媒体session;注册支持的编码类型,注册支持的媒体信息承载类型(文本,流等)。
tdav_init-> register sessions, codecs.
tmedia_session_mgr_create-> tmedia_session_mgr_ctor,sessions,qos,sdp.
_tmedia_session_mgr_load_sessions,创建音视频会话。
tmedia_session_create,创建具体会话插件类型,tdav_session_video/audio_ctor
tmedia_session_init,初始化
tmedia_session_load_codecs,此会话支持的编码类型
tmedia_codec_create,穿件具体编码类型。
创建过程
准备阶段
trtp_manager_prepare,指定传输层接收数据回调trtp_transport_layer_cb
tdav_session_audio_prepare,trtp_manager_create,trtp_manager_set_rtp_callback
tnet_transport_create
tnet_transport_set_callback
启动
tmedia_session_mgr_start(),启动所有上面创建的会话类型,启动之前一定要设置sdp信息
(Startsthe session manager by
startingall underlying sessions.You should set
bothremote and local offers before calling this
session->plugin->start(),如视频会话启动 ,tdav_session_video/audio.c
trtp_manager_set_rtp_remote,设置对端ip,port,后续发送rtp包时构造包头用
trtp_manager_set_payload_type,设置此次会话用什么编码类型,编码类型通过协商后选择最佳
trtp_manager_start,启动rtp,rtcp包管理,
tnet_transport_start,启动传输层线程,绑定socket地址,开始接收udp数据, tnet_transport_mainthread
请求或响应中sdp与codec匹配过程
tmedia_session_match_codec->tmedia_codec_match_fmtp->tdav_codec_h264_fmtp_match->
tdav_codec_h264_get_profile(根据fmt获取对方的profile版本),
当发起外乎请求时codec与sdp处理关系,
发起invite或对方更改媒体信息时要把codec信息加载到sdp消息体中,
对于video,audio过程是一样的。
(videosession from codecs to sdp)
tdav_session_video_get_lo
|
tsdp_header_M_create(创建sdp媒体头)
|
tmedia_session_match_codec(此函数最终会返回一个协商成功的编码类型)
对于h264编码格式,此函数内部调用过程,遍历协议栈初始化时指定的编码链表,用此次请求的sdp消息体中的编码与自己的编码链表比较。->tmedia_codec_match_fmtp->tdav_codec_h264_fmtp_match->
tdav_codec_h264_get_profile
tmedia_session_match_codec返回协商成功的编码列表(即双方都支持的编码类型列表)后复制给协议栈,
self->neg_codecs= tmedia_session_match_codec
然后调用tmedia_codec_video_set_callback设置此编码类型对应的回调函数,当想发送rtp包时直接触发此回调函数即可完成发送rtp包的任务。
tmedia_codec_video_set_callback((tmedia_codec_video_t*)TSK_LIST_FIRST_DATA(self->neg_codecs),tdav_session_video_raw_cb, self);
tdav_session_video_raw_cb为具体的毁掉函数,内部为调用trtp_manager_send_rtp,发送rtp包。
值得注意的是传给函数的tdav_session_video_raw_cb数据只是未经过加工成rtp包的裸数据,tdav_session_video_raw_cb内部调用trtp_manager_send_rt,由trtp_manager_send_rt来把数据加工成rtp包,
然后调用传输层发送到网络上。
/*Encapsulate raw data into RTP packet and send it over the network
*Very IMPORTANT: For voice packets, the marker bits indicates thebeginning of a talkspurt */
inttrtp_manager_send_rtp(trtp_manager_t* self, const void* data,tsk_size_t size, uint32_t duration, tsk_bool_t marker, tsk_bool_tlast_packet)
trtp_manager_send_rtp内部又具体调用trtp_rtp_packet_create,创建rtp格式的数据包,包括rtp消息头的创建,初始化默认参数(version,marker,payload_type,seq_num等)。然后调用trtp_rtp_packet_serialize把rtp包序列化到一个buffer中。
trtp_manager_send_rtp最后调用tnet_sockfd_sendto传输层函数完成实际发送到网络上。
回到设置tmedia_codec_video_set_callback完毕后,tdav_session_video_get_lo调用tmedia_codec_to_sdp
把协商后的编码类型的信息转换成sdp格式的信息。
tmedia_codec_to_sdp(self->neg_codecs,self->M.lo);保存到M.lo属性,即本地的媒体信息。
tmedia_codec_to_sdp分析:
此函数的功能即把协商后的编码链表放到协议栈的sdp属性中,这样以后发送invite请求时就可以直接用。
/**@ingrouptmedia_codec_group
*Serialize a list of codecs to sdp (m= line) message.<br>
*Will add: fmt, rtpmap and fmtp.
*@param codecs The list of codecs to convert
*@param m The destination
*@retval Zero if succeed and non-zero error code otherwise
*/
inttmedia_codec_to_sdp(const tmedia_codecs_L_t* codecs, tsdp_header_M_t*m)
TSK_DEBUG_INFO("Serializea list of codecs to sdp (m= line) message/n");
tsk_list_foreach(item,codecs){
遍历每个编码类型,添加fmt,rtpmap属性,fmtp属性(tmedia_codec_get_fmtp,对于h264格式即调用tmedia_codec_h264_get_fmtp)
最后,tdav_session_video_get_lo内部在属性M.ro(即已经有请求的sdp信息)非空时考虑此请求是否为
保持还是接回,通过设置spd属性,sendrecv,sendonly来提示类型。最后,设置Qos信息。
流程tdav_session_video_get_lo
|
tsdp_header_M_create(创建sdp媒体头)
|
tmedia_session_match_codec
|
tmedia_codec_video_set_callback
|
tmedia_codec_to_sdp
但是
tdav_session_video_get_lo又是由谁触发的呢?tdav_session_video_get_lo为某一具体session的回调,
比如视频的session回调,音频的回调,视频,音频的session以plugin的方式挂在到session中。
/**Virtual table used to define a session plugin */
typedefstruct tmedia_session_plugin_def_s
{
//!object definition used to create an instance of the session
consttsk_object_def_t* objdef;
//!the type of the session
tmedia_type_ttype;
//!the media name. e.g. "audio", "video", "message","image" etc.
constchar* media;
int(*set) (tmedia_session_t* , const tmedia_param_t*);
int(* prepare) (tmedia_session_t* );
int(* start) (tmedia_session_t* );
int(* pause) (tmedia_session_t* );
int(* stop) (tmedia_session_t* );
struct{/* Special case */
int(* send_dtmf) (tmedia_session_t*, uint8_t );
}audio;
consttsdp_header_M_t* (* get_local_offer) (tmedia_session_t* );
/*return zero if can handle the ro and non-zero otherwise */
int(* set_remote_offer) (tmedia_session_t* , const tsdp_header_M_t* );
}
tmedia_session_plugin_def_t;
tdav_session_video_get_lo即为get_local_offer的具体回调。
get_local_offer被tmedia_session_get_lo调用。tmedia_session_get_lo又被tmedia_session_mgr_get_lo】
调用,正是上面提到的tmedia_session_mgr为管理session的抽象接口,用来与sip信令交互。
整个流程为:
tmedia_session_mgr
|
tmedia_session_get_lo
|
tdav_session_video_get_lo
|
tsdp_header_M_create(创建sdp媒体头)
|
tmedia_session_match_codec
|
tmedia_codec_video_set_callback
|
tmedia_codec_to_sdp
tmedia_session_mgr_get_lo又被谁触发呢?
刚才说了,是由sip协议栈调用的,具体有这样几个与sdp协商有关的sip点,我们知道,invite请求以及200ok应答,183响应,100响应的确认(prack)中有sdp信息:
(1)发送或者更新请求(invite)
send_INVITEorUPDATE
//send INVITE/UPDATE request
intsend_INVITEorUPDATE(tsip_dialog_invite_t *self, tsk_bool_t is_INVITE,tsk_bool_t force_sdp)
5.prack响应
//Send PRACK
intsend_PRACK(tsip_dialog_invite_t *self, const tsip_response_t* r1xx)
6.//Send ACK
intsend_ACK(tsip_dialog_invite_t *self, const tsip_response_t*r2xxINVITE)
初始请求中没有sdp信息,在ack中需要携带sdp信息
(4)发送响应时
/Send any response
intsend_RESPONSE(tsip_dialog_invite_t *self, const tsip_request_t*request, short code, const char* phrase, tsk_bool_t force_sdp)
2.处理请求中的sdp信息过程
tsip_dialog_invite_process_ro
|
tmedia_session_mgr_set_ro
tsip_dialog_invite_process_ro为sip信令中处理sdp信息的入口,在状态机的回调中适时调用
。比如在保持状态转到接回状态。
tsip_dialog_invite_process_ro会初始化mgr,启动,
tmedia_session_mgr_create,tmedia_session_mgr_set_ro,tmedia_session_mgr_set_natt_ctx,
tmedia_session_mgr_start。
Doubango ims 框架分析之 sip协议栈
最后更新于:2022-04-01 14:19:16
本人承接 sip/ims 视频客户端定制开发,提供IMS硬终端solution,支持接入sip软交换,ims核心网,支持 语音,视频,即时通信功能,视频格式支持 h263,h264,mpeg4 软编软解,提供硬件编解码接口对接,支持基于三星 sp5v210,trident, 海斯芯片H264 硬件编解码,提供服务器,有兴趣请联系我。
VOIP行业资讯和技术趋势请参考: www.voip123.cn
csdn lidp [http://blog.csdn.net/perfectpdl](http://blog.csdn.net/perfectpdl)
1.tinysip 介绍 :
兼容性 : SIP(RFC 3261) 以及 3GPPIMS/LTE (TS 24.229) implementation
依赖 tinySAK,tinyNET, tinySDP, tinyMEDIA, tinyHTTP and tinyIPSec.
2.SIP协议 - tinysip的实现机制
SIP是一个分层结构的协议,这意味着它的行为是根据一组平等独立的处理阶段来描述,每一阶段之间只是松耦合。协议分层描述是为了表达,从而允许功能的描述可在一个部分跨越几个元素。它不指定任何方式的实现。当我们说某元素包含某层,我们是指它顺从该层定义的规则集。
不是协议规定的每个元素都包含各层。而且,由SIP规定的元素是逻辑元素,不是物理元素。一个物理实现可以选择作为不同的逻辑元素,甚至可能在一个个事务的基础上。
SIP的最底层是语法和编码。它的编码使用增强Backus-Nayr形式语法(BNF)来规定。
第二层是传输层。它定义了网络上一个客户机如何发送请求和接收响应以及一个服务器如何接收请求和发送响应。所有的SIP元素包含传输层。
第三层是事务层。事务是SIP的基本元素。一个事务是由客户机事务发送给服务器事务的请求(使用传输层),以及对应该请求的从服务器事务发送回客户机的所有响应组成。事务层处理应用层重传,匹配响应到请求,以及应用层超时。任何用户代理客户机(UAC)完成的任务使用一组事务产生。用户代理包含一个事务层,有状态的代理也有。无状态的代理不包含事务层。事务层具有客户机组成部分(称为客户机事务)和服务器组成部分(称为服务器事务),每个代表有限的状态机,它被构造来处理特定的请求。
事务层之上的层称为事务用户(TU)。每个SIP实体,除了无状态代理,都是事务用户。当一个TU希望发送请求,它生成一个客户机事务实例并且向它传递请求和IP地址,端口,和用来发送请求的传输机制。一个TU生成客户机事务也能够删除它。当客户机取消一个事务时,它请求服务器停止进一步的处理,将状态恢复到事务初始化之前,并且生成特定的错误响应到该事务。这由CANCEL请求完成,它构成自己的事务,但涉及要取消的事务。
SIP通过EMAIL形式的地址来标明用户地址。每一用户通过一等级化的URL来标识,它通过诸如用户电话号码或主机名等元素来构造(例如:SIP:vision-com.com)。因为它与EMAIL地址的相似性,SIPURLs容易与用户的EMAIL地址关联。
SIP提供它自己的可靠性机制从而独立于分组层,并且只需不可靠的数据包服务即可。SIP可典型地用于UDP或TCP之上。
Register,Invite, Options …
Nat Tranversal
Dialog Layer
Transaction Layer
Parsing Layer
Transport Layer
sip 协议栈分层结构图
根据sip消息流向可以分为incomingmessage 和outgoing message,incoming 消息从 下到上,即TransportLayer → Register,Invite, Options; outgoing message 消息流向与此相反。
3.根据以上定义,tinysip分如下模块:
1).api外部接口,对sip协议支持的方法的接口封装,协议栈提供的发起请求及接受请求对应的接口,包括registar layer, presencelayer等上层应用,当前版本支持如下请求:
REGISTER,SUBSCRIBE(订阅),MESSAGE(即时通信),PUBLISH(状态展示),OPTIONS(查询服务器能力),INVITE(发起请求),Cancel(取消一个请求),
BYE(结束通话)。
2).Nat traversal :Nat穿越层,tinysip目前支持stun,turn穿透。
3).Dialog,会话模块,一路呼叫的唯一标识,处于sip事务层之上。
4).parsers,sip消息解析,处于sip 语法层,解析从传输层传递的数据包为协议栈理解的结构。
5).transactions, 事务层,事务是一个请求以及与此请求相关的所有响应组成,用transaction id唯一标识,由于sip信令一般由udp承载,所以不能保证信息的可靠到达,所以事务层必须提供一种机制处理udp所不能提供的功能,这里一般通过定时器及一个有限状态机来实现。
6).transports ,传输层,即 udp, TCP, TLS, SCTP socket 系统调用系列,此层隐藏了所有传输层的细节,对于incomingsip message , 此层为sip消息的入口,对于outgoingsip message, 此层为sip消息的出口。
4.doubango sip 协议栈使用流程:
1).初始化
doubango sip协议栈依赖于tinyNET模块,所以必须先调用tnet_startup函数初始化,退出时调用tnet_cleanup清除资源,初始化sip
协议栈之前必须设置用户的域(realm参见(1))及用户的私有(IMPI(2))及共有标别(IMPU(3)),这些为ims引入的概念。
(1)realm 解释:reaml为域名,用来作客户端认证用(authenticate).必须是一个有效的 sipuri 如:sip:vision-com.com.cn,realm 为sip协议栈启动之前必须设置的选项,一旦协议栈启动realm就不可以更改,如果不填写sip代理服务器地址,则系统会用realm通过 dns NAPTR + SRV 或者DHCP(还没实现)动态查找机制确定sip 服务器地址。
(2)用户私有标识,为用户所属网络赋予的唯一值,用来做验证,为IMS中的概念,如果用doubangosip协议栈作为普通sip功能,即非IMS网络中的sip功能,此处的impi意义与sip协议栈中的验证域名相同,私有ID用在身份认证,授权等,主要是安全方面的作用。
(3)用户公有标识,ims网络中一个impi可以对应多个impu.,公有ID用在业务配置、计费等,主要是业务方面的作用。
更进一步解释:
IMPU,它更靠近业务层,用于标识业务签约关系,计费等,还表示用户身份以及用于路由,但它不能表示用户实际的位置信息,当然这个是对于非传统固话来说,传统固定方式下用户号码与位置存在绑定关系,因为用户线接入是固定的。
然后是IMPI,它是网络层的东东,用于表示用户和网络的签约关系,一般也可以唯一的表示一个终端。使用IMPI,网络可以通过鉴权来识别用户是否可以使用网络。
最后是用户的联系地址,这个是注册的时候要带的,它是用户真实的寻址地址,IMPU和联系地址分离才能支持移动性,对于传统GSM网络,漫游号起到了类似联系地址的作用,你也可以认为GSM中的位置更新也是一种注册流程。
注册后S-CSCF知道IMPU以及联系地址,当有人呼叫此IMPU,根据联系地址即可找到实际的用户,因此注册维护了一个用户的寻址通路。
2).创建以及启动
通过调用 tsip_stack_create创建协议栈, 调用tsip_stack_start启动,
完整例子:
~~~
tsip_stack_handle_t*stack = tsk_null;
intret;
constchar* realm_uri = "sip:vision-com.com.cn";
constchar* impi_uri = "lideping@vision-com.com.cn";
constchar* impu_uri = "sip:bob@vision-com.com.cn";
//...必须先初始化tnet工具
tnet_startup();
// ...
//创建协议栈,指定回调函数,参数为域名,公有及私有标识。
stack= tsip_stack_create(sip_callback, realm_uri, impi_uri, impu_uri,
TSIP_STACK_SET_PASSWORD("yourpassword"),
//...other macros...
//此处初始化其他信息,比如代理服务器地址,编码信息等。
tsip_stack_start(stack) //启动协议栈
TSIP_STACK_SET_NULL());//用来终止传给app_callback的参数。
TSK_OBJECT_SAFE_FREE(stack);
tnet_cleanup();
//释放资源
//事件回调
intsip_callback(const tsip_event_t *sipevent)
{
//事件类型
switch(sipevent->type){
casetsip_event_register:
{ /*REGISTER */
break;
}
casetsip_event_invite:
{ /*INVITE */
break
}
casetsip_event_message:
{ /*MESSAGE */
break
}
casetsip_event_publish:
{/* PUBLISH */
break
}
casetsip_event_subscribe:
{ /*SUBSCRIBE */
break
}
casetsip_event_options:
{ /*OPTIONS */
break
}
casetsip_event_dialog:
{ /*Common to all dialogs */
break
}
/*case …*/
}
~~~
会话事件,协议栈事件放入事件队列,协议栈启动时创建一个线程不断从队列里取事件,然后通过调用事件回调通知上层用户。
5.代码分析
首先调用tsip_stack_create创建协议栈,tsip_stack_create内部首先检查参数是否合法,然后创建协议栈结构tsip_stack_t,设置realm,IMPI and IMPU,初始化一些协议栈默认值。创建SigComp 信令压缩模块(可选)创建dns处理模宽,DHCPcontext ,接下来创建上面提到的sip协议栈各层,分别调用tsip_dialog_layer_create,tsip_transac_layer_create,tsip_transport_layer_create创建会话层,事务层及传输层。至此,创建协议栈毕。
在真正启动协议栈之前,即tsip_stack_create与tsip_stack_start之间可调用协议栈提供的api初始化其他参数,然后调用tsip_stack_start启动协议栈,首先启动定时器线程,这里定时器主要在事务层提供状态机功调度功能。然后设置传输层类型,设置是否用ipsec把sip信令加密,然后如果协议栈是处于客户端模式并且代理服务器地址没有设置则用认证的域名查找代理服务器的地址(用SNAPTR+SRV),然后设置Runnable回调,启动run 线程,run内部不断从消息队列里取消息,这里的消息是从传输层从下到上传送过来,最终串联到消息队列,然后调用协议栈创建时指定的回调sip_callback,所有incomingsip消息以及媒体信息的改变最终都会走到此回调函数,此函数内部根据消息类型的不同调用相应的handler,接下来启动nat穿越模块,设置stun地址。然后调用tsip_transport_layer_start启动传输层线程,在sip端口5060接收数据,最后,设置stack->started= tsk_true; 至此协议栈启动完毕,各层在相应端口或状态机上监听,不断轮询到来的事件并处理。
驱动过程:
协议栈启动完毕后,对于每一个incoming及outgoing 消息的入口不一样,下面分别分析对于呼入请求(incoming)及外乎请求(outgoing)的代码流程。
a.呼入请求
1)客户端传输层在5060端口上接收到udp包,语法层解析成识别的sip消息后传给事务层。
2)事务层锁住本地事务链表,根据sip消息的事务id在事务链查找是否存在匹配的事务,没有则创建。
3)一旦找到事务或创建新事务完毕,释放锁并把消息传递到会话层。
4)会话层收到sip消息后查找会话链,找不到则创建会话,同时根据消息类型(invite,ack 等)设置此消息的状态机,状态机内指定具体事件的回调。
b.呼出请求
1)构造外乎请求,包括消息头和消息体.
2)创建会话层,事务层,事务层调用传输层接口发出请求。
6. 外部编程接口
为了在android上层通过java访问doubango核心,imsdroid对doubango voip框架做了面向对象封装,根据具体模块功能抽象成具体Java类供应用层使用,应用层通过jni访问doubango核心,同时,在imsdroid2.0版本中,根据android上应用层的架构抽象出一个类库,doubango-ngn-stack,利用此类库我们可以在android上自己开发一些客户端应用程序,包括语音,视频,即时通信,多媒体共享,会议等应用。Imsdroid2.0即是构建在 doubango-ngn-stack上的一个具体应用。
Doubango-ngn-stack原理
doubango-ngn-stack是对doubangovoip框架的一个java层封装,内部通过java本地调用技术实现(jni),这与android上的框架设计是相符的(如java类库提供的摄像头功能即依赖于底层驱动,上层通过jni访问底层驱动),
doubango/bindings/java
用SWIG工具把c/C++函数封装成JAVA中的类,目前SWIG已经可以支持Python,Java, C#,Ruby,PHP,等语言。
7. 代码实例分析
注册过程
1). 注册流程(java-->C++-->C)
register(NgnSipService.java)
|
register(NgnRegistrationSession.java)
|
register_(sipsession.cxx)
|
tsip_action_REGISTER(stip_api_register.c)
tsip_action_REGISTER
分三步:
_tsip_action_create创建注册请求, tsip_dialog_layer_new 创建注册session,为后续进入状态机作准备, tsip_dialog_fsm_act 进入状态机模式。
1._tsip_action_create
创建注册请求,这里只是创建一个抽象的请求,请求对应sip的方法,sip协议定义了
register,invite,publish,subscribe, bye,message等方法。
然后调用_tsip_action_set初始化上层(java)传过来的参数。
1.
请求创建成功后创建会话层tsip_dialog_layer_new
sip协议中每个请求方法对应一个会话,即session,此函数根据会话类型创建相应会话的session.对于注册会话,会调用tsip_dialog_register_create创建会话层,然后把创建后的会话保存到协议栈的会话层链表。
(1)tsip_dialog_register_create(tsip_dialog_register.c)创建注册会话
内部new一个注册对象tsip_dialog_register_def_t,构造函数中执行如下动作。
tsip_dialog_register_ctor内部分三步:
a.tsip_dialog_init 初始化基本的会话信息,这里分客户端及服务器端。同时会创建此会话的状态机,并初始化状态机的状态。
b.tsk_fsm_set_callback_terminated 设置注册会话结束状态机回调。
c.tsip_dialog_register_init 初始化具体注册会话信息
tsip_dialog_register_client_init初始化注册请求的客户端状态机回调。
这里实际上是当一个请求过程中,当请求从一个状态到另一个状态时应该调用的回调函数。
tsip_dialog_register_server_init功能相同。
tsip_dialog_register_event_callback设置从传输层过来的注册事件的回调,通知上层。
3. tsip_dialog_fsm_act,是所有sip会话开始进入状态机的入口。
参数为 会话,会话类型,sip消息,请求。
此函数内部会调用状态机通用函数tsk_fsm_act。执行一个具体的请求,同时可能改变相应会话的状态机的状态。
inttsk_fsm_act(tsk_fsm_t* self, tsk_fsm_action_id action, const void*cond_data1, const void* cond_data2, …)
内部是一个循环,不断检测状态,根据状态调用相应的回调。
每个状态机都有一个初始状态作为运转入口,对于注册请求,入口为,
TSK_FSM_ADD_ALWAYS(_fsm_state_Started,_fsm_action_oREGISTER, _fsm_state_InProgress,tsip_dialog_register_Started_2_InProgress_X_oRegister,"tsip_dialog_register_Started_2_InProgress_X_oRegister"),
tsk_fsm_act一开始会执行tsip_dialog_register_Started_2_InProgress_X_oRegister回调,
此回调是由状态_fsm_state_Started到_fsm_state_InProgress时执行的操作,
内部先更改自己的当前状态为_fsm_state_InProgress,这样给tsk_fsm_act 提供了调用下一个状态的入口。
函数内部调用tsip_dialog_register_send_REGISTER 创建事务层,初始化事务层状态机,最后调用传输层socket接口把请求发送出去。
由于sip根据请求的类型把事务层分为几种类型,包括客户端请求(invite)事务,客户端非请求事务(如bye,register),服务器端请求事务,服务器端非请求事务。
所以对于注册请求,会创建非请求客户端事务层。
tsip_dialog_register_send_REGISTER---> tsip_dialog_request_new
--->tsip_dialog_request_send---->tsip_transac_layer_new--->tsip_transac_start
--->tsip_transac_nict_start,进入事务层状态机模式。
这里,在创建事务层时会设置事务层事件回调,tsip_transac_nict_event_callback。
比如发出register请求,服务器端给200ok响应。此时传输层会把此响应作为事件给事务层,事务层收到此事件,解析后进入事务层状态机。
所以对于 一次 sip请求,有两个状态机在运转,一个为会话层状态机,一个为事务层状态机。
至此,一个register请求已经发送出去,会话层,事务层状态机都在运转,
此时,如果服务器返回注册成功,则会给客户端传输层发送200OK响应。
tsip_transport_layer_dgram_cb,tsip_transport_layer_handle_incoming_msg,
tsip_transac_layer_handle_incoming_msg,tsip_transac_layer_find_client
客户端传输层收到响应后会根据响应的事务id查找是否为已经创建的事务。
,找到后会根据此事务创建时指定的回调,调用相应事务层的回调函数,这里为tsip_transac_nict_event_callback,此函数内部根据消息类型调用tsip_transac_fsm_act执行事务层状态机,比如 200ok ,会调用事务层创建时指定的回调。这里为tsip_transac_nict_Trying_2_Completed_X_200_to_699。
内部调用会话层回调通知上层注册成功。tsip_dialog_register_event_callback
此函数根据状态调用相应会话层状态机,tsip_dialog_register_InProgress_2_Connected_X_2xx,修改状态机当前状态,为下一个状态作准备,最后调用TSIP_DIALOG_REGISTER_SIGNAL发射 注册成功事件给上层用户。
这里事件机制:TSIP_DIALOG_REGISTER_SIGNAL 通过tsip_register_event_signal创建一个注册事件,然后把此事件放入协议栈启动时的事件队列中,
tsip_stack_start内部启动run线程处理协议栈事件。
/* ===Runnable === */
TSK_RUNNABLE(stack)->run= run;
if((ret= tsk_runnable_start(TSK_RUNNABLE(stack), tsip_event_def_t))){
stack_error_desc= "Failed to start timer manager";
TSK_DEBUG_ERROR("%s",stack_error_desc);
gotobail;
}
在run线程中不断扫描事件队列,pop出一个事件,然后送给sip协议栈创建时指定的事件回调,从而把协议栈事件(事务层事件,会话层事件,协议栈事件)返回给上层用户。
至此,一次正常(没考虑异常,401认证过程等)的注册流程分析完毕。
2.)外乎流程:
screenAV.java
publicstatic boolean makeCall(String remoteUri, NgnMediaType mediaType){
makeCall(StringremoteUri, NgnMediaType mediaType)
|
createOutgoingSession
|
makeCall(remoteUri);(NgnAVSession.java)
|
callAudioVideo
|
callAudioVideo(sipsession.cxx)
|
__droid_call(sipsession.cxx)
|
__droid_call_thread(sipsession.cxx)
|
tsip_action_INVITE(tsip_api_invite)
至此,由上层调用到底层协议栈。
同样分三步:
_tsip_action_create
tsip_dialog_layer_new
tsip_dialog_fsm_act
从创建会话层开始分析:
tsip_dialog_layer_new
tsip_dialog_invite_create
tsip_dialog_invite_ctor(tsip_dialog_invite.c)
构造函数内过程:
(1)tsip_dialog_init创建相关头域,创建invitesession状态机并初始化状态。
(2)tsk_fsm_set_callback_terminated设置 invite会话状态机结束的回调函数tsip_dialog_invite_OnTerminated。
(3)tsip_dialog_invite_init初始化invite请求本身,具体如下:
a.tsip_dialog_invite_client_init初始化服务器端invite会话信息,实际上为设置客户端invite会话的状态机。
Started-> (send INVITE) -> Outgoing-> Connected ......
状态转换过程中调用相应回调。
b.tsip_dialog_invite_server_init 设置服务器端invite会话状态机。
//Started -> (Bad Extendion) -> Terminated
//Started -> (Bad content) -> Terminated
//Started -> (Session Interval Too Small) -> Started
..........
1.
tsip_dialog_invite_hold_init设置 hold 状态机,3GPPTS 24.610:
CommunicationHold。
d.tsip_dialog_invite_stimers_init ,设置sessiontimer 状态机,rfc RFC
4028:SessionTimers
e.tsip_dialog_invite_qos_init,设置 qos状态机,RFC 3312
f.初始化其他状态机。
TSIP_DIALOG(self)->callback= TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_invite_event_callback);
设置invite会话层事件回调,比如服务器响应,则会调用此回调,内部根据响应类型调用状态机,根据状态执行相应回调。
tsip_dialog_fsm_act,所有准备阶段完成后,第三步进入状态机模型,轮转吧。
第一个调用的状态机回调为c0000_Started_2_Outgoing_X_oINVITE,
Started-> (oINVITE) -> Outgoing。
此函数内部:
首先调用tmedia_session_mgr_create初始化自己的媒体信息,后面放到invite请求的
sdp里面。
接下来更新此次请求的状态机阶段,这样下一个状态机回调以此为起点。
然后设置invite请求的一些特殊头域。tmedia_session_mgr_set_qos,/*100rel */
self->supported._100rel等。
最后调用send_INVITE,TSIP_DIALOG_SIGNAL产生invite事件通知上层用户。
send_INVITE又调用send_INVITEorUPDATE
内部又分如下过程。
a.tsip_dialog_request_new,创建一个通用的sip 请求。
b.tmedia_session_mgr_get_lo创建invite消息的消息体,即sdp信息。
1.
初始化其他头域
tsip_dialog_request_send发送请求。 创建事务层。
tsip_transac_layer_new,tsip_transac_ict_create
tsip_transac_ict_ctor初始化 客户端invite事务层:
tsip_transac_init
tsk_fsm_set_callback_terminated
tsip_transac_ict_init
最终开启客户端请求事务状态机。
tsip_transac_start
当服务器端返回 200ok后,会调用事务层,事务层又转给会话层,最终转到
c0000_Outgoing_2_Connected_X_i2xxINVITE回调函数,
内部对200ok响应做出里:
tsip_dialog_update更新会话状态,
tsip_dialog_invite_process_ro处理对端 sdp,建立rtp流。
send_ACK 给 ACK响应。
TSIP_DIALOG_INVITE_SIGNAL最后给上层发射 事件,通知用户接通。
前言
最后更新于:2022-04-01 14:19:14
> 原文出处:[网络协议专栏](http://blog.csdn.net/column/details/protocol.html)
作者:[voipmaker](http://blog.csdn.net/voipmaker)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 网络协议专栏
> 分析各种网络协议的原理及实现,包括 ip,icmp,tcp,udp,ospf,sip,rtp,xmpp等。