美文网首页
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