1.封装通信地址类

目的: 在API级别,屏蔽IPv4、IPv6、Unix域套接字等带来的通信地址差异。切换通信IO时候在代码逻辑上可以较为平滑。

· 类关系:

Socket开发 - 图1

1.1 Adress类

1.1.1 接口

1.1.1.1 虚接口

所有通信地址子类中都会用到的几个通用的接口。

  1. /**
  2. * @brief 获取通信地址的结构体
  3. * @return 返回通信地址的结构体 const struct sockaddr*
  4. */
  5. virtual const struct sockaddr* getAddr() const = 0;
  6. /**
  7. * @brief 获取通信地址的结构体
  8. * @return 返回通信地址的结构体 struct sockaddr*
  9. */
  10. virtual struct sockaddr* getAddr() = 0;
  11. /**
  12. * @brief 获取通信结构体长度
  13. * @return 返回通信结构体长度
  14. */
  15. virtual socklen_t getAddrLen() const = 0;
  16. /**
  17. * @brief 输出通信地址的全部信息
  18. * @param[in] os 标准库规定的标准输出流对象
  19. * @return 返回标准输出流对象
  20. */
  21. virtual std::ostream& insert(std::ostream& os) const = 0;

1.1.1.2 符号重载

对象存入容器后,重载必备的几个比较符号,来用于地址对象存取比较。STL容器默认比较的是存入类型,即:比较指针类型,这不是我们所期望的。我们需要比较指针所指向的对象中存储的网络通信地址。

  • **operator<()**

通过memcmp()逐字节地比较传入的通信地址对象中包含的通信地址结构体的内容,通过每一个字节的对比来最终确定地址对象的大小关系。

  1. /**
  2. * @brief 重载符号 <
  3. * @param[in] addr 通信地址对象
  4. * @return true 当前对象比传入对象"小"
  5. * @return false 当前对象比传入对象"大"
  6. */
  7. bool Address::operator<(const Address& addr) const
  8. {
  9. //拿到最小的长度
  10. socklen_t min_len = std::min(getAddrLen(), addr.getAddrLen());
  11. //以最小字节长度为限 逐字节比较
  12. int ret = memcmp(getAddr(), addr.getAddr(), min_len);
  13. if(ret < 0)
  14. return true;
  15. else if (ret > 0)
  16. return false;
  17. else if(getAddrLen() < addr.getAddrLen()) //字节大小都相同的 比较通信结构体长度
  18. return true;
  19. return false;
  20. }
  • **operator==()**

    1. /**
    2. * @brief 重载符号 ==
    3. * @param[in] addr 通信地址对象
    4. * @return true 当前对象与传入对象"相等"
    5. * @return false 当前对象与传入对象"不相等"
    6. */
    7. bool Address::operator==(const Address& addr) const
    8. {
    9. return getAddrLen() == addr.getAddrLen() &&
    10. memcmp(getAddr(), addr.getAddr(), getAddrLen()) == 0;
    11. }
  • **operator!=()**

    1. /**
    2. * @brief 重载符号 !=
    3. * @param[in] addr 通信地址对象
    4. * @return true 当前对象与传入对象"不相等"
    5. * @return false 当前对象与传入对象"相等"
    6. */
    7. bool Address::operator!=(const Address& addr) const
    8. {
    9. return !(*this == addr);
    10. }

1.1.1.3 核心接口

1). CreateFromText()

功能:根据传入的协议类型创建对应的通信地址子类对象

  1. /**
  2. * @brief 根据传入的协议类型创建对应的通信地址子类对象
  3. * @param[in] sockaddr 通信地址结构体指针
  4. * @param[in] len 通信地址结构体长度
  5. * @return 返回通信地址对象指针
  6. */
  7. static Address::ptr CreateFromText(const struct sockaddr* sockaddr, socklen_t len);
  8. Address::ptr Address::CreateFromText(const struct sockaddr* sockaddr, socklen_t len)
  9. {
  10. if(sockaddr == nullptr)
  11. return nullptr;
  12. Address::ptr result;
  13. switch(sockaddr->sa_family)
  14. {
  15. //IPv4类型地址
  16. case AF_INET:
  17. {
  18. result.reset(new IPv4Address(*(const struct sockaddr_in*)sockaddr));
  19. }break;
  20. //IPv6类型地址
  21. case AF_INET6:
  22. {
  23. result.reset(new IPv6Address(*(const struct sockaddr_in6*)sockaddr));
  24. }break;
  25. //Unix域类型地址
  26. case AF_UNIX:
  27. {
  28. result.reset(new UnixAddress(*(const struct sockaddr_un*)sockaddr));
  29. }
  30. break;
  31. //其他为未知类型地址
  32. default:
  33. {
  34. result.reset(new UnkonwAddress(*sockaddr));
  35. }break;
  36. }
  37. return result;
  38. }

2). LookUp() (重点)

功能:通过一个域名获取所有通信地址(IPv4、IPv6),不支持AF_UNIX。
难点:处理IPv6协议,其中包含一个service字段。

  • 核心逻辑:
    1. 检查是否是IPv6形式请求服务,格式为[xxx]:service
    2. 检查是否是IPv4形式请求服务,格式为xxx:service
    3. 以上两个检查都没有发现service字段,说明不存在service的内容,只有域名内容
    4. 调用APIgetaddrinfo()获取域名上的所有网络通信地址
    5. 获取到的所有网络通信地址是一个链表的形式,,依次顺序访问,构建对应的地址类对象并存储起来 ```cpp /**
    • @brief 通过域名/地址字符串获取所有通信地址
    • @param[out] result 保存通信地址的容器
    • @param[in] host 输入的域名
    • @param[in] family 输入的地址族类型 默认为IPv4
    • @param[in] type 输入的套接字类型 默认为流式
    • @param[in] protocol 输入的地址协议类型
    • @return true result只要有地址就获取成功
    • @return false result为空可能获取失败 */ static bool LookUp(std::vector& result, const std::string& host, int family = AF_INET, int type = SOCK_STREAM, int protocol = 0);

bool Address::LookUp(std::vector& result, const std::string& host, int family, int type, int protocol) { struct addrinfo hints, results, next; hints.ai_family = family; hints.ai_flags = 0; hints.ai_socktype = type; hints.ai_protocol = protocol; hints.ai_addrlen = 0; hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL;

  1. std::string node;
  2. const char* service = nullptr;
  3. /*1. 检查是否是IPv6形式的请求服务 [xxx]:service*/
  4. if(host.size() && host[0] == '[')
  5. {
  6. //先找到IPv6地址中"]"的位置
  7. const char* endipv6 = (const char*)memchr(host.c_str() + 1, ']', host.size() - 1);
  8. if(endipv6) //如果不为NULL 说明使用一个IPv6的URL
  9. {
  10. //如果"]"后紧跟着一个":"则认为有service字段
  11. if(*(endipv6 + 1) == ':')
  12. {
  13. //存储service字段的首地址
  14. service = endipv6 + 2;
  15. }
  16. //将 [xxx]:service 中xxx的部分取出放入string node中
  17. node = host.substr(1, endipv6 - host.c_str() - 1);
  18. }
  19. }
  20. /*2.如果node为空 说明是IPv4形式请求服务 xxx:service */
  21. if(!node.size())
  22. {
  23. service = (const char*)memchr(host.c_str(), ':', host.size());
  24. //不为空存在service字段
  25. if(service)
  26. {
  27. // xxx:service 找到 ':' 后是否还存在另外一个 ':'
  28. if(!memchr(service + 1, ':', host.c_str() + host.size() - service - 1))
  29. {
  30. node = host.substr(0, service - host.c_str());
  31. //此时字符串指针指向":" +1便是service字段起始处
  32. ++service;
  33. }
  34. }
  35. }
  36. /*3. 如果node在上面的两次检查中都未被赋值 说明不存在service字段 直接赋值*/
  37. if(!node.size())
  38. node = host;
  39. /*4.调用API获取域名上的网络通信地址*/
  40. int ret = getaddrinfo(node.c_str(), service, &hints, &results);
  41. if(ret != 0)
  42. {
  43. KIT_LOG_ERROR(g_logger) << "Address::LookUp(" << host << ", "
  44. << family << ", " << type << ") error=" << ret << ", is:" << gai_strerror(ret);
  45. return false;
  46. }
  47. /*5.获取到的所有网络通信地址是一个链表的形式 依次访问构建对应的地址类对象*/
  48. next = results;
  49. while(next)
  50. {
  51. result.emplace_back(CreateFromText(next->ai_addr, (socklen_t)next->ai_addrlen));
  52. next = next->ai_next;
  53. }
  54. freeaddrinfo(results);
  55. return result.size();

}

  1. <a name="Qjvhe"></a>
  2. ##### 3). `LookUpAny()`
  3. 功能:通过一个域名获取任意一个可用的通信地址。借助`LookUp()`即可。
  4. ```cpp
  5. /**
  6. * @brief 通过域名获取任意一个通信地址
  7. * @param[in] host 输入的域名
  8. * @param[in] family 输入的地址族类型
  9. * @param[in] type 输入的套接字类型
  10. * @param[in] protocol 输入的地址协议类型
  11. * @return 返回的通信地址对象智能指针
  12. */
  13. static Address::ptr LookUpAny(const std::string& host, int family = AF_INET,
  14. int type = 0, int protocol = 0);
  15. Address::ptr Address::LookUpAny(const std::string& host, int family, int type, int protocol)
  16. {
  17. std::vector<Address::ptr> result;
  18. //默认返回得到结果中的第一个地址即可
  19. if(LookUp(result, host, type, protocol))
  20. {
  21. return result[0];
  22. }
  23. return nullptr;
  24. }

4). LookUpAnyIPAddress()

功能:通过一个域名获取任意一个IP地址,LookUp()可能会返回一个UnknownAddress对象,这个函数可以排除这种情况,如果无法转换为IP地址就会返回nullptr

  1. /**
  2. * @brief 通过域名获取任意一个IP地址
  3. * @param[in] host 输入的域名
  4. * @param[in] family 输入的地址族类型
  5. * @param[in] type 输入的套接字类型
  6. * @param[in] protocol 输入的地址协议类型
  7. * @return 返回的IP地址对象智能指针
  8. */
  9. static std::shared_ptr<IPAddress> LookUpAnyIPAddress(const std::string& host, int family = AF_INET, int type = 0, int protocol = 0);
  10. std::shared_ptr<IPAddress> Address::LookUpAnyIPAddress(const std::string& host, int family, int type, int protocol)
  11. {
  12. std::vector<Address::ptr> result;
  13. if(LookUp(result, host, type, protocol))
  14. {
  15. for(auto &x : result)
  16. {
  17. IPAddress::ptr val = std::dynamic_pointer_cast<IPAddress>(x);
  18. if(val)
  19. return val;
  20. }
  21. }
  22. return nullptr;
  23. }

5). GetInertfaceAddresses() (重点)

