zlMediaKit 5 socketUtil模块--封装常用的操作+DNSCache

sockutil.h

SockUtil

套接字工具类,封装了网络的一些基本操作

封装socket相关的基本操作

/**
 * 线程安全的in_addr转ip字符串。 原来的inet_ntoa内有静态变量线程不安全, 复写后inet_ntop,返回字符串,线程安全
 */
static std::string inet_ntoa(const struct in_addr &addr);
static std::string inet_ntoa(const struct in6_addr &addr);
static std::string inet_ntoa(const struct sockaddr *addr);
string SockUtil::inet_ntoa(const struct in_addr &addr)

setsockopt

int SockUtil::setNoSigpipe(int fd) //SO_NOSIGPIPE
int SockUtil::setCloExec(int fd, bool on)//FD_CLOEXEC 表示子进程在执行exec的时候,该文件描述符就需要进行关闭
int SockUtil::setKeepAlive(int fd, bool on)//SO_KEEPALIVE //tcp的keepalive 并不能准确体现应用层的可用性,只是传输层可用 https://www.zhihu.com/question/40602902
int SockUtil::setBroadcast(int fd, bool on) //SO_BROADCAST 允许广播
int SockUtil::setReuseable(int fd, bool on, bool reuse_port)//SO_REUSEADDR | SO_REUSEPORT
int SockUtil::setNoDelay(int fd, bool on)//TCP_NODELAY //否开启Nagle算法
int SockUtil::setCloseWait(int fd, int second)// SO_LINGER
int SockUtil::setRecvBuf(int fd, int size)  //SO_SNDBUF 设置收发缓冲区的大小
int SockUtil::setSendBuf(int fd, int size)  //SO_RCVBUF

SO_LINGER和优雅关闭连接以及短链接TIME_WAIT问题解决方法 在调用closesocket()时还有数据未发送完,允许等待, SO_LINGER可以取消等待抛弃数据,或者设置最长等待时间

bind

static int bind_sock(int fd, const char *ifr_ip, uint16_t port, int family)
static int bind_sock4(int fd, const char *ifr_ip, uint16_t port)
static int bind_sock6(int fd, const char *ifr_ip, uint16_t port)
{htons && inet_pton && ::bind}

static int set_ipv6_only(int fd, bool flag)//IPV6_V6ONLY
//如果该标志设置为true(非零),则套接字只能发送和接收IPv6报文。 此时,可以同时绑定IPv4和IPv6应用程序的同一端口。

connect

int SockUtil::connect(const char *host, uint16_t port, bool async, const char *local_ip, uint16_t local_port)
{
    getDomainIP()
    setReuseable(sockfd); //设置后续可绑定复用端口(处于TIME_WAITE状态)
    setNoSigpipe(sockfd); //写socket不触发SIG_PIPE信号(貌似只有mac有效)
    setNoBlocked(sockfd, async);//设置读写socket是否阻塞
    setNoDelay(sockfd);//开启TCP_NODELAY,降低TCP交互延时
    setSendBuf(sockfd);//设置socket接收缓存,默认貌似8K左右,一般有设置上限 可以通过配置内核配置文件调整
    setRecvBuf(sockfd);//设置socket接收缓存,默认貌似8K左右,一般有设置上限 可以通过配置内核配置文件调整
    setCloseWait(sockfd);//开启SO_LINGER特性
    setCloExec(sockfd);//是否开启FD_CLOEXEC特性(多进程相关)
    
    bind_sock
        
    ::connect
}

listen

int SockUtil::listen(const uint16_t port, const char *local_ip, int back_log)
{
    setReuseable(fd, true, false);//设置后续可绑定复用端口(处于TIME_WAITE状态)
    setNoBlocked(fd);//设置读写socket是否阻塞
    setCloExec(fd);//是否开启FD_CLOEXEC特性(多进程相关)
    
    bind_sock
    ::listen
}

