美文网首页
Live555 源代码分析(二)

Live555 源代码分析(二)

作者: RonZheng2010 | 来源:发表于2020-09-20 17:32 被阅读0次

1 Socket操作

Live555对常用的socket操作进行了包装。

1.1 setupDatagramSocket()

setupDatagramSocket()创建UDP socket。

  • 调用createSocket()创建UDP socket
  • 调用setsockopt()设置特性:重用地址和端口
  • 调用MAKE_SOCKADDR_IN()构造地址
  • 调用bind()将socket绑定在这个地址

1.2 setupStreamSocket()

setupStreamSocket()创建TCP socket。

  • 调用createSocket()创建TCP socket
  • 调用setsockopt()设置特性:重用地址和端口
  • 调用MAKE_SOCKADDR_IN()构造地址
  • 调用bind()将socket绑定在这个地址
  • 调用fcntl()将socket设置为非阻塞模式。

1.3 increaseSendBufferTo()

increaseSendBufferTo()调用setsockopt(),增加发送缓存的大小。

1.4 writeSocket()、readSocket()

writeSocket()和readSocket()分别发送和接收数据包。

1.5 ourIPAddress()

ourIPAddress()得到本地地址。

  • 调用setupDatagramSocket()创建UDP Socket
  • 调用socketJoinGroup(),将socket加入指定组播组。
  • 向组播组发送数据包。因为socket在这个组内,所以能收到自己发出的数据包。、
  • 调用select()开始监听。数据到达后返回。
  • 调用readSocket()接收包,同时接收数据包的接收地址。这就是本地地址。
  • 调用socketLeaveGroup(),退出组播组。
  • 调用close(),关闭socket。

2 RTP数据收发

2.1 RTPInterface

RTPInterface负责发送、接收数据包。

TCP协议和UDP协议都可以承载数据包。一个RTPInterface实例可以同时支持多路TCP数据和多路UDP数据。对于TCP,一个TCP连接上可以有多个channel,每个channel对应一路数据。

RTPInterface自己处理TCP部分。这其中需要借助tcpStreamRecord和SocketDescriptor。

RTPInterface将UDP部分委托给Groupsock,一个GroupSock实例处理多个UDP连接。

  • 成员fGS是Groupsock实例,用于支持UDP。
  • 成员fTCPStreams是tcpStreamRecord链表。 每个tcpStreamRecord实例对应一个TCP socket上的channel。

2.2 TCP连接上的数据格式

因为不同channel共用同一个TCP连接,所以SocketDescriptor发送数据包时,会加上一个数据包头,其中对不同channel加以区分。

TCP连接上的数据格式如下图所示。

  • 符号$占一个字节,是数据包开始标志。
  • channel Id占一个字节,是channel的编号。
  • 数据包大小占两个字节。Size high 和size low分别是高位字节和低位字节。
  • data是调用者的真正的数据。

2.3 用tcpStreamRecord发送TCP数据

RTPInterface::addStreamSocket()创建tcpStreamRecord实例,并注册到对应的SocketDescriptor实例中,以便监控socket的可读数据。

  • 使用socket句柄和channel id创建tcpStreamRecord实例,保存在fTCPStreams中。
  • 调用lookupSocketDescriptor(),查找与socket对应的SocketDescriptor实例。
  • 调用SocketDescritor::registerRTPInterface(),向SocketDescriptor实例注册RTPInterface实例自身,这里要提供channel编号。
    • 这里将socket置于监听状态。当有数据可读时,RTPInterface的成员fReadHandlerProc将被调用。

RTPInterface::sendRTPorRTPPacketOverTCP(),向单个TCP连接发送数据包。

  • 两次调用sendDataOverTCP(),依次发送包头部分($ + channel id + size high + size low),和数据部分(data)。

2.4 用SocketDescriptor接收TCP数据

_Tables的成员socketTable保存了全局唯一的SocketDescriptor的hash表。

SocketDescriptor管理一个TCP socket,以及承载在它上面的channel。

对于SocketDescriptor,

  • 成员fOurSocketNum是监听的socket。SocketDescriptor自己不创建这个socket,使用者通过它的构造函数传给它。
  • 成员fSubChannelHashTable是RTPInterface实例的Hash表。表的key值是channel的编号。
  • registerRTPInterface()向成员fSubChannelHashTable中注册RTPInterface的channel,这里要同时提供channel的编号。
  • lookupRTPInterface()和deregisterRTPInterface()分别在fSubChannelHashTable中查找和删除RTPInterface的channel。

SocketDescriptor::registerRTPInterface()还同时将SocketDescriptor::tcpReadHandler()设置为socket的监听函数。

当socket有数据可读时,SocketDescriptor::tcpReadHandler()被调用。

  • 因为TCP数据可能接收不完整,所以tcpReadHandler()在循环中反复调用tcpReadHandler1(),直到一个完整的数据包读取完成。

在tcpReadHandler1()中,

  • 用一个状态机读取数据包。
    • 读取字符$、channel id、和size的时候,调用readSocket()读取一个字节,根据当前的状态保存它们。
    • 读取数据时,读取数据部分,也就是data。
  • 根据channel编号,调用lookupRTPInterface()查找对应的RTPInterface实例,
  • 调用RTPInterface的成员fReadHandlerProc,这是使用者提供的回调函数。
    • 注意,这时前面的readSockt()已经读出数据包的头,下一次readSocket()读到的是数据包的数据(data)。
    • 实际上,使用者RTPInterface指定的回调函数,将调用readSocket()读取数据部分。