功能:通过所有本地网卡获取所有通信地址。

  1. /**
  2. * @brief 通过所有网卡获取所有通信地址
  3. * @param[out] result 保存 网卡----{通信地址对象指针, 掩码长度}
  4. * @param[in] family 协议类型
  5. * @return true 获取成功
  6. * @return false 获取失败
  7. */
  8. static bool GetInertfaceAddresses(std::multimap<std::string, std::pair<Address::ptr, uint32_t> >& result, int family = AF_INET);
  9. bool Address::GetInertfaceAddresses(std::multimap<std::string, std::pair<Address::ptr, uint32_t> >& result, int family)
  10. {
  11. struct ifaddrs *next, *results;
  12. int ret = getifaddrs(&results);
  13. if(ret != 0)
  14. {
  15. KIT_LOG_ERROR(g_logger) << "Address::GetInertfaceAddresses getifaddrs error=" << errno << ", is:" << strerror(errno);
  16. freeifaddrs(results);
  17. return false;
  18. }
  19. try
  20. {
  21. for(next = results;next;next = next->ifa_next)
  22. {
  23. Address::ptr addr;
  24. uint32_t len = ~0u;
  25. //过滤掉不为指定地址族的结点
  26. if(family != AF_UNSPEC && family != next->ifa_addr->sa_family)
  27. continue;
  28. switch(next->ifa_addr->sa_family)
  29. {
  30. case AF_INET:
  31. {
  32. addr = CreateFromText(next->ifa_addr, sizeof(struct sockaddr_in));
  33. //小技巧 掩码转换 sockaddr---->sockaddr_in
  34. uint32_t netmask = ((struct sockaddr_in*)next->ifa_netmask)->sin_addr.s_addr;
  35. //计算掩码长度
  36. len = CountBytes(netmask);
  37. }break;
  38. case AF_INET6:
  39. {
  40. addr = CreateFromText(next->ifa_addr, sizeof(struct sockaddr_in6));
  41. //小技巧 掩码转换 sockaddr---->sockaddr_in6
  42. struct in6_addr& netmask = ((struct sockaddr_in6*)next->ifa_netmask)->sin6_addr;
  43. //计算前缀长度
  44. len = 0;
  45. //每一个元素为uint8_t 共有16个
  46. for(int i = 0;i < 16;++i)
  47. {
  48. len += CountBytes(netmask.s6_addr[i]);
  49. }
  50. }break;
  51. default:
  52. break;
  53. }
  54. if(addr)
  55. {
  56. result.insert({next->ifa_name, {addr, len}});
  57. }
  58. }
  59. }
  60. catch(...)
  61. {
  62. KIT_LOG_ERROR(g_logger) << "Address::GetInertfaceAddresses error!";
  63. freeifaddrs(results);
  64. return false;
  65. }
  66. freeifaddrs(results);
  67. return true;
  68. }

6). (重载)GetInertfaceAddresses()

功能:通过指定一个本地网卡获取其上绑定的所有通信地址。没有指定或指定为任意网卡名称,就返回一个默认的IP地址

  1. /**
  2. * @brief 通过指定网卡获取所有通信地址
  3. * @param[out] result 保存 {通信地址对象指针, 掩码长度}
  4. * @param[in] iface 指定的网卡名称
  5. * @param[in] family 协议类型
  6. * @return true 获取成功
  7. * @return false 获取失败
  8. */
  9. static bool GetInertfaceAddresses(std::vector<std::pair<Address::ptr, uint32_t> >& result, const std::string &iface, int family = AF_UNSPEC);
  10. bool Address::GetInertfaceAddresses(std::vector<std::pair<Address::ptr, uint32_t> >& result, const std::string &iface, int family )
  11. {
  12. //如果网卡没有指定 或者 为任意网卡
  13. if(!iface.size() || iface == "*")
  14. {
  15. if(family == AF_INET || family == AF_UNSPEC)
  16. {
  17. result.push_back({Address::ptr(new IPv4Address), 0u});
  18. }
  19. if(family == AF_INET6 || family == AF_UNSPEC)
  20. {
  21. result.push_back({Address::ptr(new IPv6Address), 0u});
  22. }
  23. return true;
  24. }
  25. std::multimap<std::string, std::pair<Address::ptr, uint32_t> > results;
  26. if(!GetInertfaceAddresses(results, family))
  27. {
  28. return false;
  29. }
  30. //返回一个迭代器区间 first为首迭代器 second为尾迭代器
  31. //迭代器包装的内含容器类型仍为multimap<std::string, std::pair<Address::ptr, uint32_t> >
  32. auto it = results.equal_range(iface);
  33. for(;it.first != it.second;++it.first)
  34. {
  35. result.push_back(it.first->second);
  36. }
  37. return true;
  38. }

1.2 IPAdress类

1.2.1 接口

1.2.1.1 虚接口

  1. /**
  2. * @brief IP地址的广播地址
  3. * @param[in] len 掩码长度
  4. * @return 返回IP地址对象指针
  5. */
  6. virtual IPAddress::ptr broadcastAddress(uint32_t len) = 0;
  7. /**
  8. * @brief 子网地址
  9. * @param[in] len 掩码长度
  10. * @return 返回IP地址对象指针
  11. */
  12. virtual IPAddress::ptr networkAddress(uint32_t len) = 0;
  13. //子网掩码
  14. /**
  15. * @brief
  16. * @param[in] len 掩码长度
  17. * @return 返回IP地址对象指针
  18. */
  19. virtual IPAddress::ptr subnetMask(uint32_t len) = 0;
  20. /**
  21. * @brief 获取端口号
  22. * @return 返回端口号 uint16_t
  23. */
  24. virtual uint16_t getPort() const = 0;
  25. /**
  26. * @brief 设置端口号
  27. * @param[in] val 传入要设置的端口号uint16_t
  28. */
  29. virtual void setPort(uint16_t val) = 0;

1.2.2 核心接口

1). CreateFromText()

功能:从文本型网络地址转换为一个实际IP地址。例如,传入一个字符串”192.168.1.1”———>转换为一个真正的通信地址 struct sockaddr_in.sin_addr.s_addr(uint32_t)

  1. /**
  2. * @brief 从文本型网络地址转换为一个实际IP地址
  3. * @param[in] address 地址字符串
  4. * @param[in] port 端口号
  5. * @return 返回IP地址对象指针
  6. */
  7. static IPAddress::ptr CreateFromText(const char* address, uint16_t port = 0);
  8. IPAddress::ptr IPAddress::CreateFromText(const char* address, uint16_t port)
  9. {
  10. struct addrinfo hints, *results;
  11. memset(&hints, 0, sizeof(hints));
  12. //hints.ai_flags = AI_NUMERICHOST; //只允许使用点分十进制类型
  13. hints.ai_family = AF_UNSPEC;
  14. int ret = getaddrinfo(address, NULL, &hints, &results);
  15. if(ret != 0)
  16. {
  17. KIT_LOG_ERROR(g_logger) << "IPAddress::CreateFromText(" << address << ", " << port << "), "
  18. << "error=" << ret
  19. << ", errno=" << errno << ", is " <<strerror(errno);
  20. return nullptr;
  21. }
  22. try
  23. {
  24. //复用Address类中的CreateFromText()函数 由通信结构体创建一个IP地址对象
  25. IPAddress::ptr ret = std::dynamic_pointer_cast<IPAddress>(
  26. Address::CreateFromText(results->ai_addr, (socklen_t)results->ai_addrlen));
  27. if(ret)
  28. {
  29. ret->setPort(port);
  30. }
  31. freeaddrinfo(results);
  32. return ret;
  33. }
  34. catch(...)
  35. {
  36. freeaddrinfo(results);
  37. return nullptr;
  38. }
  39. }

** 重要辅助函数

1. CreateHostMask()

功能:生成一个只有主机号的掩码,即:子网掩码取反后的结果。

  1. /**
  2. * @brief 创建一个只有主机号的掩码
  3. * @param[in] bits 掩码位数
  4. * @return T 返回传入类型的数据
  5. */
  6. template<class T>
  7. static T CreateHostMask(uint32_t bits)
  8. {
  9. return (1 << (sizeof(T) * 8 - bits)) - 1;
  10. }

2. CountBytes()

功能:计算一个数二进制表示有多少位”1”。

  1. /**
  2. * @brief 计算一个数里面 二进制表示中有多少个1
  3. * @param[in] val 传入的数据
  4. * @return 返回传入数据中二进制共有"1"的个数 uint32_t
  5. */
  6. template<class T>
  7. static uint32_t CountBytes(T val)
  8. {
  9. uint32_t count = 0;
  10. for(;val;++count)
  11. val &= val - 1;
  12. return count;
  13. }

1.3 IPv4Adress类

1.3.1 成员变量

  1. class IPv4Adress: public IPAddress
  2. ...
  3. ...
  4. private:
  5. //IPv4通信地址结构体
  6. struct sockaddr_in m_sockaddr;
  7. };

1.3.2 接口

1.3.2.1 构造函数

  1. /**
  2. * @brief IPv4类构造函数
  3. * @param[in] sockaddr 通信结构体
  4. */
  5. IPv4Address(const struct sockaddr_in sockaddr);
  6. /**
  7. * @brief IPv4类构造函数
  8. * @param[in] address IPv4地址
  9. * @param[in] port 端口号
  10. */
  11. IPv4Address(uint32_t address = INADDR_ANY, uint16_t port = 0);
  12. IPv4Address::IPv4Address(uint32_t address, uint16_t port)
  13. {
  14. memset(&m_sockaddr, 0 ,sizeof(m_sockaddr));
  15. m_sockaddr.sin_family = AF_INET;
  16. m_sockaddr.sin_port = byteswapOnSmallEndian(port); //转换为大端字节序
  17. m_sockaddr.sin_addr.s_addr = byteswapOnSmallEndian(address); //转换为大端字节序
  18. }
  19. IPv4Address::IPv4Address(const struct sockaddr_in sockaddr)
  20. :m_sockaddr(sockaddr)
  21. {
  22. }

1.3.2.2 重写接口

  1. /*来自Address类*/
  2. const struct sockaddr* getAddr() const override;
  3. struct sockaddr* getAddr() override;
  4. socklen_t getAddrLen() const override;
  5. std::ostream& insert(std::ostream& os) const override;
  6. /*来自IPAddress类*/
  7. IPAddress::ptr broadcastAddress(uint32_t len) override;
  8. IPAddress::ptr networkAddress(uint32_t len) override;
  9. IPAddress::ptr subnetMask(uint32_t len) override;
  10. uint16_t getPort() const override;
  11. void setPort(uint16_t val) override;

1). insert()

功能:将IPv4地址通过”流”的形式重新组装起来,格式:xxx.xxx.xxx.xxx:端口号
注意:由于采用点分十进制的表示方式,需要将uint32_t每8位分割,表示成一个十进制数

  • 方法一:

    1. std::ostream& IPv4Address::insert(std::ostream& os) const
    2. {
    3. uint32_t addr = byteswapOnSmallEndian(m_sockaddr.sin_addr.s_addr);
    4. //手动转 点分十进制
    5. os << ((addr >> 24) & 0xff) << "."
    6. << ((addr >> 16) & 0xff) << "."
    7. << ((addr >> 8) & 0xff) << "."
    8. << (addr & 0xff);
    9. os << ":" << byteswapOnSmallEndian(m_sockaddr.sin_port);
    10. return os;
    11. }
  • 方法二:

    1. std::ostream& IPv4Address::insert(std::ostream& os) const
    2. {
    3. //直接调API
    4. char p[INET_ADDRSTRLEN];
    5. inet_ntop(AF_INET, &m_sockaddr.sin_addr, p, sizeof(m_sockaddr));
    6. os << p;
    7. os << ":" << byteswapOnSmallEndian(m_sockaddr.sin_port);
    8. return os;
    9. }

2). broadcastAddress()

功能:得到当前IP子网下的广播地址。

  1. IPAddress::ptr IPv4Address::broadcastAddress(uint32_t len)
  2. {
  3. if(len > 32)
  4. return nullptr;
  5. struct sockaddr_in baddr(m_sockaddr);
  6. //或运算 一个主机号掩码
  7. baddr.sin_addr.s_addr |= byteswapOnSmallEndian(CreateHostMask<uint32_t>(len));
  8. return IPv4Address::ptr(new IPv4Address(baddr));
  9. }

3). subnetAddress()