bind/unbind udp

int SockUtil::bindUdpSock(const uint16_t port, const char *local_ip, bool enable_reuse) {
	setReuseable(fd);//设置后续可绑定复用端口(处于TIME_WAITE状态)
	setNoSigpipe(fd);//写socket不触发SIG_PIPE信号(貌似只有mac有效)
    setNoBlocked(fd);//设置读写socket是否阻塞
    setSendBuf(fd);  //设置socket接收缓存,默认貌似8K左右,一般有设置上限 可以通过配置内核配置文件调整
    setRecvBuf(fd);  //设置socket接收缓存,默认貌似8K左右,一般有设置上限 可以通过配置内核配置文件调整
    setCloseWait(fd);//开启SO_LINGER特性
    setCloExec(fd);  //是否开启FD_CLOEXEC特性(多进程相关)
	bind_sock(...)
}

int SockUtil::dissolveUdpSock(int fd)
{
    getsockname(fd, (struct sockaddr *)&addr, &addr_len); //获取本机的地址
    ::connect(fd, (struct sockaddr *)&addr, addr_len);    //连接本机就能解除udp连接了嘛
}

make_sockaddr

sockaddr_in、sockaddr_in6、sockaddr_storage

sockaddr传统的通用套接字地址结构,问题在于不方便赋值,一般都是用在传参是强转

sockaddr_storage新的通用套接字地址,

struct sockaddr_storage SockUtil::make_sockaddr(const char *host, uint16_t port) {
    struct sockaddr_storage storage;
    bzero(&storage, sizeof(storage));

    struct in_addr addr;
    struct in6_addr addr6;
    if (1 == inet_pton(AF_INET, host, &addr)) {
        // host是ipv4
        reinterpret_cast<struct sockaddr_in &>(storage).sin_addr = addr;
        reinterpret_cast<struct sockaddr_in &>(storage).sin_family = AF_INET;
        reinterpret_cast<struct sockaddr_in &>(storage).sin_port = htons(port);
        return storage;
    }
    if (1 == inet_pton(AF_INET6, host, &addr6)) {
        // host是ipv6
        reinterpret_cast<struct sockaddr_in6 &>(storage).sin6_addr = addr6;
        reinterpret_cast<struct sockaddr_in6 &>(storage).sin6_family = AF_INET6;
        reinterpret_cast<struct sockaddr_in6 &>(storage).sin6_port = htons(port);
        return storage;
    }
    throw std::invalid_argument(string("not ip address:") + host);
}
// 判断ip4还是ip6地址
bool SockUtil::is_ipv4(const char *host) {
	inet_pton(AF_INET, host, &addr);
}
bool SockUtil::is_ipv6(const char *host) 

getsockname | getpeername

using getsockname_type = decltype(getsockname);//decltype的使用
static string get_socket_ip(int fd, getsockname_type func) {
    struct sockaddr_storage addr;
    socklen_t addr_len = sizeof(addr);
    if (-1 == func(fd, (struct sockaddr *)&addr, &addr_len)) {
        return "";
    }
    return SockUtil::inet_ntoa((struct sockaddr *)&addr);
}

static uint16_t get_socket_port(int fd, getsockname_type func) {
    struct sockaddr_storage addr;
    socklen_t addr_len = sizeof(addr);
    if (-1 == func(fd, (struct sockaddr *)&addr, &addr_len)) {
        return 0;
    }
    return SockUtil::inet_port((struct sockaddr *)&addr);
}

string SockUtil::get_local_ip(int fd) {
    return get_socket_ip(fd, getsockname);
}

string SockUtil::get_peer_ip(int fd) {
    return get_socket_ip(fd, getpeername);
}

uint16_t SockUtil::get_local_port(int fd) {
    return get_socket_port(fd, getsockname);
}

uint16_t SockUtil::get_peer_port(int fd) {
    return get_socket_port(fd, getpeername);
}