2.5 用GroupSock收发UDP数据

2.5.1 NetInterface和Socket

NetInterface和Socket定义了网络接口。

Port保存端口号,它的成员fPortNum是网络字节序的端口号。

对于NetInterface,

  • 定义了write()接口,用于发送packet。

对于Socket,

  • 定义了虚拟成员函数handleRead(),用于接收packet。
  • 成员fPort保存了本地端口号。
  • 成员fSocketNum是创建的socket。

Socket的构造函数调用setupDatagramSocket()创建socket,绑定本地端口号fPort,地址缺省为INADDR_ANY,即由协议层决定。

2.5.2 OutputSocket和Groupsock

OutputSocket和GroupSock分别实现了write()和handleRead()。

对于OutputSocket,

  • write()调用全局函数writeSocket(),后者调用sendto()发送数据包。

对于GroupSock,

  • 类destRecord的成员fGroupEid中保存一个组播地址,包括地址和端口号。
  • 成员fDests是一个destRecord链表,所以它实际上保存了一个组播地址组。
  • 成员函数addDestination()和removeDestination()用于从fDests增加和删除组播地址。

成员函数output()遍历fDests中的组播地址,向它们发送数据包。

成员函数handleRead()负责接收消息。它调用全局函数readSocket(),后者调用recvfrom()接收数据包。接收的数据通过handleRead()返回给调用者。

2.6 用RTPInterface发送数据

RTPInterface::sendPacket()发送数据包。

  • 调用GroupSock::output()通过UDP组播发送数据包。
  • 遍历fTCPStreams,针对其中的TCP连接,依次调用sendRTPorRTPPacketOverTCP(),发送数据包。

5.7.用RTPInterface接收数据

成员函数startNetworkReading()开始监听所有的TCP和UDP连接。调用者需要指定有数据可读时的回调函数。

  • 这个回调函数保存在成员fReadHandleProc中。
  • 调用TaskScheduler::setBackgroundHandling()将UDP连接置于监听状态,指定的回调函数也是fReadHandlerProc。
  • 遍历fTCPStreams,调用lookupSocketDescriptor(),根据socket值,找到对应的SocketDescriptor实例。调用SocketDescriptor::registerRTPInterface(),注册channel。

当RTCPInterface的UDP或TCP socket有数据可读时,调用者指定的回调函数被调用。这时它应该调用RTCPInterface::handleRead()读取数据。

  • 每次调用只读一个socket,TCP连接优先。
    ++ 成员fNextTCPReadStreamSocketNum和fNextTCPReadStreamChannelId保存了当前可读的TCP连接的socket和channel编号。SocketDescriptor::tcpReadHandler()读取数据包的包头后,会设置这两个值。
  • 值得再次说明的是,对于TCP,SocketDescriptor::tcpReadHandler()已经读取了数据包的包头,RTPInterface::handleRead()只需要调用::readSocket(),继续读取TCP包的数据部分。
  • 如果是UDP可读,则调用GroupSock::handleRead()读取。

6 RTCP消息收发

6.1 RTCPInstance

RTCPInstance从RTPSink的信息构造RTCP数据包,然后通过RTPInterface发送出去。OutPacketBuffer作为发送缓存使用。

对于OutPacketBuffer,

  • 成员fBuf是数据包缓存,fPacketStart是数据包开始位置,fCurOffset是数据包结束位置,也是当前的写位置。
  • 成员函数enqueue()向缓存当前写位置写入数据,insert()向指定位置写入数据。
  • 成员函数extract()从指定位置提取位置。

对于RTCPInstance,

  • 成员fRTCPInterface负责发送、接收数据包。这是从RTCP连接的Groupsock实例构造的,Groupsock实例通过RTCPInstance的构造函数指定。
  • 成员fSink是绑定的RTPSink实例,也是通过RTCPInstance的构造函数指定。
  • 成员函数addStreamSocket()和removeStreamSocket(),只是在RTPInterface的同名函数上加了一层包装。通过它们,可以增加RTCP连接的TCP的客户端目标。
  • 成员fOutBuf是发送缓存。

6.2 发送RTCP消息

RTCPInstance::sendReport()是发送数据包的例子。它先调用addReport()和addSDES()构造数据包,然后在调用sendBuiltPacket()发送它。sendBuiltPacket使用了RTPInterface::sendPacket()。

RTCPInstance::schedule()设置一个超时任务。如果某些预期的事,没有在指定时间内发生,则触发指定的回调函数RTCPInstance::onExpire()进行重试。

RTCPInstace::onExpire()调用全局函数OnExpire()。在OnExpire()中,检查哪些事件没有如期发生,如发送BYE消息或其他消息没有成功,则重新尝试。

6.3 接收RTCP消息

RTCPInstance::setByeHandler()设置一个回调函数,收到对应消息时,该回调函数被调用。这个回调函数保存在成员fByeHandlerTask。

在RTCPInstance的构造函数中,调用RTCPInterface::startNetworkReading()。其中将RTCPInstance::incomingReportHandler()设置为数据可读时的回调函数。

在RTCPInstance::incomingReportHandler()中,

  • 调用RTCPInterface::handleRead()读取数据到本地缓存。
  • 调用processIncomingReport()处理缓存。如果是bye消息,调用fByeHandlerTask()进行处理。

相关文章

网友评论

      本文标题:Live555 源代码分析(二)

      本文链接:https://www.haomeiwen.com/subject/sgkpyktx.html