功能:得到当前IP子网下网段起始地址。

  1. IPAddress::ptr IPv4Address::subnetAddress(uint32_t len)
  2. {
  3. if(len > 32)
  4. return nullptr;
  5. struct sockaddr_in baddr(m_sockaddr);
  6. //与运算 一个子网掩码 = ~主机号掩码
  7. baddr.sin_addr.s_addr &= ~byteswapOnSmallEndian(CreateHostMask<uint32_t>(len));
  8. return IPv4Address::ptr(new IPv4Address(baddr));
  9. }

4). subnetMask()

功能:得到当前掩码位数对应的子网掩码。

  1. IPAddress::ptr IPv4Address::subnetMask(uint32_t len)
  2. {
  3. if(len > 32)
  4. return nullptr;
  5. struct sockaddr_in subnet;
  6. memset(&subnet, 0, sizeof(subnet));
  7. subnet.sin_family = AF_INET;
  8. subnet.sin_addr.s_addr = ~byteswapOnSmallEndian(CreateHostMask<uint32_t>(len));
  9. return IPv4Address::ptr(new IPv4Address(subnet));
  10. }

1.4 IPv6Adress类 (是否保留这部分有待考量)

1.4.1 成员变量

  1. class IPv6Adress: public IPAddress
  2. ...
  3. ...
  4. private:
  5. //IPv6通信地址结构体
  6. struct sockaddr_in6 m_sockaddr;
  7. };

1.4.2 接口

1.4.2.1 构造函数

  1. /**
  2. * @brief 无参IPv6类构造函数
  3. */
  4. IPv6Address();
  5. /**
  6. * @brief IPv6类构造函数
  7. * @param[in] sockaddr 通信结构体
  8. */
  9. IPv6Address(const struct sockaddr_in6 sockaddr);
  10. /**
  11. * @brief IPv6类构造函数
  12. * @param[in] address IPv6地址数值组
  13. * @param port 端口号
  14. */
  15. IPv6Address(const uint8_t address[16], uint16_t port = 0);
  16. IPv6Address::IPv6Address()
  17. {
  18. memset(&m_sockaddr, 0, sizeof(m_sockaddr));
  19. m_sockaddr.sin6_family = AF_INET6;
  20. }
  21. IPv6Address::IPv6Address(const uint8_t address[16], uint16_t port)
  22. {
  23. memset(&m_sockaddr, 0, sizeof(m_sockaddr));
  24. m_sockaddr.sin6_family = AF_INET6;
  25. m_sockaddr.sin6_port = byteswapOnSmallEndian(port);
  26. memcpy(&m_sockaddr.sin6_addr.s6_addr, address, 16);
  27. }
  28. IPv6Address::IPv6Address(const struct sockaddr_in6 sockaddr)
  29. :m_sockaddr(sockaddr)
  30. {
  31. }

1.4.2.2 重写接口

  1. /*来自Address类*/
  2. const struct sockaddr* getAddr() const override;
  3. struct sockaddr* getAddr() override;
  4. socklen_t getAddrLen() const override;
  5. std::ostream& insert(std::ostream& os) const override;
  6. /*来自IPAddress类*/
  7. IPAddress::ptr broadcastAddress(uint32_t len) override;
  8. IPAddress::ptr networkAddress(uint32_t len) override;
  9. IPAddress::ptr subnetMask(uint32_t len) override;
  10. uint16_t getPort() const override;
  11. void setPort(uint16_t val) override;

1). insert()

功能:将IPv6地址通过”流”的形式重新组装起来,格式:[xxx:xxx:···:xxx]:端口号
注意:由于采用冒号十六进制的表示方式,需要将uint8_t addr[16]每16位分割,表示成一个十六进制进制数

  • 方法一:

小难点处理连续0出现的情形: 开头、中间、结尾

  1. std::ostream& IPv6Address::insert(std::ostream& os) const
  2. {
  3. /*手动转*/
  4. os << "[";
  5. uint16_t* addr = (uint16_t*)&m_sockaddr.sin6_addr.s6_addr;
  6. bool used_zero8 = false;
  7. for(size_t i = 0;i < 8;++i)
  8. {
  9. if(addr[i] == 0 && !used_zero8)
  10. continue;
  11. //连续0用两个"::"代替 且只能代替一次
  12. if(i && addr[i - 1] == 0 && !used_zero8)
  13. {
  14. os << ":";
  15. used_zero8 = true;
  16. }
  17. if(i)
  18. os << ":";
  19. //地址数字 输出十六进制
  20. os << std::hex << (int)byteswapOnSmallEndian(addr[i]);
  21. }
  22. // "::"出现在末尾
  23. if(!used_zero8 && !addr[7])
  24. os << "::";
  25. os << "]:" << std::dec << byteswapOnSmallEndian(m_sockaddr.sin6_port);
  26. return os;
  27. }
  28. }
  • 方法二: ```cpp std::ostream& IPv6Address::insert(std::ostream& os) const { /直接调API/ os << “[“; char p[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &m_sockaddr.sin6_addr, p, sizeof(m_sockaddr)); os << p;

    os << std::dec << “]:” << byteswapOnSmallEndian(m_sockaddr.sin6_port);

    return os;

}

  1. <a name="yYe5b"></a>
  2. ##### 2). `broadcastAddress()`
  3. 功能:得到当前IP子网下的广播地址。(IPv6没有广播地址,保持一致性,按照IPv4的概念计算得到)<br />实现:
  4. 1. 首先要处理网络标识与主机标识交界处的一部分比特位。
  5. - 交界处 = 前缀长度 / 8bit,这么计算是因为`struct sockaddr_in6`中存储方式为
  6. `struct sin6_addr == uint8_t __u6_addr8[16];`
  7. - 需要处理的比特位数 = 前缀长度 % 8bit,得到要处理的主机标识比特位数,借由辅助函数构建一个主机号掩码,进行一个或运算即可
  8. 2. 从`交界处 + 1`的位置 ~ `__u6_addr8[15]`的位置,全部置为`0xff`
  9. ```cpp
  10. IPAddress::ptr IPv6Address::broadcastAddress(uint32_t len)
  11. {
  12. if(len > 128)
  13. return nullptr;
  14. struct sockaddr_in6 baddr(m_sockaddr);
  15. //处理网络标识和主机标识交界地方的比特位
  16. baddr.sin6_addr.s6_addr[len / 8] |= CreateHostMask<uint8_t>(len % 8);
  17. for(size_t i = len / 8 + 1;i < 16;++i)
  18. {
  19. baddr.sin6_addr.s6_addr[i] = 0xff;
  20. }
  21. return IPv6Address::ptr(new IPv6Address(baddr));
  22. }

3). subnetAddress()

功能:得到当前IP子网下网段起始地址。(IPv6没有网段,保持一致性,按照IPv4的概念计算得到)

  1. IPAddress::ptr IPv6Address::subnetAddress(uint32_t len)
  2. {
  3. if(len > 128)
  4. return nullptr;
  5. struct sockaddr_in6 sock_addr(m_sockaddr);
  6. sock_addr.sin6_addr.s6_addr[len / 8] &= ~CreateHostMask<uint8_t>(len % 8);
  7. for(size_t i = len / 8 + 1;i < 16;++i)
  8. {
  9. sock_addr.sin6_addr.s6_addr[i] = 0;
  10. }
  11. return IPv6Address::ptr(new IPv6Address(sock_addr));
  12. }

4). subnetMask()

功能:得到当前掩码位数对应的IPv6的”类子网掩码”,IPv6中只有前缀长度,不存在子网掩码的概念。

  1. IPAddress::ptr IPv6Address::subnetMask(uint32_t len)
  2. {
  3. if(len > 128)
  4. return nullptr;
  5. struct sockaddr_in6 subnet;
  6. memset(&subnet, 0, sizeof(subnet));
  7. subnet.sin6_family = AF_INET6;
  8. subnet.sin6_addr.s6_addr[len / 8] = ~CreateHostMask<uint8_t>(len % 8);
  9. for(size_t i = 0;i < len / 8;++i)
  10. {
  11. subnet.sin6_addr.s6_addr[i] = 0xff;
  12. }
  13. return IPv6Address::ptr(new IPv6Address(subnet));
  14. }

1.5 UnixAdress类

1.5.1 成员变量

  1. class UnixAdress: public Address
  2. ...
  3. ...
  4. private:
  5. //Unix域通信地址结构体
  6. struct sockaddr_un m_sockaddr;
  7. //通信文件路径
  8. std::string m_path;
  9. //Unix域通信地址结构体长度 变长结构体要单独记录
  10. socklen_t m_sockaddrlen;
  11. };

1.5.2 接口

1.5.2.1 构造函数

  1. /**
  2. * @brief 无参Unix域地址类构造函数
  3. */
  4. UnixAddress();
  5. /**
  6. * @brief Unix域地址类构造函数
  7. * @param[in] path 通信文件路径
  8. */
  9. UnixAddress(const std::string& path);
  10. /**
  11. * @brief Unix域地址类构造函数
  12. * @param[in] addr 通信结构体
  13. */
  14. UnixAddress(const struct sockaddr_un addr);
  15. //struct sockaddr_un 是一个变长结构体
  16. static const size_t MAX_PATH_LEN = sizeof(((struct sockaddr_un*)0)->sun_path) - 1;
  17. UnixAddress::UnixAddress()
  18. {
  19. memset(&m_sockaddr, 0, sizeof(m_sockaddr));
  20. m_sockaddr.sun_family = AF_UNIX;
  21. m_sockaddrlen = offsetof(struct sockaddr_un, sun_path) + MAX_PATH_LEN;
  22. }
  23. UnixAddress::UnixAddress(const std::string& path)
  24. :m_path(path)
  25. {
  26. memset(&m_sockaddr, 0, sizeof(m_sockaddr));
  27. m_sockaddr.sun_family = AF_UNIX;
  28. m_sockaddrlen = path.size() + 1;
  29. if(path.size() && path[0] == '\0')
  30. --m_sockaddrlen;
  31. if(m_sockaddrlen > sizeof(m_sockaddr.sun_path))
  32. throw std::logic_error("Unix socket path length too long!!");
  33. memcpy(m_sockaddr.sun_path, path.c_str(), m_sockaddrlen);
  34. m_sockaddrlen += offsetof(struct sockaddr_un, sun_path);
  35. }
  36. UnixAddress::UnixAddress(const struct sockaddr_un addr)
  37. {
  38. m_sockaddr = addr;
  39. }

1.5.2.2 重写接口

1). insert()

功能:输出Unix域通信文件的路径。

  1. std::ostream& UnixAddress::insert(std::ostream& os) const
  2. {
  3. if(m_sockaddrlen > offsetof(struct sockaddr_un, sun_path) &&
  4. m_sockaddr.sun_path[0] == '\0')
  5. {
  6. return os << "\\0" << std::string(m_sockaddr.sun_path + 1, m_sockaddrlen - offsetof(struct sockaddr_un, sun_path) - 1);
  7. }
  8. os << m_sockaddr.sun_path;
  9. return os;
  10. }

2. 字节序转换封装

目的:当用户物理机字节序和网络字节序不同,进行一个转换。不同的物理机的自带字节序不一定和网络字节序相同。网络字节序为大端字节序,物理机不同厂商设置的字节序可能是大端也可能是小端。

  • 大端模式:一个数据的低位字节序的内容放在高地址处,高位字节序存的内容放在低地址处
  • 小端模式:一个数据的低位字节序内容存放在低地址处,高位字节序的内容存放在高地址处

口诀:高低低高为大端,高高低低为小端

  • 以存储数据**0x1234**为例:1byte = 8bit = (16进制)4 + 4

Socket开发 - 图2

  • 简单验证方法:

利用一个联合体,存储一个16位的无符号整数,通过单字节数组可以看到存储顺序,可以看到本人的物理机为小端模式(低字节在低地址,高字节在高地址)。

  1. union U
  2. {
  3. uint16_t a;
  4. char s[2];
  5. };
  6. int main()
  7. {
  8. U u1;
  9. u1.a = 0x1234;
  10. //注意 cout的进制转换只对整数有效 必须转为 int 或 uint
  11. std::cout << std::hex << (uint)u1.s[0] << (uint)u1.s[1] << std::endl;
  12. //printf("%x%x\n", u1.s[0], u1.s[1]); /*使用printf简单粗暴*/
  13. return 0;
  14. }

image.png

2.1 使用SFINEA规则对不同位数据的位交换重载

功能:对16bit、32bit、64bit的数据重载它们的高低位交换函数。

实际上通过反汇编文件,使用if...else写法和这种模板函数重载效果是一样的。

  1. /**
  2. * @brief 如果传入类型T为 无符号64位 则使用类型T作为返回值
  3. * @tparam T 传入类型 uint64_t
  4. * @param value 具体数值
  5. * @return 返回 uint64_t 交换后的数据
  6. */
  7. template<class T>
  8. typename std::enable_if<sizeof(T) == sizeof(uint64_t), T>::type
  9. byteswap(T value)
  10. {
  11. return (T)bswap_64((uint64_t)value);
  12. }
  13. /**
  14. * @brief 如果传入类型T为 无符号32位 则使用类型T作为返回值
  15. * @tparam T 传入类型 uint32_t
  16. * @param value 具体数值
  17. * @return 返回 uint32_t 交换后的数据
  18. */
  19. template<class T>
  20. typename std::enable_if<sizeof(T) == sizeof(uint32_t), T>::type
  21. byteswap(T value)
  22. {
  23. return (T)bswap_32((uint32_t)value);
  24. }
  25. /**
  26. * @brief 如果传入类型T为 无符号16位 则使用类型T作为返回值
  27. * @tparam T 传入类型 uint16_t
  28. * @param value 具体数值
  29. * @return 返回 uint16_t 交换后的数据
  30. */
  31. template<class T>
  32. typename std::enable_if<sizeof(T) == sizeof(uint16_t), T>::type
  33. byteswap(T value)
  34. {
  35. return (T)bswap_16((uint16_t)value);
  36. }

2.2 根据用户物理机大小端存储,决定如何转换字节序

功能:网络字节序一般指的大端字节序。
小端机器:收到:大端字节序————>小端字节序;发出:小端字节序———>大端字节序
大端机器:收到:一般不需要做改变;发出:一般不需要做改变

  1. #define KIT_SMALL_ENDIAN 1 //小端
  2. #define KIT_BIG_ENDIAN 2 //大端
  3. //判断物理机大小端
  4. #if BYTE_ORDER == BIG_ENDIAN
  5. #define KIT_BYTE_ORDER KIT_BIG_ENDIAN
  6. #else
  7. #define KIT_BYTE_ORDER KIT_SMALL_ENDIAN
  8. #endif
  9. #if KIT_BYTE_ORDER == KIT_BIG_ENDIAN
  10. /**
  11. * @brief 得到大端 大端机器 什么都不用操作
  12. */
  13. template<class T>
  14. T byteswapOnSmallEndian(T t)
  15. {
  16. return t;
  17. }
  18. /**
  19. * @brief 得到小端 大端机器 大端----->小端
  20. */
  21. template<class T>
  22. T byteswapOnBigEndian(T t)
  23. {
  24. return byteswap(t);
  25. }
  26. #else
  27. /**
  28. * @brief 得到大端 小端机器 小端------->大端
  29. */
  30. template<class T>
  31. T byteswapOnSmallEndian(T t)
  32. {
  33. return byteswap(t);
  34. }
  35. /**
  36. * @brief 得到小端 小端机器 什么都不用做
  37. */
  38. template<class T>
  39. T byteswapOnBigEndian(T t)
  40. {
  41. return t;
  42. }

3. Socket类封装

目的:对套接字读写操作、套接字的设置做一层封装。

· 类关系

Socket开发 - 图4

3.1 成员变量

  1. class Socket: public std::enable_shared_from_this<Socket>, Noncopyable
  2. {
  3. ...
  4. ...
  5. private:
  6. //套接字句柄
  7. int m_fd;
  8. //地址族类型
  9. int m_family;
  10. //套接字类型
  11. int m_type;
  12. //套接字协议
  13. int m_protocol;
  14. //套接字连接状态
  15. bool m_isConnectd;
  16. //本地地址对象智能指针
  17. Address::ptr m_localAddr;
  18. //远端地址对象智能指针
  19. Address::ptr m_remoteAddr;
  20. };

3.2 接口

3.2.1 构造函数

  1. /**
  2. * @brief Socket类构造函数
  3. * @param[in] family 地址族
  4. * @param[in] type 套接字类型
  5. * @param[in] protocol 协议类型
  6. */
  7. Socket(int family, int type, int protocol = 0);
  8. Socket::Socket(int family, int type, int protocol)
  9. :m_fd(-1),
  10. m_family(family),
  11. m_type(type),
  12. m_protocol(protocol),
  13. m_isConnectd(false)
  14. {
  15. }

3.2.2 析构函数

  1. Socket::~Socket()
  2. {
  3. close();
  4. }

** 便捷接口

  1. /**
  2. * @brief 使用传入的地址 创建TCP套接字对象
  3. * @param[in] address 传入指定的通信地址对象智能指针
  4. * @return Socket::ptr
  5. */
  6. static Socket::ptr CreateTCP(Address::ptr address);
  7. /**
  8. * @brief 使用传入的地址 创建UDP套接字对象
  9. * @param[in] address 传入指定的通信地址对象智能指针
  10. * @return Socket::ptr
  11. */
  12. static Socket::ptr CreateUDP(Address::ptr address);
  13. /**
  14. * @brief 默认创建TCP IPv4 套接字
  15. * @return Socket::ptr
  16. */
  17. static Socket::ptr CreateTCPSocket();
  18. /**
  19. * @brief 默认创建UDP IPv4 套接字
  20. * @return Socket::ptr
  21. */
  22. static Socket::ptr CreateUDPSocket();
  23. /**
  24. * @brief 默认创建TCP IPv6 套接字
  25. * @return Socket::ptr
  26. */
  27. static Socket::ptr CreateTCPSocket6();
  28. /**
  29. * @brief 默认创建UDP IPv6 套接字
  30. * @return Socket::ptr
  31. */
  32. static Socket::ptr CreateUDPSocket6();
  33. /**
  34. * @brief 默认创建TCP UNIX域 套接字
  35. * @return Socket::ptr
  36. */
  37. static Socket::ptr CreateUnixTCPSocket();
  38. /**
  39. * @brief 默认创建UDP UNIX域 套接字
  40. * @return Socket::ptr
  41. */
  42. static Socket::ptr CreateUnixUDPSocket();
  43. Socket::ptr Socket::CreateTCP(Address::ptr address)
  44. {
  45. Socket::ptr sock(new Socket(address->getFamily(), Type::TCP, 0));
  46. return sock;
  47. }
  48. Socket::ptr Socket::CreateUDP(Address::ptr address)
  49. {
  50. Socket::ptr sock(new Socket(address->getFamily(), Type::UDP, 0));
  51. return sock;
  52. }
  53. Socket::ptr Socket::CreateTCPSocket()
  54. {
  55. Socket::ptr sock(new Socket(Family::IPv4, Type::TCP, 0));
  56. return sock;
  57. }
  58. Socket::ptr Socket::CreateUDPSocket()
  59. {
  60. Socket::ptr sock(new Socket(Family::IPv4, Type::UDP, 0));
  61. return sock;
  62. }
  63. Socket::ptr Socket::CreateTCPSocket6()
  64. {
  65. Socket::ptr sock(new Socket(Family::IPv6, Type::TCP, 0));
  66. return sock;
  67. }
  68. Socket::ptr Socket::CreateUDPSocket6()
  69. {
  70. Socket::ptr sock(new Socket(Family::IPv6, Type::UDP, 0));
  71. return sock;
  72. }
  73. Socket::ptr Socket::CreateUnixTCPSocket()
  74. {
  75. Socket::ptr sock(new Socket(Family::Unix, Type::TCP, 0));
  76. return sock;
  77. }
  78. Socket::ptr Socket::CreateUnixUDPSocket()
  79. {
  80. Socket::ptr sock(new Socket(Family::Unix, Type::UDP, 0));
  81. return sock;
  82. }

** 私有接口

1). initSocket()

功能:初始化一个套接字句柄

  1. /**
  2. * @brief 初始化套接字
  3. */
  4. void initSocket();
  5. void Socket::initSocket()
  6. {
  7. int val = 1;
  8. setOption(SOL_SOCKET, SO_REUSEADDR, val);
  9. //禁用TCP套接字的 nagle算法
  10. if(m_family != Family::Unix && m_type == SOCK_STREAM)
  11. setOption(IPPROTO_TCP, TCP_NODELAY, val);
  12. }

2). newSocket()

功能:创建一个新的套接字句柄。将之前hook好的 API 简单封装即可

  1. void Socket::newSocket()
  2. {
  3. m_fd = socket(m_family, m_type, m_protocol);
  4. if(KIT_LICKLY(m_fd != -1))
  5. {
  6. initSocket();
  7. }
  8. else
  9. {
  10. KIT_LOG_ERROR(g_logger) << "socket(" << m_family << ", " << m_type << ", " << m_protocol << ")errno=" << errno << ", is:" << strerror(errno);
  11. }
  12. }

3). init()

功能:初始化一个新的fd句柄。

  1. /**
  2. * @brief 初始化fd
  3. * @param[in] fd 传入文件句柄
  4. * @return 初始化成功
  5. * @return 初始化失败
  6. */
  7. bool init(int fd);
  8. bool Socket::init(int fd)
  9. {
  10. //新的fd加入到FdManager的管理中
  11. FdCtx::ptr ctx = FdMgr::GetInstance()->get(fd);
  12. //fd为socket 并且没被关闭 对其进行初始化
  13. if(ctx && ctx->isSocket() && !ctx->isClose())
  14. {
  15. m_fd = fd;
  16. m_isConnectd = true;
  17. initSocket();
  18. getLocalAddress();
  19. getRemoteAddress();
  20. return true;
  21. }
  22. return false;
  23. }

3.2.3 setOption()

功能:设置套接字的配置属性。将之前hook好的 API 简单封装即可

  1. /**
  2. * @brief 设置套接字配置信息
  3. * @param[in] level 在哪一个层级句柄设置
  4. * @param[in] option 设置的具体配置
  5. * @param[in] result 设置的结果
  6. * @param[in] len 参数大小
  7. * @return true 设置成功
  8. * @return false 设置失败
  9. */
  10. bool setOption(int level, int option, const void* result, size_t len);
  11. /**
  12. * @brief 设置套接字配置信息重载模板函数
  13. */
  14. template<class T>
  15. bool setOption(int level, int option, const T& result)
  16. {
  17. return setOption(level, option, &result, (socklen_t)sizeof(T));
  18. }
  19. bool Socket::setOption(int level, int option, const void* result, size_t len)
  20. {
  21. int ret = setsockopt(m_fd, level, option, result, (socklen_t)len);
  22. if(ret)
  23. {
  24. KIT_LOG_ERROR(g_logger) << "setsockopt errno=" << errno
  25. << ", is:" << strerror(errno)
  26. << ", fd=" << m_fd
  27. << ", level=" << level
  28. << ", option=" << option;
  29. return false;
  30. }
  31. return true;
  32. }

3.2.4 getOption()

