为何NAT里面的主机可以访问NAT外的WEB服务器,但不能获取RTSP流媒体服务器码流?
原因:
对于 HTTP 这样的协议,客户端与 WEB 服务器建立 socket 连接, 是由 WEB 服务器绑定一个固定的 TCP 端口,在这个端口监听。 位于 NAT 后面的客户端随机选择一个 TCP 端口 connect(2)
WEB SERVER。
而对于 RTSP 流媒体服务器,采用 RTP 封装多媒体荷载的话,一般的实现是位于客户端这边的播放器看 UDP 5000 这个端口(也可定义为其他端口, 不过主流的播放器大多是这个端口)是否占用, 未占用则绑定这个端口;如果 UDP 5000 这个端口已经被占用了,则向上检查 UDP 5002(习惯上RTP使用偶数号端口,紧挨着的奇数号端口给RTCP使用)等,直到找到一个未使用的端口。然后请求流媒体码流前通过 RTSP 数据包交互告诉服务器播放器这边接收 RTP 数据包的端口, 流媒体服务器随机选择一个端口发送 RTP 包,并把即将启动的会话信息通过 SDP 格式以 RTSP 封装通知播放器。因此,对于 RTP 通信来说, 真正的服务端是在播放器这边,被动地接收 RTSP 服务器那边发送过来的 RTP 码流。
一般类型的 NAT 允许一个数据包通过的条件是:
-
如果这个数据包是从 NAT 里面的设备向外网发送的, 允许通过。NAT 设备会查看这个数据包的源ip地址与端口及目的地址与端口信息,如果没有建立过映射关系就新建一个NAT映射关系: 把源ip与端口映射为NAT地址与一个新分配的端口,根据NAT设备类型的不同,映射关系不同:
- 完全锥型、地址受限锥型和端口受限锥型是根据(源IP:源端口)建立一个(NAT地址:端口)的映射,
-
对称型NAT把(源IP:源端口,目的IP:目的端口)映射为(NAT IP:NAT 端口)。
-
如果这个数据包是从 NAT 外面发送给里面的,看这个数据包的信息能否在NAT映射表里找到对应的映射,找不到则禁止通过;若找的到,不同类型的 NAT 有不同的处理:
- 完全锥型: 允许通过。
- 地址受限锥型: 如果这个映射对应的NAT内部主机之前发送过数据包给这个外网主机,则允许通过,否则不允许。
- 端口受限锥型: 如果这个映射对应的NAT内部主机之前发送过数据包给这个外网主机的端口,则允许通过,否则不允许。
- 对称型: 因为映射就包含了源IP、源端口、目的IP、目的端口的所有信息,找到这个映射说明之前NAT里面对应的主机发过包给对应的外网主机,所以允许通过。
因此,位于 NAT 里面的主机可以这样正常的访问外网的 HTTP WEB服务器:发起HTTP请求时, NAT 会为这个会话建立一个映射关系,这样 WEB 服务器回应的包正好匹配这个映射,于是可以被客户端浏览器接收。
但是NAT里面的主机要请求流媒体就不行了:对于 RTSP 来说, 和 HTTP 一样可以正常通信, 关键是对于 RTP 包, 服务端是在 NAT 里面的播放器的,即使 RTSP 知道NAT里面播放器主机的地址以及NAT地址, RTP 包到达 NAT 时,属于上述的2的情况,由于之前没有建立映射关系,这个包将被 NAT 丢弃。
有的流媒体服务器会使用 HTTP 来封装流媒体,可以获取较好的穿透性, 不过需要实现自己的播放器才能解码了。
RTP 丢包的一个原因
用 UDP 封装的 RTP 传输视频码流, 播放出现花屏、马赛克的一个可能原因是丢包严重。UDP 在局域网 丢包不太可能是物理传输中的干扰(一般表现为 UDP 校验出错,可用 netstat -su
查看). 这里说说一个较常见的原因以及表现方式, 从Linux内核的角度来看, 就是 socket 缓存区满, 在播放器读取缓存区之前, 又一个视频码流数据过来, 那么, 这个udp数据包将丢失。
为什么会造成这个socket内核缓存区满呢? 有可能是下面的原因:
- 播放器的平均处理数据速度小于流媒体服务器端发包速度;
- 播放器平均处理速度大于对端发包速度, 但是发包速度抖动大, 而且内核socket读缓存区设置过小。
(待续)