网卡相关

template<typename FUN>
void for_each_netAdapter_posix(FUN &&fun){ //type: struct ifreq *
    //读取系统中的所有网卡的ip地址//...厉害了
    ioctl(sockfd, SIOCGIFCONF, &ifconf)
        
    //接下来一个一个的获取IP地址
    struct ifreq * adapter = (struct ifreq*) buf;
    for (int i = (ifconf.ifc_len / sizeof(struct ifreq)); i > 0; --i,++adapter) {
        if(fun(adapter)){
            break;
        }
    }
}

// 找到第一个非docker的网口地址ip
string SockUtil::get_local_ip() {
    string address = "127.0.0.1";
    for_each_netAdapter_posix([&](struct ifreq *adapter){
        string ip = SockUtil::inet_ntoa(&(adapter->ifr_addr));
        if (strstr(adapter->ifr_name, "docker")) {
            return false;
        }
        return check_ip(address,ip); 
    });
    return address;
}

// 获取所有网卡的ip
vector<map<string, string>> SockUtil::getInterfaceList() {
    for_each_netAdapter_posix([&](struct ifreq *adapter){
        map<string,string> obj;
        obj["ip"] = SockUtil::inet_ntoa(&(adapter->ifr_addr));
        obj["name"] = adapter->ifr_name;
        ret.emplace_back(std::move(obj));
        return false;
    });
}

// 获取指定名称的网卡ip
string SockUtil::get_ifr_ip(const char *if_name)
// 获取指定ip的网卡名称
string SockUtil::get_ifr_name(const char *local_ip) 
// 获取子网掩码
string SockUtil::get_ifr_mask(const char *if_name)
// 根据网卡名获取广播地址
string SockUtil::get_ifr_brdaddr(const char *if_name)

组播相关

static void clearMulticastAllSocketOption(int socket) //确保我们只接收发送到指定IP组播地址的数据包,即使在同一系统上的其他进程加入了不同的组播组,且端口相同
int SockUtil::setMultiTTL(int fd, uint8_t ttl) //IP_MULTICAST_TTL 设置组播ttl
int SockUtil::setMultiIF(int fd, const char *local_ip)//IP_MULTICAST_IF 设置组播发送网卡 
int SockUtil::setMultiLOOP(int fd, bool accept) //IP_MULTICAST_LOOP 设置是否接收本机发出的组播包
int SockUtil::joinMultiAddr(int fd, const char *addr, const char *local_ip)//IP_ADD_MEMBERSHIP 加入组播
int SockUtil::leaveMultiAddr(int fd, const char *addr, const char *local_ip)//IP_DROP_MEMBERSHIP 退出组播    
// 模板形式的memcpy
template<typename A, typename B>
static inline void write4Byte(A &&a, B &&b) {
    memcpy(&a, &b, sizeof(a));
}

getDomainIP 解析host

DnsCache::Instance().getDomainIP

DnsCache

结构

//DnsCache
mutex _mtx;
unordered_map<string, DnsItem> _dns_cache;

//DnsItem dns == addr_info 
std::shared_ptr<struct addrinfo> addr_info;
time_t create_time;//还有一个时间判断是否过期

//addrinfo结构就存储了nds的所有信息了
struct addrinfo
{
  int ai_flags;			/* Input flags.  */
  int ai_family;		/* Protocol family for socket.  */
  int ai_socktype;		/* Socket type.  */
  int ai_protocol;		/* Protocol for socket.  */
  socklen_t ai_addrlen;		/* Length of socket address.  */
  struct sockaddr *ai_addr;	/* Socket address for socket.  */
  char *ai_canonname;		/* Canonical name for service location.  */
  struct addrinfo *ai_next;	/* Pointer to next in list.  */
};
//+ 获取host对应的ip放入sockaddr_storage中
bool getDomainIP(const char *host, sockaddr_storage &storage, int ai_family = AF_INET,
int ai_socktype = SOCK_STREAM, int ai_protocol = IPPROTO_TCP, int expire_sec = 60)
{
    //1 数字类的先尝试inet_pton转换
    //2 缓存里查
    //3 系统调用查,缓存保存
    //4 返回结果
}    
    