功能:获取套接字的配置属性。将之前hook好的 API 简单封装即可

  1. /**
  2. * @brief 获取套接字配置信息
  3. * @param[in] level 在哪一个层级句柄获取
  4. * @param[in] option 获取的具体配置
  5. * @param[in] result 获取的结果
  6. * @param[in] len 参数大小
  7. * @return true 获取成功
  8. * @return false 获取失败
  9. */
  10. bool getOption(int level, int option, void* result, size_t* len);
  11. /**
  12. * @brief 获取套接字配置信息重载模板函数
  13. */
  14. template<class T>
  15. bool getOption(int level, int option, T& result)
  16. {
  17. socklen_t len = sizeof(T);
  18. return getOption(level, option, &result, &len);
  19. }
  20. bool Socket::getOption(int level, int option, void* result, size_t* len)
  21. {
  22. int ret = getsockopt(m_fd, level, option, result, (socklen_t*)len);
  23. if(ret)
  24. {
  25. KIT_LOG_ERROR(g_logger) << "getsockopt errno=" << errno
  26. << ", is:" << strerror(errno)
  27. << ", fd=" << m_fd
  28. << ", level=" << level
  29. << ", option=" << option;
  30. return false;
  31. }
  32. return true;
  33. }

3.2.5 setSendTimeout()/getSendTimeout()

功能:设置/获取fd句柄上的发送超时时间

  1. /**
  2. * @brief 设置发送超时时间
  3. * @param[in] val 传入设置的具体的时间 单位ms
  4. */
  5. void setSendTimeout(int64_t val);
  6. void Socket::setSendTimeout(int64_t val)
  7. {
  8. struct timeval tv;
  9. tv.tv_sec = val / 1000;
  10. tv.tv_usec = val % 1000 * 1000;
  11. //设置套接字属性
  12. setOption(SOL_SOCKET, SO_SNDTIMEO, tv);
  13. }
  14. /**
  15. * @brief 获取发送超时时间
  16. * @return 返回超时时间 单位ms
  17. */
  18. int64_t getSendTimeout() const;
  19. int64_t Socket::getSendTimeout() const
  20. {
  21. FdCtx::ptr ctx = FdMgr::GetInstance()->get(m_fd);
  22. if(ctx)
  23. return ctx->getTimeout(SO_SNDTIMEO);
  24. return -1;
  25. }

3.2.6 setRecvTimeout()/getRecvTimeout()

功能:设置/获取fd句柄上的接收超时时间

  1. /**
  2. * @brief 获取接收超时时间
  3. * @return 返回超时时间 单位ms
  4. */
  5. int64_t getRecvTimeout() const;
  6. int64_t Socket::getRecvTimeout() const
  7. {
  8. FdCtx::ptr ctx = FdMgr::GetInstance()->get(m_fd);
  9. if(ctx)
  10. return ctx->getTimeout(SO_RCVTIMEO);
  11. return -1;
  12. }
  13. /**
  14. * @brief 设置接收超时时间
  15. * @param[in] val 传入设置的具体的时间 单位ms
  16. */
  17. void setRecvTimeout(int64_t val);
  18. void Socket::setRecvTimeout(int64_t val)
  19. {
  20. struct timeval tv;
  21. tv.tv_sec = val / 1000;
  22. tv.tv_usec = val % 1000 * 1000;
  23. //设置套接字属性
  24. setOption(SOL_SOCKET, SO_RCVTIMEO, tv);
  25. }

3.2.9 accept()

功能:接收新的客户端连接,生成一个新的套接字对象。将之前hook好的 API 简单封装即可

  1. /**
  2. * @brief 接收客户端连接
  3. * @return 返回新的通信套接字智能指针
  4. */
  5. Socket::ptr accept();
  6. Socket::ptr Socket::accept()
  7. {
  8. Socket::ptr sock(new Socket(m_family, m_type, m_protocol));
  9. int ac_fd = ::accept(m_fd, nullptr, nullptr);
  10. if(ac_fd < 0)
  11. {
  12. KIT_LOG_ERROR(g_logger) << "accept errno =" << errno << ", is:" <<strerror(errno);
  13. return nullptr;
  14. }
  15. if(sock->init(ac_fd))
  16. return sock;
  17. return nullptr;
  18. }

3.2.10 bind()

功能:绑定通信地址

  1. /**
  2. * @brief 套接字绑定地址
  3. * @param[in] addr 传入的通信地址智能指针
  4. * @return 绑定成功
  5. * @return 绑定失败
  6. */
  7. bool bind(const Address::ptr addr);
  8. bool Socket::bind(const Address::ptr addr)
  9. {
  10. //如果套接字无效
  11. if(!isValid())
  12. {
  13. //创建新的套接字
  14. newSocket();
  15. if(KIT_UNLICKLY(!isValid()))
  16. {
  17. return false;
  18. }
  19. }
  20. if(KIT_UNLICKLY(addr->getFamily() != m_family))
  21. {
  22. KIT_LOG_ERROR(g_logger) << "bind socket family =" << m_family << ", addr family="
  23. << addr->getFamily() << ", is defferent!";
  24. return false;
  25. }
  26. //bind 没有被HOOK
  27. int ret = ::bind(m_fd, (struct sockaddr*)addr->getAddr(), addr->getAddrLen());
  28. if(ret < 0)
  29. {
  30. KIT_LOG_ERROR(g_logger) << "bind error, errno=" << errno << ", is:" << strerror(errno);
  31. return false;
  32. }
  33. getLocalAddress();
  34. return true;
  35. }

3.2.11 connect()

功能:连接远端地址,带有超时功能

  1. /**
  2. * @brief 连接远端地址,带有超时功能
  3. * @param[in] addr 传入的通信地址智能指针
  4. * @param[in] timeout_ms 连接的超时时间
  5. * @return 连接成功
  6. * @return 连接失败
  7. */
  8. bool connect(const Address::ptr addr, uint64_t timeout_ms = -1);
  9. bool Socket::connect(const Address::ptr addr, uint64_t timeout_ms)
  10. {
  11. if(KIT_UNLICKLY(!isValid()))
  12. {
  13. //创建新的套接字
  14. newSocket();
  15. if(KIT_UNLICKLY(!isValid()))
  16. {
  17. return false;
  18. }
  19. }
  20. if(KIT_UNLICKLY(addr->getFamily() != m_family))
  21. {
  22. KIT_LOG_ERROR(g_logger) << "connect socket family =" << m_family << ", addr family="
  23. << addr->getFamily() << ", is defferent!";
  24. return false;
  25. }
  26. if(timeout_ms == (uint64_t)-1)
  27. {
  28. //会使用默认超时时间
  29. int ret = ::connect(m_fd, addr->getAddr(), addr->getAddrLen());
  30. if(ret < 0)
  31. {
  32. KIT_LOG_ERROR(g_logger) << "connect error, errno=" << errno << ", is:" << strerror(errno);
  33. close();
  34. return false;
  35. }
  36. }
  37. else //使用超时connect
  38. {
  39. int ret = ::connect_with_timeout(m_fd, addr->getAddr(), addr->getAddrLen(), timeout_ms);
  40. if(ret < 0)
  41. {
  42. KIT_LOG_ERROR(g_logger) << "connect error, errno=" << errno << ", is:" << strerror(errno);
  43. close();
  44. return false;
  45. }
  46. }
  47. m_isConnectd = true;
  48. //获取远端地址
  49. getRemoteAddress();
  50. //获取本地地址
  51. getLocalAddress();
  52. return true;
  53. }

3.2.12 listen()

功能:服务器监听。

  1. /**
  2. * @brief 服务器监听
  3. * @param[in] backlog 监听最大数值
  4. * @return 监听成功
  5. * @return 监听失败
  6. */
  7. bool listen(int backlog = SOMAXCONN);
  8. bool Socket::listen(int backlog)
  9. {
  10. if(KIT_UNLICKLY(!isValid()))
  11. {
  12. KIT_LOG_ERROR(g_logger) << "listen error sock fd = -1";
  13. return false;
  14. }
  15. if(::listen(m_fd, backlog) < 0)
  16. {
  17. KIT_LOG_ERROR(g_logger) << "listen error, errno =" << errno << ",is:" << strerror(errno);
  18. return false;
  19. }
  20. return true;
  21. }

3.2.13 close()

功能:关闭套接字。将之前hook好的 API 简单封装即可

  1. /**
  2. * @brief 关闭套接字
  3. * @return true 已经处于关闭
  4. * @return false 还没处于关闭刚刚关闭
  5. */
  6. bool close();
  7. bool Socket::close()
  8. {
  9. if(!isConnected() && m_fd == -1)
  10. {
  11. return true;
  12. }
  13. m_isConnectd = false;
  14. if(m_fd != -1)
  15. {
  16. ::close(m_fd);
  17. m_fd = -1;
  18. }
  19. return false;
  20. }

3.2.14 写系列函数

功能:发送数据。将之前hook好的函数简单封装

  1. //send API
  2. int send(const void *buffer, size_t len, int flags = 0);
  3. int send(const struct iovec* buffers, size_t len, int flags = 0);
  4. int sendTo(const void * buffer, size_t len, const Address::ptr addr_to, int flags = 0);
  5. int sendTo(const struct iovec* buffers, size_t len, const Address::ptr addr_to, int flags = 0);
  6. int Socket::send(const void *buffer, size_t len, int flags)
  7. {
  8. if(isConnected())
  9. {
  10. return ::send(m_fd, buffer, len, flags);
  11. }
  12. return -1;
  13. }
  14. int Socket::send(const struct iovec* buffers, size_t len, int flags)
  15. {
  16. if(isConnected())
  17. {
  18. struct msghdr msg;
  19. memset(&msg, 0, sizeof(msg));
  20. msg.msg_iov = (struct iovec *)buffers;
  21. msg.msg_iovlen = len;
  22. return ::sendmsg(m_fd, &msg, flags);
  23. }
  24. return -1;
  25. }
  26. int Socket::sendTo(const void * buffer, size_t len, const Address::ptr addr_to, int flags)
  27. {
  28. if(isConnected())
  29. {
  30. return ::sendto(m_fd, buffer, len, flags, addr_to->getAddr(), addr_to->getAddrLen());
  31. }
  32. return -1;
  33. }
  34. int Socket::sendTo(const struct iovec* buffers, size_t len, const Address::ptr addr_to, int flags)
  35. {
  36. if(isConnected())
  37. {
  38. struct msghdr msg;
  39. memset(&msg, 0, sizeof(msg));
  40. msg.msg_iov = (struct iovec *)buffers;
  41. msg.msg_iovlen = len;
  42. msg.msg_name = addr_to->getAddr();
  43. msg.msg_namelen = addr_to->getAddrLen();
  44. return ::sendmsg(m_fd, &msg, flags);
  45. }
  46. return -1;
  47. }

3.2.15 读系列函数

功能:读取数据

  1. //recv API
  2. int recv(void *buffer, size_t len, int flags = 0);
  3. int recv(struct iovec* buffers, size_t len, int flags = 0);
  4. int recvFrom(void * buffer, size_t len, Address::ptr addr_from, int flags = 0);
  5. int recvFrom(struct iovec* buffers, size_t len, Address::ptr addr_from, int flags = 0);
  6. int Socket::recv(void *buffer, size_t len, int flags)
  7. {
  8. if(isConnected())
  9. {
  10. return ::recv(m_fd, buffer, len, flags);
  11. }
  12. return -1;
  13. }
  14. int Socket::recv(struct iovec* buffers, size_t len, int flags)
  15. {
  16. if(isConnected())
  17. {
  18. struct msghdr msg;
  19. memset(&msg, 0, sizeof(msg));
  20. msg.msg_iov = buffers;
  21. msg.msg_iovlen = len;
  22. return ::recvmsg(m_fd, &msg, flags);
  23. }
  24. return -1;
  25. }
  26. int Socket::recvFrom(void * buffer, size_t len, Address::ptr addr_from, int flags)
  27. {
  28. if(isConnected())
  29. {
  30. socklen_t len = addr_from->getAddrLen();
  31. return ::recvfrom(m_fd, buffer, len, flags, (struct sockaddr*)addr_from->getAddr(), &len);
  32. }
  33. return -1;
  34. }
  35. int Socket::recvFrom(struct iovec* buffers, size_t len, Address::ptr addr_from, int flags)
  36. {
  37. if(isConnected())
  38. {
  39. struct msghdr msg;
  40. memset(&msg, 0, sizeof(msg));
  41. msg.msg_iov = (struct iovec *)buffers;
  42. msg.msg_iovlen = len;
  43. msg.msg_name = addr_from->getAddr();
  44. msg.msg_namelen = addr_from->getAddrLen();
  45. return ::recvmsg(m_fd, &msg, flags);
  46. }
  47. return -1;
  48. }

