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()进行处理。
网友评论