一、groupsock 目录介绍
groupsock 目录总共有16个源码文件,编译后生成 libgroupsock.a,这个库中的类封装了网络接口和套接字,特别是“Groupsock”类封装了一个用于发送/或接收多播数据报的套接字。libgroupsock.a 作为live555的网络库,包含了TCP套接字和UDP套接字的创建以及收发数据,还实现了单播、多播,但代码量却很少,还是值得一看的。里面涉及很多基础网络编程知识,基础薄弱的同学阅读过后对网络编程会有新的了解。这篇文件旨在梳理这16个文件的逻辑关系和框架,帮助读者更好阅读源码。
二、阅读源码
阅读源码前,先看一下 Groupsock 的协作图,图中实线表示继承,虚线表示有该类的成员变量。可见,Groupsock 继承自 OutputSocket,OutputSocke 继承自 Socket,Socket 继承自 NetInterface。而 Groupsock 又有 GroupEId类、destRecord类、NetInterfaceTrafficStats类 的对象。其他的依此类推。对协作图有个大概了解后,下面看看几个主要的类以及主要的实现文件。
1、GroupEId 类
GroupEId 类主要是对多播地址进行封装,用于创建一个多播地址(IP地址、端口号、源地址、TTL)
1.1、GroupEId 类的属性
private: struct in_addr fGroupAddress; // IP地址 struct in_addr fSourceFilterAddress;// 源地址 portNumBits fPortNum; // 端口号(使用网络字节序) u_int8_t fTTL; // Time To Live的缩写,表示IP包被路由器丢弃之前允许通过的最大网段数量
1.2、GroupEId 类的方法
GroupEId 类的方法主要分为为2部分,一个是构造 源无关 或 指定源 多播地址,另一个是获取ip地址、源地址、端口、TTL的,代码实现也很简单,可以看以下注释。
public: // 构造一个 源无关 或 指定源 的多播地址 GroupEId(struct in_addr const& groupAddr,portNumBits portNum, u_int8_t ttl); GroupEId(struct in_addr const& groupAddr,struct in_addr const& sourceFilterAddr, portNumBits portNum); struct in_addr const& groupAddress() const { return fGroupAddress; }// 获取IP地址 struct in_addr const& sourceFilterAddress() const {return fSourceFilterAddress;}//指定源地址 Boolean isSSM() const; // SSM(指定源组播) portNumBits portNum() const { return fPortNum; }// 获取端口号 u_int8_t ttl() const { return fTTL; } private: void init(struct in_addr const& groupAddr,struct in_addr const& sourceFilterAddr, portNumBits portNum,u_int8_t ttl);
2、NetAddress.hh 和 NetAddress.cpp
这两个文件主要实现了几个类用于对ip地址、端口号的封装,以及域名解析等,下面简单介绍各个类的功能:
2.1、NetAddress 类
NetAddress 类是对IP地址的封装,成员变量(属性) fLength 表示该IP地址的字节数,fData 是一个指向IP地址的指针。
成员函数(方法)中有三个构造函数用于不同情况创建对象,主要函数注释如下
NetAddress(u_int8_t const* data,unsigned length = 4);// 根据data的数据构造IP地址 NetAddress(unsigned length = 4); // 构造一个0.0.0.0的IP地址 NetAddress(NetAddress const& orig);// 根据另一个对象构造地址 NetAddress& operator=(NetAddress const& rightSide);// 赋值运算符函数 unsigned length() const { return fLength; } u_int8_t const* data() const { return fData; } // always in network byte order
2.2、NetAddressList 类
NetAddressList 类主要用于域名解析,如果hostname是IP地址则返回一个元素的列表,如果是域名则返回该域名的所有IPV4地址的列表。
成员变量(属性) fNumAddresses 表示该IP地址列表的个数,fAddressArray 是一个指向IP地址列表的指针。
成员函数(方法)的各个实现看起来都挺简单的,没什么需要特别说明的,主要看懂构造函数里调用 gethostbyname 函数进行域名解析即可。
2.3、Port 类
对端口号进行封装。代码简单明了。
2.4、AddressPortLookupTable 类
AddressPortLookupTable 类是通过(地址1,地址2,端口)查找对象的通用表,内部是使用哈希表实现的,将“地址1,地址2,端口”合起来作为 key。
2.5、AddressString 类
AddressString 类可以接收 sockaddr_in、in_addr、netAddressBits 这三种类型的IP地址,并将该IP地址转为点分十进制的字符串(xxx.xxx.xxx.xxx),用来替换不是线程安全的 “inet_ntoa()”。
2.6、IsMulticastAddress 函数
IsMulticastAddress 函数用来判断是否为合法多播地址,多播路由器从不转发这个目的地址(224.0.0.0到224.0.0.255)的数据报。
3、NetInterface.hh 和 NetInterface.cpp
3.1、NetInterface 类
NetInterface 类是网络接口类,无提供接口方法,但有静态变量 DefaultUsageEnvironment,如果非 NULL,则用于每个新接口。
3.2、DirectedNetInterface 类
DirectedNetInterface类是定向网络接口类,提供了接口方法write、SourceAddrOKForRelaying。是 NetInterface类 的子类。
3.3、DirectedNetInterfaceSet 类
DirectedNetInterfaceSet 类是用于 DirectedNetInterface类 的定向网络接口表,内部实现是哈希表,用的 DirectedNetInterface 对象的地址作为 key。
3.4、Socket 类
Socket 类继承自 NetInterface类 ,是对套接字的封装,其构造函数根据端口号创建 udp 套接字,并保存到相关成员变量。由于构造函数是 protected 属性,不能直接实例化对象。
成员变量(属性) fSocketNum 记录创建成功的套接字,fPort 记录端口号,fEnv 记录使用环境相关的变量。
成员函数(方法)主要是创建套接字的,以及获取套接字、端口等,实现相对简单。
3.5、SocketLookupTable 类
SocketLookupTable 类是通过端口号查找 Socket类 对象的表,内部实现是哈希表。
3.6、NetInterfaceTrafficStats 类
NetInterfaceTrafficStats 类用于统计网络流量的。成员变量(属性) fTotNumPackets 表示总的包数,fTotNumBytes 表示总字节数。
4、OutputSocket 类
OutputSocket 类继承自 Socket类 ,默认情况下只用于发送数据,不会收到任何数据包。因为重写了父类的纯虚函数 handleRead ,所以 OutputSocket类 不是抽象类,且其构造函数是 public ,可以实例化。
成员属性(属性) fSourcePort 记录创建套接字时,内核指定的端口;fLastSentTTL 记录最后一次设置的TTL。
成员函数(方法)OutputSocket 创建 udp 套接字,端口号由内核指定。write 用于发送数据。
5、Groupsock 类
Groupsock 类用于发送和接收数据包。它最初是为发送/接收多播而设计的,但它也可以发送/接收单播。
方法:
public: // 一、构造函数、析构函数 // 创建非特定源多播(组播)套接字:创建一个 UDP 套接字,并把本地ip地址加入到一个多播组中 Groupsock(UsageEnvironment& env,struct in_addr const& groupAddr,Port port,u_int8_t ttl); // 创建特定源多播(组播)套接字 Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr, struct in_addr const& sourceFilterAddr,Port port);// 特定源多播 virtual ~Groupsock();// 离开一个多播组 // 二、目标地址记录相关 // 记录使用单链表实现的,这里会传入下个头节点的地址,并返回新建的节点的地址 virtual destRecord* createNewDestRecord(struct in_addr const& addr, Port const& port, u_int8_t ttl, unsigned sessionId, destRecord* next); // 修改“sessionId”对应的“destRecord”,sessionId不存在则添加一个新的“destRecord”。 void changeDestinationParameters(struct in_addr const& newDestAddr, Port newDestPort, int newDestTTL,unsigned sessionId = 0); // 根据目标地址查找目标记录 unsigned lookupSessionIdFromDestination(struct sockaddr_in const& destAddrAndPort) const; virtual void addDestination(struct in_addr const& addr, Port const& port, unsigned sessionId);// 向 fDests 添加目标记录 virtual void removeDestination(unsigned sessionId); // 从fDests链表删除指定 id 的目标 void removeAllDestinations(); // 清空目标记录 Boolean hasMultipleDestinations() const;// 有多个目标地址则返回true // 三、获取多播地址相关参数 struct in_addr const& groupAddress() const ; struct in_addr const& sourceFilterAddress() const; Boolean isSSM() const; u_int8_t ttl() const; // 四、套接字发送相关 void multicastSendOnly();// 没有使用 virtual Boolean output(UsageEnvironment& env, unsigned char* buffer, unsigned bufferSize, DirectedNetInterface* interfaceNotToFwdBackTo = NULL); DirectedNetInterfaceSet& members() { return fMembers; } // // 没有使用 五、套接字读取相关 virtual Boolean handleRead(unsigned char* buffer, unsigned bufferMaxSize, unsigned& bytesRead,struct sockaddr_in& fromAddressAndPort);// 重写了套接字读取函数
6、GroupsockHelper.hh 和 GroupsockHelper.cpp
这两个文件是一些网络接口的底层实现,包括创建套接字、发送/接收数据、设置发送/接收缓冲区、设置阻塞/非阻塞、加入/退出多播组 等。
// 创建一个 UDP 套接字,并设置相应属性 int setupDatagramSocket(UsageEnvironment& env, Port port) // 创建 TCP 套接字,并设置 地址复用、端口复用 int setupStreamSocket(UsageEnvironment& env,Port port,Boolean makeNonBlocking,Boolean setKeepAlive) // 从套接字读取数据 int readSocket(UsageEnvironment& env,int socket,unsigned char* buffer,unsigned bufferSize,struct sockaddr_in& fromAddress) // 发送数据 Boolean writeSocket(UsageEnvironment& env,int socket, struct in_addr address, portNumBits portNum, unsigned char* buffer, unsigned bufferSize) // “发送或接收”缓冲区相关 unsigned getSendBufferSize(UsageEnvironment& env, int socket); unsigned getReceiveBufferSize(UsageEnvironment& env, int socket); unsigned setSendBufferTo(UsageEnvironment& env,int socket, unsigned requestedSize); unsigned setReceiveBufferTo(UsageEnvironment& env,int socket, unsigned requestedSize); unsigned increaseSendBufferTo(UsageEnvironment& env,int socket, unsigned requestedSize); unsigned increaseReceiveBufferTo(UsageEnvironment& env,int socket,unsigned requestedSize); // 设置非阻塞 Boolean makeSocketNonBlocking(int sock) // 设置阻塞 Boolean makeSocketBlocking(int sock, unsigned writeTimeoutInMilliseconds) // 将 socket 加入多播组 Boolean socketJoinGroup(UsageEnvironment& env,int socket,netAddressBits groupAddress) // 将 socket 退出多播组 Boolean socketLeaveGroup(UsageEnvironment&, int socket,netAddressBits groupAddress) // 将 socket 加入“特定源”多播组 - SSM ( Source-Specific Multicast,特定源组播) Boolean socketJoinGroupSSM(UsageEnvironment& env, int socket,netAddressBits groupAddress, netAddressBits sourceFilterAddr) // 将 socket 退出“特定源”多播组 Boolean socketLeaveGroupSSM(UsageEnvironment& /*env*/, int socket,netAddressBits groupAddress,netAddressBits sourceFilterAddr)