3.2.16 getRemoteAddress()

功能:获取连接的对端通信地址

  1. /**
  2. * @brief 获取远端地址
  3. * @return 返回通信地址对象智能指针
  4. */
  5. Address::ptr getRemoteAddress();
  6. Address::ptr Socket::getRemoteAddress()
  7. {
  8. if(m_remoteAddr)
  9. return m_remoteAddr;
  10. Address::ptr result;
  11. switch(m_family)
  12. {
  13. case AF_INET: result.reset(new IPv4Address);break;
  14. case AF_INET6: result.reset(new IPv6Address);break;
  15. case AF_UNIX: result.reset(new UnixAddress);break;
  16. default: result.reset(new UnkonwAddress(m_family));break;
  17. }
  18. socklen_t new_len = result->getAddrLen();
  19. if(getpeername(m_fd, (struct sockaddr*)result->getAddr(), &new_len) < 0)
  20. {
  21. KIT_LOG_ERROR(g_logger) << "getpeername error, errno=" << errno << ", is:" << strerror(errno);
  22. return Address::ptr(new UnkonwAddress(m_family));
  23. }
  24. //如果为Unix域通信地址需要单独设置一下结构体长度
  25. if(m_family == AF_UNIX)
  26. {
  27. UnixAddress::ptr uaddr = std::dynamic_pointer_cast<UnixAddress>(result);
  28. uaddr->setAddrLen(new_len);
  29. }
  30. m_remoteAddr = result;
  31. return m_remoteAddr;
  32. }

3.2.17 getLocalAddress()

功能:获取本地通信地址

  1. /**
  2. * @brief 获取本地地址
  3. * @return 返回通信地址对象智能指针
  4. */
  5. Address::ptr getLocalAddress();
  6. Address::ptr Socket::getLocalAddress()
  7. {
  8. if(m_localAddr)
  9. return m_localAddr;
  10. Address::ptr result;
  11. switch(m_family)
  12. {
  13. case AF_INET: result.reset(new IPv4Address);break;
  14. case AF_INET6: result.reset(new IPv6Address);break;
  15. case AF_UNIX: result.reset(new UnixAddress);break;
  16. default: result.reset(new UnkonwAddress(m_family));break;
  17. }
  18. socklen_t new_len = result->getAddrLen();
  19. if(getsockname(m_fd, (struct sockaddr*)result->getAddr(), &new_len) < 0)
  20. {
  21. KIT_LOG_ERROR(g_logger) << "getsockname error, errno=" << errno << ", is:" << strerror(errno);
  22. return Address::ptr(new UnkonwAddress(m_family));
  23. }
  24. if(m_family == AF_UNIX)
  25. {
  26. UnixAddress::ptr uaddr = std::dynamic_pointer_cast<UnixAddress>(result);
  27. uaddr->setAddrLen(new_len);
  28. }
  29. m_localAddr = result;
  30. return m_localAddr;
  31. }Address::ptr Socket::getLocalAddress()
  32. {
  33. if(m_localAddr)
  34. return m_localAddr;
  35. Address::ptr result;
  36. switch(m_family)
  37. {
  38. case AF_INET: result.reset(new IPv4Address);break;
  39. case AF_INET6: result.reset(new IPv6Address);break;
  40. case AF_UNIX: result.reset(new UnixAddress);break;
  41. default: result.reset(new UnkonwAddress(m_family));break;
  42. }
  43. socklen_t new_len = result->getAddrLen();
  44. if(getsockname(m_fd, (struct sockaddr*)result->getAddr(), &new_len) < 0)
  45. {
  46. KIT_LOG_ERROR(g_logger) << "getsockname error, errno=" << errno << ", is:" << strerror(errno);
  47. return Address::ptr(new UnkonwAddress(m_family));
  48. }
  49. m_localAddr = result;
  50. return m_localAddr;
  51. }

3.2.18 getError()

功能:获取套接字上的错误信息

  1. /**
  2. * @brief 获取套接字上的错误
  3. * @return int 返回错误码
  4. */
  5. int getError();
  6. int Socket::getError()
  7. {
  8. int error = 0;
  9. size_t len = sizeof(error);
  10. if(!getOption(SOL_SOCKET, SO_ERROR, &error, &len))
  11. {
  12. return -1;
  13. }
  14. return error;
  15. }

3.2.19 dump()

功能:将类中管理的socket的相关信息放入到标准输出流中。

  1. /**
  2. * @brief 套接字的信息放入标准输出流中
  3. * @param os 标准输出流
  4. * @return std::ostream&
  5. */
  6. std::ostream& dump(std::ostream& os) const;
  7. std::ostream& Socket::dump(std::ostream& os) const
  8. {
  9. os << "[Socket fd=" << m_fd
  10. << ",connected=" << m_isConnectd
  11. << ",family=" << m_family
  12. << ",type=" << m_type
  13. << ",protocol=" << m_protocol;
  14. if(m_localAddr)
  15. os << ",local addr=" << m_localAddr->toString();
  16. if(m_remoteAddr)
  17. os << ", remote addr=" << m_remoteAddr->toString();
  18. os << "]";
  19. return os;
  20. }

3.2.20 toString()

功能:将类中管理的socket的相关信息输出为字符串

  1. /**
  2. * @brief 套接字的信息以字符串输出
  3. * @return std::string
  4. */
  5. std::string toString() const;
  6. std::string Socket::toString() const
  7. {
  8. std::stringstream ss;
  9. dump(ss);
  10. return ss.str();
  11. }

C\C++知识点复习补充:Unix域套接字 进程通信

出处:《Unix环境高级编程 第三版》P513
功能:用于同一台计算机上进程间的通信,作为IPC的手段之一。

  • 和网络通信socket的区别:

虽然网络通信socket也能实现进程通信,但是在不需要跨网络通信的场景下,Unix域套接字的效率高

  1. Unix域套接字有套接字的功能但不包含:协议处理、添加/删除网络协议报文头部、计算校验和、产生序列号,发送确认报文等。
  2. Unix域套接字提供流式传输和数据报传输,但是Unix域数据报服务是可靠的,不会发生丢失和错传
  3. Unix域套接字是一种网络套接字和管道的”混合体” ,是全双工的工作模式,和管道不同,能够同时在一端读写数据。和管道一样分为无名Unix域套接字、有名Unix域套接字
  1. /*Unix域无名套接字*/
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. int socketpair(int domain, int type, int protocol, int sv[2]);

image.png

  1. /*Unix域有名套接字*/
  2. #include <sys/socket.h>
  3. #include <sys/un.h>
  4. struct sockaddr_un {
  5. sa_family_t sun_family; /*PF_UNIX或AF_UNIX */
  6. char sun_path[UNIX_PATH_MAX]; /* 路径名 */
  7. };
  8. int fd, size;
  9. struct sockaddr_un un;
  10. un.sun_family = AF_UNIX;
  11. strcpy(un.sun_path, "xxxxxxx");
  12. fd = socket(AF_UNIX, SOCK_STREAM, 0);
  13. size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
  14. /*服务器端可以使用bind、listen、accept*/
  15. /*客户端可以使用connect*/

image.png

  • Unix域无名套接字测试: ```cpp

    include

    include

    include

    include

    include

    include

    include

using namespace std;

int main() { int fd[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, fd);

  1. pid_t pid;
  2. pid = fork();
  3. if(pid == 0)
  4. {
  5. close(fd[0]);
  6. char buf[100] = "写端写入 66";
  7. write(fd[1], buf, strlen(buf) + 1);
  8. read(fd[1], buf, sizeof(buf));
  9. cout << "写端收到: " << buf << endl;
  10. }
  11. else if(pid > 0)
  12. {
  13. close(fd[1]);
  14. char buf[100];
  15. read(fd[0], buf, sizeof(buf));
  16. cout << "读端读到: " << buf << endl;
  17. write(fd[0], "66666", 6);
  18. }
  19. usleep(1);
  20. return 0;

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25460685/1641645895207-b9b8af23-8777-491d-9cd1-57cc1000df78.png#clientId=u908adfbb-7ded-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=102&id=u54b0d9d6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=102&originWidth=608&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10273&status=done&style=none&taskId=uf79496c4-b45b-4891-ac1b-d9089b3e1d1&title=&width=608)
  2. - Unix域有名套接字测试:
  3. - `client.cpp`
  4. ```cpp
  5. #include <iostream>
  6. #include <sys/socket.h>
  7. #include <unistd.h>
  8. #include <stdlib.h>
  9. #include <sys/types.h>
  10. #include <sys/un.h>
  11. #include <string.h>
  12. #include <errno.h>
  13. using namespace std;
  14. #define PATH "./sock"
  15. int main()
  16. {
  17. int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  18. struct sockaddr_un sockaddr;
  19. bzero(&sockaddr, sizeof(struct sockaddr_un));
  20. sockaddr.sun_family = AF_UNIX;
  21. strcpy(sockaddr.sun_path, PATH);
  22. int n = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path);
  23. if(connect(fd, (struct sockaddr*)&sockaddr, n) < 0)
  24. {
  25. cout << "connect error:" << errno << ",is:" << strerror(errno) << endl;
  26. return 0;
  27. }
  28. char buf[100] = "hello im send";
  29. write(fd, buf, strlen(buf) + 1);
  30. read(fd, buf, sizeof(buf));
  31. cout << "recv from: " << buf << endl;
  32. close(fd);
  33. return 0;
  34. }
  • server.cpp ```cpp

    include

    include

    include

    include

    include

    include

    include

    include

    include

using namespace std;

define PATH “./sock”

int main() { //创建套接字 int fd = socket(AF_UNIX, SOCK_STREAM, 0);

  1. //关联通信文件
  2. struct sockaddr_un sockaddr;
  3. bzero(&sockaddr, sizeof(struct sockaddr_un));
  4. sockaddr.sun_family = AF_UNIX;
  5. strcpy(sockaddr.sun_path, PATH);
  6. int n = offsetof(struct sockaddr_un, sun_path) + strlen(sockaddr.sun_path);
  7. unlink(PATH); //必须先unlink 防止还在使用
  8. if(bind(fd, (struct sockaddr*)&sockaddr, n) < 0)
  9. {
  10. cout << "bind error:" << errno << ",is:" << strerror(errno) << endl;
  11. return 0;
  12. }
  13. if(listen(fd, 5) < 0)
  14. {
  15. cout << "listen error:" << errno << ",is:" << strerror(errno) << endl;
  16. return 0;
  17. }
  18. socklen_t len = sizeof(struct sockaddr_un);
  19. int sock_fd = accept(fd, (struct sockaddr*)&sockaddr, &len);
  20. if(sock_fd < 0)
  21. {
  22. cout << "accept error:" << errno << ",is:" << strerror(errno) << endl;
  23. return 0;
  24. }
  25. char buf[100];
  26. int ret = read(sock_fd, buf, sizeof(buf));
  27. if(ret < 0)
  28. {
  29. cout << "read error:" << errno << ",is:" << strerror(errno) << endl;
  30. return 0;
  31. }
  32. cout << "recv from msg: " << buf << endl;
  33. char p[100] = "server recv:";
  34. strcat(p, buf);
  35. ret = write(sock_fd, p, strlen(p) + 1);
  36. if(ret < 0)
  37. {
  38. cout << "writeerror:" << errno << ",is:" << strerror(errno) << endl;
  39. return 0;
  40. }
  41. close(sock_fd);
  42. close(fd);
  43. return 0;

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25460685/1641649133630-bf4f1435-39b1-4e7c-9e8e-64127f1e16ca.png#clientId=u908adfbb-7ded-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=59&id=u1d78dd04&margin=%5Bobject%20Object%5D&name=image.png&originHeight=59&originWidth=1263&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9441&status=done&style=none&taskId=u364f2fc0-73c1-4fce-ba15-dd153d2f1fd&title=&width=1263)
  2. <a name="QBe5M"></a>
  3. # C\C++知识点复习补充:memcmp()函数
  4. 功能:比较内存区域s1s2的前n个字节,该函数是以字节为单位进行比较的。<br />返回值:在前n个字节中:<br />s1 < s2 返回值<0<br />s1 = s2 返回值=0<br />s1 > s2 返回值>0
  5. 注意:
  6. 1. 对于memcmp(),如果两个字符串相同而且n大于字符串长度的话,memcmp不会在`\0`处停下来,会继续比较`\0`后面的内存单元,直到返回结果不为零或者达到了需要比较的字节数。
  7. 2. 如果 n 为零,则返回值为零。
  8. ```cpp
  9. #include <string.h>
  10. int memcmp(const void *s1, const void *s2, size_t n);