//-
//获取/设置 缓存的dns
std::shared_ptr<struct addrinfo> getCacheDomainIP(const char *host, int expireSec)
void setCacheDomainIP(const char *host, std::shared_ptr<struct addrinfo> addr)
//获取系统解析的dns
std::shared_ptr<struct addrinfo> getSystemDomainIP(const char *host)
{
    getaddrinfo(...struct addrinfo **answer)//系统调用,是阻塞式的且有可能被打断
    std::shared_ptr<struct addrinfo>(answer, freeaddrinfo);//自己传入dellter,析构
} 
//获取下一个地址
struct addrinfo *getPerferredAddress(struct addrinfo *answer, int ai_family, int ai_socktype, int ai_protocol)
    
//一个DNS可能对应多个ip,struct addrinfo *ai_next;链表
./test www.baidu.com
14.215.177.38
14.215.177.39

补充

字节序转换和地址转换

字节序转换函数htons、htonl

#include <arpa/inet.h>
// 将 32位主机字节序数据转换成网络字节序数据
//(h:host, n:net,l:long)
uint32_t htonl(uint32_t hostint32);
// 将 16 位主机字节序数据转换成网络字节序数据
uint16_t htons(uint16_t hostint16);
// 将 32 位网络字节序数据转换成主机字节序数据
uint32_t ntohl(uint32_t netint32);
// 将 16 位网络字节序数据转换成主机字节序数据
uint16_t ntohs(uint16_t netint16);

htons是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用big-endian排序方式。
htons的功能:将一个无符号短整型的主机数值转换为网络字节顺序,即大尾顺序(big-endian)

htonl,其实是host to network, l 的意思是返回类型是long. 将主机数转换成无符号长整型的网络字节顺序。本函数将一个32位数从主机字节顺序转换成网络字节顺序。

地址转换函数

【inet_ntoa()】是编程语言,功能是将网络地址转换成“.”点隔的字符串格式。
函数声明:char *inet_ntoa(struct in_addr in);
将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址(返回点分十进制的字符串在静态内存中的指针)。
【点分十进制】(Dotted Decimal Notation)全称为点分(点式)十进制表示法,是IPv4的IP地址标识方法。IPv4中用四个字节表示一个IP地址,每个字节按照十进制表示为0~255。
点分十进制就是用4个从0~255的数字,来表示一个IP地址。如192.168.1.1。

【inet_ntoa】 将 十进制网络字节序 转换为 点分十进制IP格式的字符串。
【inet_pton】 是一个IP地址转换函数,将 点分十进制的IP地址 转换为 二进制网络字节序 的IP地址。
【inet_ntop】 是一个IP地址转换函数,将 二进制网络字节序的IP地址 转换为 点分十进制的IP地址。
【inet_addr】 函数可以把 点分十进制的IP地址字符串 转化为 二进制网络字节序 的IP地址,如inet_addr(“192.168.1.166”)
inet_addr 是一个计算机函数,功能是将一个点分十进制的IP转换成一个长整数型数(u_long类型)等同于inet_addr()
【inet_addr】 函数可以转化字符串,主要用来将一个十进制的数转化为二进制的数,用途多于ipv4的IP转化。
返回:若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE

总结

  • sockaddr_storage | sockaddr_in | sockaddr_in6 | sockaddr | 字节序转换和地址转换

  • 如何获取DNS,生成一个DNS缓存查询表 getaddrinfo | freeaddrinfo

  • 如何查询网卡的信息,对网卡进行一些操作

  • socketopt的各种操作

常见的操作争取记住,记不住也要有印象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值