C\C++知识点复习补充:模板SFINEA规则

出处:《深入理解C++11新特性解析与应用》 P119
概念:C++模板中。有一条非常著名的规则,SFINEA = substitution failure is not an error,直译:匹配失败不是错误。表示的是对重载的模板的参数进行展开的时候,如果展开导致一些类型的不匹配,编译器并不会报错。

  • 直观的例子:

可以看到调用f<int>(2)虽然不存在int::foo这样的参数类型但是编译器没有报错。而是转而匹配到了另外一个符合预期的模板。SFINEA规则能够使得模板匹配更为”精确”,在一些模板函数、模板类实例化时使用特殊的模板版本,而另外一些使用通用的版本。

  1. #include <iostream>
  2. using namespace std;
  3. struct Test
  4. {
  5. typedef int foo;
  6. };
  7. template<class T>
  8. void f(typename T::foo a)
  9. {
  10. std::cout << "f1 run!" << std::endl;
  11. }
  12. template<class T>
  13. void f(T a)
  14. {
  15. std::cout << "f2 fun!" << std::endl;
  16. }
  17. int main()
  18. {
  19. f<Test>(1);
  20. f<int>(2);
  21. return 0;
  22. }

image.png

C\C++知识点复习补充:enable_if 模板

功能:满足条件时类型有效,作为选择类型的小工具

  • 定义: ```cpp template struct enable_if { };

template struct enable_if { using type = T; };

  1. 主要用途:
  2. 1. **模板偏特化**
  3. 在使用模板编程时,经常会用到根据模板参数的某些特性进行不同类型的选择,或者在编译时校验模板参数的某些特性。
  4. ```cpp
  5. template <typename T, typename Enable=void>
  6. struct check;
  7. //只需要 value == true 的 T
  8. template <typename T>
  9. struct check<T, typename std::enable_if<T::value>::type> {
  10. static constexpr bool value = T::value;
  11. };
  1. 控制函数返回值

根据不同的模板参数返回不同类型的值

  1. template <std::size_t k, class T, class... Ts>
  2. typename std::enable_if<k==0, typename element_type_holder<0, T, Ts...>::type&>::type
  3. get(tuple<T, Ts...> &t) {
  4. return t.tail;
  5. }
  6. template <std::size_t k, class T, class... Ts>
  7. typename std::enable_if<k!=0, typename element_type_holder<k, T, Ts...>::type&>::type
  8. get(tuple<T, Ts...> &t) {
  9. tuple<Ts...> &base = t;
  10. return get<k-1>(base);
  11. }
  1. 校验函数模板参数类型

定义的模板函数,只希望特定的类型可以调用

  1. //只有参数T为int型,返回值bool生效
  2. template <typename T>
  3. typename std::enable_if<std::is_integral<T>::value, bool>::type
  4. is_odd(T t) {
  5. return bool(t%2);
  6. }
  7. //传入参数T为int 才行
  8. template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
  9. bool is_even(T t) {
  10. return !is_odd(t);
  11. }

C\C++知识点复习补充:memchr()函数

功能:扫描由 s 指向的内存区域的初始 n 字节以查找 c 的第一个实例。 c 和 s 指向的内存区域的字节都被解释为无符号字符。

返回值:返回一个指向匹配字节的指针,如果字符没有出现在给定的内存区域中,则返回 NULL。

  1. #include <string.h>
  2. void *memchr(const void *s, int c, size_t n);
  3. void *memrchr(const void *s, int c, size_t n);
  4. void *rawmemchr(const void *s, int c);

C\C++知识点复习补充:getaddrinfo()函数

功能:给定域名节点node和服务service,它们标识互联网主机和服务,getaddrinfo() 返回一个或多个 addrinfo 结构,每个结构都包含一个互联网地址,可以在调用bind()connect()时指定。 getaddrinfo()函数将gethostbyname()getservbyname()函数提供的功能组合到一个接口中,但与后面的函数不同,getaddrinfo( ) 是可重入的,并且允许程序消除 IPv4 与 IPv6 的依赖关系。

返回值:如果成功则返回 0;否则返回非零错误代码之一,gai_strerror()打印具体的错误信息。

  • 部分参数解释:

hints:指向一个 addrinfo 结构体,该结构限定了返回参数 res 指向的链表中的套接字地址结构的标准形式(即:指定返回什么类型的套接字地址结构)。如果为NULL,将会有一个默认的addrinfo 结构体被设置。

service:指定要请求的”服务”,指定后在每个返回的地址结构中设置对应的端口。 如果此参数是服务名称,则将其转换为相应的端口号。 此参数也可以指定为十进制数,只需将其转换为二进制即可。 如果 service 为 NULL,则返回的套接字地址的端口号将保持未初始化。

res:存储返回的套接字地址结构体链表,是一块动态分配内存,需要手动调用freeaddrinfo()释放空间

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <netdb.h>
  4. int getaddrinfo(const char *node, const char *service,
  5. const struct addrinfo *hints,
  6. struct addrinfo **res);
  7. void freeaddrinfo(struct addrinfo *res);
  8. const char *gai_strerror(int errcode);
  • struct addrinfo

    1. struct addrinfo {
    2. int ai_flags;
    3. int ai_family; /*指定/返回的地址指定所需的地址族,有效值AF_INET,AF_INET6
    4. AF_UNSPEC表示返回可用于节点和服务的任何地址族*/
    5. int ai_socktype; /* 指定/返回套接字类型 SOCK_STREAM,SOCK_DGRAM*/
    6. int ai_protocol; /* 指定/返回的套接字地址的协议*/
    7. socklen_t ai_addrlen; /*返回套接字地址结构体长度*/
    8. struct sockaddr * ai_addr; /*返回套接字地址结构体首地址*/
    9. char * ai_canonname; /*返回请求域名的正式名称*/
    10. struct addrinfo * ai_next; /*下一个addrinfo结点指针*/
    11. };
  • 用例: ```cpp

    include

    include

    include

    include

int main() {

  1. struct addrinfo hints, *results, *next;
  2. hints.ai_family = AF_INET;
  3. hints.ai_flags = 0;
  4. hints.ai_socktype = SOCK_STREAM;
  5. hints.ai_protocol = 0;
  6. hints.ai_addrlen = 0;
  7. hints.ai_canonname = NULL;
  8. hints.ai_addr = NULL;
  9. hints.ai_next = NULL;
  10. int ret = getaddrinfo("www.badiu.com", nullptr, &hints, &results);
  11. if(ret != 0)
  12. {
  13. std::cout << "getaddrinfo error, errno=" << ret << ",is:" << gai_strerror(ret) << std::endl;
  14. return 0;
  15. }
  16. next = results;
  17. while(next)
  18. {
  19. char buf[INET_ADDRSTRLEN] = {0};
  20. inet_ntop(AF_INET, &((struct sockaddr_in*)next->ai_addr)->sin_addr, buf, sizeof(buf));
  21. std::cout << "addr=" << buf << std::endl;
  22. next = next->ai_next;
  23. }
  24. freeaddrinfo(results);
  25. return 0;

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25460685/1645080279355-b93c485e-b576-4c45-b3f1-dd855ae4b6d6.png#clientId=ub7cb399c-52b6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=55&id=ua34e2668&margin=%5Bobject%20Object%5D&name=image.png&originHeight=55&originWidth=771&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8382&status=done&style=none&taskId=ue0ce7160-5696-48f7-b8a8-df546d179b5&title=&width=771)
  2. <a name="SKQ28"></a>
  3. # C\C++知识点复习补充:getifaddrs()函数
  4. 功能:创建一个本地系统网络接口(网卡)的结构的链表,并将列表第一项的地址存储在`ifap` 中。<br />只支持AF_INETAF_INET6,不支持AF_UNIX
  5. 返回值:成功时,返回零; 出错时,返回 -1,并适当设置 errno
  6. ```cpp
  7. #include <sys/types.h>
  8. #include <ifaddrs.h>
  9. int getifaddrs(struct ifaddrs **ifap);
  10. void freeifaddrs(struct ifaddrs *ifa);
  • struct ifaddrs: ```cpp struct ifaddrs { struct ifaddrs ifa_next; / Next item in list 下一个结构体指针/ char ifa_name; / Name of interface 网卡名称/ unsigned int ifa_flags; / Flags from SIOCGIFFLAGS / struct sockaddr ifa_addr; / Address of interface 通信地址结构体指针/ struct sockaddr ifa_netmask; / Netmask of interface 和通信地址关联的掩码结构指针/

    union {

    1. struct sockaddr *ifu_broadaddr; /* Broadcast address of interface 广播地址*/
    2. struct sockaddr *ifu_dstaddr; /* Point-to-point destination address 点对点地址*/

    } ifa_ifu;

define ifa_broadaddr ifa_ifu.ifu_broadaddr

define ifa_dstaddr ifa_ifu.ifu_dstaddr

void ifa_data; / Address-specific data 包含地址族特定数据的缓冲区*/ };

  1. - **用例:**
  2. ```cpp
  3. #include <iostream>
  4. #include <sys/socket.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <sys/types.h>
  8. #include <errno.h>
  9. #include <stdarg.h>
  10. #include <ifaddrs.h>
  11. #include <netinet/in.h>
  12. #include <arpa/inet.h>
  13. using namespace std;
  14. int main()
  15. {
  16. struct ifaddrs *next, *results;
  17. if(getifaddrs(&results) != 0)
  18. {
  19. std::cout << "getifaddrs error, errno=" << errno << ",is:" << strerror(errno) << std::endl;
  20. return 0;
  21. }
  22. next = results;
  23. while(next)
  24. {
  25. char buf[50] = {0};
  26. switch (next->ifa_addr->sa_family)
  27. {
  28. case AF_INET:
  29. {
  30. inet_ntop(AF_INET, &(((struct sockaddr_in*)next->ifa_addr)->sin_addr), buf, (socklen_t)sizeof(buf));
  31. }break;
  32. case AF_INET6:
  33. {
  34. inet_ntop(AF_INET6, &(((struct sockaddr_in6*)next->ifa_addr)->sin6_addr), buf, (socklen_t)sizeof(buf));
  35. }break;
  36. default:
  37. break;
  38. }
  39. if(strlen(buf) > 0)
  40. std::cout << next->ifa_name << ":" << "<" << buf << ">" << std::endl;
  41. next = next->ifa_next;
  42. }
  43. //释放动态分配的存储空间
  44. freeifaddrs(results);
  45. return 0;
  46. }

image.png

C\C++知识点复习补充:byteswap系列宏函数

功能:将16位、32位、64位的数据高低位进行互换。

返回值:总是成功的,返回高低位互换后的数据

注意:不是简单的数据排列的逆序,而是数据存储模式的差别。
Socket开发 - 图9
Socket开发 - 图10

  1. #include <byteswap.h>
  2. bswap_16(x);
  3. bswap_32(x);
  4. bswap_64(x);
  • 用例: ```cpp

    include

    include

using namespace std;

int main() { int16_t a = 0x1234; int32_t b = 0x12345678; int64_t c = 0x1234567890abcdef; std::cout << std::hex << a << std::endl; std::cout << std::hex << b << std::endl; std::cout << std::hex << c << std::endl;

  1. a = bswap_16(a);
  2. b = bswap_32(b);
  3. c = bswap_64(c);
  4. std::cout << "-----------------" << std::endl;
  5. std::cout << std::hex << a << std::endl;
  6. std::cout << std::hex << b << std::endl;
  7. std::cout << std::hex << c << std::endl;
  8. return 0;

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25460685/1644946684328-942a7ee0-2363-40ce-ac03-b67adf650cce.png#clientId=ucebc3da0-b786-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=151&id=uf763be22&margin=%5Bobject%20Object%5D&name=image.png&originHeight=151&originWidth=828&originalType=binary&ratio=1&rotation=0&showTitle=false&size=13312&status=done&style=none&taskId=ud2256c40-36b3-436d-a09b-cfa0dc692ab&title=&width=828)
  2. <a name="h3FHw"></a>
  3. # 计算机网络知识点复习补充:IP地址和子网掩码
  4. 子网掩码:IP地址后紧跟的`\xx`为无符号32位二进制数,从最高位开始计算的"1"的个数是xx个。每8位分割开,点分十进制表示后的就是子网掩码(网络号)。
  5. - 子网最大主机数 = `2 ^ (32 - 掩码位数) - 2` 2是因为广播地址和起始网络地址要剔除
  6. - 主机号 = `2 ^ (32 - 掩码位数) - 1`
  7. - 子网掩码 = ~`主机号`
  8. - 起始网络地址 = `IP地址` & `子网掩码`
  9. - 广播地址 = `IP地址` | `主机号`
  10. - 例题,例如IP地址是128.36.202.186,子网掩码是255.255.240.0
  11. ```cpp
  12. 128.36. 1100 1010 . 1011 1010
  13. & 255.255. 1111 0000 . 0000 0000 -------------------->主机位12位 网络位20位
  14. ---------------------------------------
  15. 128.36. 1100 0000 . 0000 0000
  16. = 128.36.192.0 --------------------->起始网络地址
  17. 1111 1010 1011 1010
  18. 128.36. 1100 1010 . 1011 1010
  19. | 0. 0. 0000 1111 . 1111 1111 -------------------->主机号全置1
  20. ---------------------------------------
  21. 128.36. 1100 1111 . 1111 1111
  22. = 128.36.207.255 --------------------->广播地址
  23. 子网最大主机数 = 2 ^ (32 - 20) - 2 = 8190
  24. 子网可用IP地址范围 = 128.36.192.1 ~ 128.36.207.254

C\C++知识点复习补充:equal_range算法

功能:C++ STL中提供的二分查找算法,在一个已排序的容器内查找等于key值的对象,并返回一个迭代器区间[first, second)(其中可能包含多个相等key的对象)

注意:必须作用于一个有序序列,否则没有意义。

  • 用例: ```cpp

    include

    include

    include

using namespace std;

int main() { std::vector mv = {3, -1, 2, 1, 2, 10, 3, 4, 9}; sort(mv.begin(), mv.end());

  1. std::cout << "排序后数组:";
  2. for(auto it = mv.begin();it != mv.end();++it)
  3. std:cout << *it << " ";
  4. std::cout << std::endl;
  5. auto its = equal_range(mv.begin(), mv.end(), 2);
  6. std::cout << "区间为:";
  7. for(auto it = its.first;it != its.second;++it)
  8. std::cout << *it << ' ';
  9. std::cout << std::endl;
  10. return 0;

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25460685/1644983640569-3cb48acc-e67c-4c01-9f74-2dff6add621d.png#clientId=ua89cab37-d4c3-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=66&id=u06017e86&margin=%5Bobject%20Object%5D&name=image.png&originHeight=66&originWidth=500&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9008&status=done&style=none&taskId=ufde38e52-f649-4fe3-bdf5-3d08b347ea5&title=&width=500)
  2. <a name="umpUU"></a>
  3. # C\C++知识点复习补充:getpeername()函数
  4. 功能:在 addr 指向的缓冲区中返回连接到套接字`sockfd` 的对方的地址。 应初始化 addrlen 参数以指示 addr 指向的空间量。
  5. 返回值:成功时,返回零。 出错时,返回 -1,并适当设置 errno
  6. ```cpp
  7. #include <sys/socket.h>
  8. int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

C\C++知识点复习补充:getsockname()函数

功能:返回套接字 sockfd 绑定的当前地址,在 addr 指向的缓冲区中。 应初始化 addrlen 参数以指示 addr 指向的空间量(以字节为单位)

返回值:成功时,返回零。 出错时,返回 -1,并适当设置 errno。

  1. #include <sys/socket.h>
  2. int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

TCP连接中启用和禁用TCP_NODELAY有什么影响?

1.nagel算法概念

试想,在某通信情景下,每次只发送1字节的有效数据,但网络中数据包大小为41字节(IP头部 20字节 + TCP头部 20字节 + 有用数据 1字节),不仅浪费了大量资源,还会造成网络拥塞。
nagel算法解决的问题:避免发送过小的数据报文段,允许网络中最多只能有一个小分组被发送,而待发送的其它小分组会被重新分组成一个”较大的”小分组,等收到上一个小分组的应答后再发送。TCP将收集这些少量的小分组,在ACK到来时凑成一个分组发送出去,某种程度上是一种延时发送。

  • nagel算法伪代码描述:
    1. if 新的数据需要被发送
    2. {
    3. if 发送窗口大小 >= MSS || 有效数据长度 >= MSS
    4. {
    5. 即刻发送该数据
    6. }
    7. else
    8. {
    9. if 数据管道内仍有未被确认的分组
    10. 将该数据放入到缓冲区直到遗留的数据被确认,再进行发送
    11. else
    12. 即刻发送该数据
    13. }
    14. }

2. nagel算法验证试验

用客户端逐一字节发送”hello world”给服务器,通过抓包工具wireshark 观察一下开启和关闭nagel算法发包情况。

2.1 nagel算法开启

  1. #include <iostream>
  2. #include <sys/socket.h>
  3. #include <sys/types.h>
  4. #include <arpa/inet.h>
  5. int main()
  6. {
  7. int fd = socket(AF_INET, SOCK_STREAM, 0);
  8. if(fd < 0)
  9. {
  10. std::cout << "socket error, errno=" << errno
  11. << ", is:" << strerror(errno) << std::endl;
  12. return 0;
  13. }
  14. struct sockaddr_in sockaddr;
  15. memset(&sockaddr, 0, sizeof(sockaddr));
  16. sockaddr.sin_family = AF_INET;
  17. sockaddr.sin_port = htons(8888);
  18. sockaddr.sin_addr.s_addr = inet_addr("192.168.77.1");
  19. if(connect(fd, (struct sockaddr*)&sockaddr, sizeof(struct sockaddr)) < 0)
  20. {
  21. std::cout << "connect error, errno=" << errno
  22. << ", is:" << strerror(errno) << std::endl;
  23. return 0;
  24. }
  25. /*字符串按照1字节逐一发送*/
  26. std::string buf = "hello world";
  27. for(int i = 0;i < buf.size();++i)
  28. {
  29. send(fd, &buf[i], 1, 0);
  30. }
  31. std::cout << "send ok!" << std::endl;
  32. close(fd);
  33. return 0;
  34. }

image.png
image.png
image.png

2.3 nagel算法关闭

  1. #include <iostream>
  2. #include <sys/socket.h>
  3. #include <sys/types.h>
  4. #include <arpa/inet.h>
  5. #include <netinet/tcp.h>
  6. int main()
  7. {
  8. int fd = socket(AF_INET, SOCK_STREAM, 0);
  9. if(fd < 0)
  10. {
  11. std::cout << "socket error, errno=" << errno
  12. << ", is:" << strerror(errno) << std::endl;
  13. return 0;
  14. }
  15. //关闭nagel算法
  16. int op = 1;
  17. setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &op, sizeof(op));
  18. struct sockaddr_in sockaddr;
  19. memset(&sockaddr, 0, sizeof(sockaddr));
  20. sockaddr.sin_family = AF_INET;
  21. sockaddr.sin_port = htons(8888);
  22. sockaddr.sin_addr.s_addr = inet_addr("192.168.77.1");
  23. if(connect(fd, (struct sockaddr*)&sockaddr, sizeof(struct sockaddr)) < 0)
  24. {
  25. std::cout << "connect error, errno=" << errno
  26. << ", is:" << strerror(errno) << std::endl;
  27. return 0;
  28. }
  29. /*字符串按照1字节逐一发送*/
  30. std::string buf = "hello world";
  31. for(int i = 0;i < buf.size();++i)
  32. {
  33. send(fd, &buf[i], 1, 0);
  34. }
  35. std::cout << "send ok!" << std::endl;
  36. close(fd);
  37. return 0;
  38. }

image.png
image.png
image.png

3. 分析和结论

一般情况下,nagel算法是默认开启的。适用于:大批量的小分组发送的场景
Nagle指出Nagle算法与Delay ACK机制有共存的情况下会有一些非常糟糕的状况:
比如举一个场景:PC1和PC2进行通信,PC1发数据给PC2,PC1使用Nagle算法,PC2有delay ACK机制

  1. PC1发送一个数据包给PC2,PC2会先不回应,delay ACK
  2. PC1再次发送小于MSS的数据,这些数据会保存到缓冲区中,等待ACK,才能再次被发送

以上这种场景已经造成某种程度的”死锁”:PC1在等待ACK,PC2却在延迟发送ACK(累计ACK回执响应),解锁的可能就是:Delay ACK的定时器到期,至少等待40~500ms不等,是一种不必要的延时。

  • 解决与避免
  1. 在TCP的RFC文档中,给出了用户编程的规范:避免 write-write-read的编程方法,连续两次发送后等待回执响应。write-read-write-read 以及 write-wtire-wtire都是没有问题的.
  2. 禁用nagel算法setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &op, sizeof(op));

C\C++知识点复习补充:__builtin_expect编译优化

概念:现在处理器都是流水线的,系统可以提前取多条指令进行并行处理,但遇到跳转时,则需要重新取指令,跳转指令打乱了CPU流水线。因此,跳转次数少的程序拥有更高的执行效率。

在C语言编程时,会不可避免地使用if-else分支语句,if-else 句型编译后, 一个分支的汇编代码紧随前面的代码,而另一个分支的汇编代码需要使用JMP xxx才能访问到。很明显通过JMP访问需要更多的时间,在复杂的程序中,有很多的if-else句型,又或者是一个有if-else句型的库函数,每秒钟被调用几万次,通常程序员在分支预测方面做得很糟糕,,编译器又不能精准的预测每一个分支,这时JMP产生的时间浪费就会很大。

  • 通常封装方法:
    1. #define likely(x) __builtin_expect(!!(x), 1) //x条件很可能为真
    2. #define unlikely(x) __builtin_expect(!!(x), 0) //x条件很可能为假