概述
本部分主要看下sockutil.h和sockutil.cpp两个文件。该模块统一封装不同平台下的网络相关API,作为后续网络编程的基础接口
分析
网络基础接口
SockUtil::connect(作为客户端并连接服务器)
声明:
/**
* 创建tcp客户端套接字并连接服务器
* @param host 服务器ip或域名
* @param port 服务器端口号
* @param async 是否异步连接
* @param local_ip 绑定的本地网卡ip
* @param local_port 绑定的本地端口号
* @return -1代表失败,其他为socket fd号
*/
static int connect(const char *host, uint16_t port, bool async = true, const char *local_ip = "::", uint16_t local_port = 0);
框架:
int SockUtil::connect(const char *host, uint16_t port, bool async, const char *local_ip, uint16_t local_port) {
sockaddr_storage addr;
......
int sockfd = (int) socket(addr.ss_family, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {
WarnL << "创建套接字失败:" << host;
return -1;
}
.....
if (bind_sock(sockfd, local_ip, local_port, addr.ss_family) == -1) {
close(sockfd);
return -1;
}
....
if (::connect(sockfd, (sockaddr *) &addr, len) == 0) {
//同步连接成功
return sockfd;
}
.....
WarnL << "连接主机失败:" << host << " " << port << " " << get_uv_errmsg(true);
close(sockfd);
return -1;
}
SockUtil::listen(创建一个TCP服务端)
声明:
/**
* 创建tcp监听套接字
* @param port 监听的本地端口
* @param local_ip 绑定的本地网卡ip
* @param back_log accept列队长度
* @return -1代表失败,其他为socket fd号
*/
static int listen(const uint16_t port, const char *local_ip = "::", int back_log = 1024);
关键:
int SockUtil::listen(const uint16_t port, const char *local_ip, int back_log) {
...
if ((fd = (int)socket(family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
WarnL << "创建套接字失败:" << get_uv_errmsg(true);
return -1;
}
...
if (bind_sock(fd, local_ip, port, family) == -1) {
close(fd);
return -1;
}
//开始监听
if (::listen(fd, back_log) == -1) {
WarnL << "开始监听失败:" << get_uv_errmsg(true);
close(fd);
return -1;
}
return fd;
}
SockUtil::bindUdpSock(创建一个UDP套接字)
int SockUtil::bindUdpSock(const uint16_t port, const char *local_ip, bool enable_reuse) {
......
if ((fd = (int)socket(family, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
WarnL << "创建套接字失败:" << get_uv_errmsg(true);
return -1;
}
......
if (bind_sock(fd, local_ip, port, family) == -1) {
close(fd);
return -1;
}
return fd;
}
然后我们来看看bind_sock是怎么实现的
bind_sock:绑定socket fd到某个网卡和端口
static int bind_sock(int fd, const char *ifr_ip, uint16_t port, int family) {
switch (family) {
case AF_INET: return bind_sock4(fd, ifr_ip, port);
case AF_INET6: return bind_sock6(fd, ifr_ip, port);
default: assert(0); return -1;
}
}
有两种情况:使用的地址是IPv4型的,使用的地址是IPv6型的。但是它们都是调用bind接口来绑定
static int bind_sock4(XXXX, const char *ifr_ip, uint16_t port) {
struct sockaddr_in addr;
.....
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
WarnL << "绑定套接字失败:" << get_uv_errmsg(true);
return -1;
}
return 0;
}
static int bind_sock6(XXXX, const char *ifr_ip, uint16_t port) {
struct sockaddr_in6 addr;
......
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
WarnL << "绑定套接字失败:" << get_uv_errmsg(true);
return -1;
}
return 0;
}
那它们之间有什么不同吗?没啥不同,参数都是一样的,只是一个将IP和PORT转成sockaddr_in ,一个转成sockaddr_in6
static int bind_sock4(int fd, const char *ifr_ip, uint16_t port) {
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (1 != inet_pton(AF_INET, ifr_ip, &(addr.sin_addr))) {
if (strcmp(ifr_ip, "::")) {
WarnL << "inet_pton to ipv4 address failed:" << ifr_ip;
}
addr.sin_addr.s_addr = INADDR_ANY;
}
.....
}
static int bind_sock6(int fd, const char *ifr_ip, uint16_t port) {
set_ipv6_only(fd, false); // -----
struct sockaddr_in6 addr;
bzero(&addr, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);
if (1 != inet_pton(AF_INET6, ifr_ip, &(addr.sin6_addr))) {
if (strcmp(ifr_ip, "0.0.0.0")) {
WarnL << "inet_pton to ipv6 address failed:" << ifr_ip;
}
addr.sin6_addr = IN6ADDR_ANY_INIT;
}
...
}
有一个不同的是,当使用IPv6时,需要设置套接字选项IPPROTO_IPV6:
static int set_ipv6_only(int fd, bool flag) {
int opt = flag;
int ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof opt);
if (ret == -1) {
TraceL << "设置 IPV6_V6ONLY 失败!";
}
return ret;
}
当设置了IPPROTO_IPV6之后,就可以设置如下选项了
dissolveUdpSock(不理解???)
不理解,命名是绑定,为什么说解绑?
/**
* @brief 解除与 sock 相关的绑定关系
* @param sock, socket fd 号
* @return 0 成功, -1 失败
*/
static int dissolveUdpSock(int sock);
int SockUtil::dissolveUdpSock(int fd) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
if (-1 == getsockname(fd, (struct sockaddr *)&addr, &addr_len)) {
return -1;
}
addr.ss_family = AF_UNSPEC;
if (-1 == ::connect(fd, (struct sockaddr *)&addr, addr_len) && get_uv_error() != UV_EAFNOSUPPORT) {
// mac/ios时返回EAFNOSUPPORT错误
WarnL << "解除 UDP 套接字连接关系失败: " << get_uv_errmsg(true);
return -1;
}
return 0;
}
IP端口信息
in_same_lan: 判断两个ip是否为同一网段
#define ip_addr_netcmp(addr1, addr2, mask) (((addr1) & (mask)) == ((addr2) & (mask)))
bool SockUtil::in_same_lan(const char *myIp, const char *dstIp) {
string mask = get_ifr_mask(get_ifr_name(myIp).data());
return ip_addr_netcmp(inet_addr(myIp), inet_addr(dstIp), inet_addr(mask.data()));
}
getDomainIP:从域名解析得到IP
声明:
/**
* dns解析
* @param host 域名或ip
* @param port 端口号
* @param addr sockaddr结构体
* @return 是否成功
*/
static bool getDomainIP(const char *host, uint16_t port, struct sockaddr_storage &addr, int ai_family = AF_INET,
int ai_socktype = SOCK_STREAM, int ai_protocol = IPPROTO_TCP, int expire_sec = 60);
关键:
1. 解析IP存储到参数addr中:
bool SockUtil::getDomainIP(const char *host, ..... struct sockaddr_storage &addr,
int ai_family, int ai_socktype, int ai_protocol, int expire_sec) {
bool flag = DnsCache::Instance().getDomainIP(host, addr, ai_family, ai_socktype, ai_protocol, expire_sec);
.....
}
2. 设置PORT:
bool SockUtil::getDomainIP(......., uint16_t port, ......) {
if (解析成功) {
switch (addr.ss_family ) {
case AF_INET : ((sockaddr_in *) &addr)->sin_port = htons(port); break;
case AF_INET6 : ((sockaddr_in6 *) &addr)->sin6_port = htons(port); break;
default: assert(0); break;
}
}
return flag;
}
实现:
bool SockUtil::getDomainIP(const char *host, uint16_t port, struct sockaddr_storage &addr,
int ai_family, int ai_socktype, int ai_protocol, int expire_sec) {
bool flag = DnsCache::Instance().getDomainIP(host, addr, ai_family, ai_socktype, ai_protocol, expire_sec);
if (flag) {
switch (addr.ss_family ) {
case AF_INET : ((sockaddr_in *) &addr)->sin_port = htons(port); break;
case AF_INET6 : ((sockaddr_in6 *) &addr)->sin6_port = htons(port); break;
default: assert(0); break;
}
}
return flag;
}
也就是关键是由DnsCache::Instance()。。。实现的。关于DnsCache::Instance()干了写啥,我们稍后再说
怎么调用:
sockaddr_storage addr;
//优先使用ipv4地址
if (!getDomainIP(host, port, addr, AF_INET, SOCK_STREAM, IPPROTO_TCP)) {
//dns解析失败
return -1;
}
is_ipv4 && is_ipv6:判断是否为ipv4/ipv6地址
bool SockUtil::is_ipv4(const char *host) {
struct in_addr addr;
return 1 == inet_pton(AF_INET, host, &addr);
}
bool SockUtil::is_ipv6(const char *host) {
struct in6_addr addr;
return 1 == inet_pton(AF_INET6, host, &addr);
}
get_xxx_port,get_xxx_ip:获取fd绑定的本地/远端IP和端口
using getsockname_type = decltype(getsockname);
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);
}
inet_ntoa:线程安全的in_addr转ip字符串
/**
* 线程安全的in_addr转ip字符串
*/
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);
static uint16_t inet_port(const struct sockaddr *addr);
static struct sockaddr_storage make_sockaddr(const char *ip, uint16_t port);
实现:
static inline string my_inet_ntop(int af, const void *addr) {
string ret;
ret.resize(128);
if (!inet_ntop(af, addr, (char *) ret.data(), ret.size())) {
ret.clear();
} else {
ret.resize(strlen(ret.data()));
}
return ret;
}
string SockUtil::inet_ntoa(const struct in_addr &addr) {
return my_inet_ntop(AF_INET, &addr);
}
std::string SockUtil::inet_ntoa(const struct in6_addr &addr) {
return my_inet_ntop(AF_INET6, &addr);
}
std::string SockUtil::inet_ntoa(const struct sockaddr *addr) {
switch (addr->sa_family) {
case AF_INET: return SockUtil::inet_ntoa(((struct sockaddr_in *)addr)->sin_addr);
case AF_INET6: {
if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)addr)->sin6_addr)) {
struct in_addr addr4;
memcpy(&addr4, 12 + (char *)&(((struct sockaddr_in6 *)addr)->sin6_addr), 4);
return SockUtil::inet_ntoa(addr4);
}
return SockUtil::inet_ntoa(((struct sockaddr_in6 *)addr)->sin6_addr);
}
default: assert(false); return "";
}
}
uint16_t SockUtil::inet_port(const struct sockaddr *addr) {
switch (addr->sa_family) {
case AF_INET: return ntohs(((struct sockaddr_in *)addr)->sin_port);
case AF_INET6: return ntohs(((struct sockaddr_in6 *)addr)->sin6_port);
default: assert(false); return 0;
}
}
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);
}
设置socket
setNoDelay:开启TCP_NODELAY
/**
* 开启TCP_NODELAY,降低TCP交互延时
* @param fd socket fd号
* @param on 是否开启
* @return 0代表成功,-1为失败
*/
static int setNoDelay(int fd, bool on = true);
int SockUtil::setNoDelay(int fd, bool on) {
int opt = on ? 1 : 0;
int ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, static_cast<socklen_t>(sizeof(opt)));
if (ret == -1) {
TraceL << "设置 NoDelay 失败!";
}
return ret;
}
setNoBlocked
/**
* 设置读写socket是否阻塞
* @param fd socket fd号
* @param noblock 是否阻塞
* @return 0代表成功,-1为失败
*/
static int setNoBlocked(int fd, bool noblock = true);
int SockUtil::setNoBlocked(int fd, bool noblock) {
int ul = noblock;
int ret = ioctl(fd, FIONBIO, &ul); //设置为非阻塞模式
if (ret == -1) {
TraceL << "设置非阻塞失败!";
}
return ret;
}
setReuseable
/**
* 设置后续可绑定复用端口(处于TIME_WAITE状态)
* @param fd socket fd号
* @param on 是否开启该特性
* @return 0代表成功,-1为失败
*/
static int setReuseable(int fd, bool on = true, bool reuse_port = true);
int SockUtil::setReuseable(int fd, bool on, bool reuse_port) {
int opt = on ? 1 : 0;
int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, static_cast<socklen_t>(sizeof(opt)));
if (ret == -1) {
TraceL << "设置 SO_REUSEADDR 失败!";
return ret;
}
if (reuse_port) {
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &opt, static_cast<socklen_t>(sizeof(opt)));
if (ret == -1) {
TraceL << "设置 SO_REUSEPORT 失败!";
}
}
return ret;
}
setRecvBuf&&setSendBuf
/**
* 设置socket接收缓存,默认貌似8K左右,一般有设置上限
* 可以通过配置内核配置文件调整
* @param fd socket fd号
* @param size 接收缓存大小
* @return 0代表成功,-1为失败
*/
static int setRecvBuf(int fd, int size = SOCKET_DEFAULT_BUF_SIZE);
/**
* 设置socket接收缓存,默认貌似8K左右,一般有设置上限
* 可以通过配置内核配置文件调整
* @param fd socket fd号
* @param size 接收缓存大小
* @return 0代表成功,-1为失败
*/
static int setSendBuf(int fd, int size = SOCKET_DEFAULT_BUF_SIZE);
int SockUtil::setRecvBuf(int fd, int size) {
int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size));
if (ret == -1) {
TraceL << "设置接收缓冲区失败!";
}
return ret;
}
int SockUtil::setSendBuf(int fd, int size) {
int ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &size, sizeof(size));
if (ret == -1) {
TraceL << "设置发送缓冲区失败!";
}
return ret;
}
setBroadcast
/**
* 运行发送或接收udp广播信息
* @param fd socket fd号
* @param on 是否开启该特性
* @return 0代表成功,-1为失败
*/
static int setBroadcast(int fd, bool on = true);
int SockUtil::setBroadcast(int fd, bool on) {
int opt = on ? 1 : 0;
int ret = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &opt, static_cast<socklen_t>(sizeof(opt)));
if (ret == -1) {
TraceL << "设置 SO_BROADCAST 失败!";
}
return ret;
}
socket默认不支持发送广播报文,通过SO_BROADCAST选项的设置,开启广播发送功能。简单总结一下广播报文收发的规律:
- 客户端socket开启SO_BROADCAST选项后才能发送广播报文,否则调用sendto会报错
- 服务端无需开启SO_BROADCAST
- 服务端bind单播地址时,不接受客户端的广播数据,仅接受目的地址为单播地址的报文
- 服务端bind广播地址时,接收客户端的广播数据
- 服务端bind通用地址INADDR_ANY时,既能够接收客户端的单播报文,也能接收广播报文
setKeepAlive
/**
* 是否开启TCP KeepAlive特性
* @param fd socket fd号
* @param on 是否开启该特性
* @return 0代表成功,-1为失败
*/
static int setKeepAlive(int fd, bool on = true);
int SockUtil::setKeepAlive(int fd, bool on) {
int opt = on ? 1 : 0;
int ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, static_cast<socklen_t>(sizeof(opt)));
if (ret == -1) {
TraceL << "设置 SO_KEEPALIVE 失败!";
}
return ret;
}
setCloExec
/**
* 是否开启FD_CLOEXEC特性(多进程相关)
* @param fd fd号,不一定是socket
* @param on 是否开启该特性
* @return 0代表成功,-1为失败
*/
static int setCloExec(int fd, bool on = true);
int SockUtil::setCloExec(int fd, bool on) {
int flags = fcntl(fd, F_GETFD);
if (flags == -1) {
TraceL << "设置 FD_CLOEXEC 失败!";
return -1;
}
if (on) {
flags |= FD_CLOEXEC;
} else {
int cloexec = FD_CLOEXEC;
flags &= ~cloexec;
}
int ret = fcntl(fd, F_SETFD, flags);
if (ret == -1) {
TraceL << "设置 FD_CLOEXEC 失败!";
return -1;
}
return ret;
}
setCloseWait
/**
* 开启SO_LINGER特性
* @param sock socket fd号
* @param second 内核等待关闭socket超时时间,单位秒
* @return 0代表成功,-1为失败
*/
static int setCloseWait(int sock, int second = 0);
int SockUtil::setCloseWait(int fd, int second) {
linger m_sLinger;
//在调用closesocket()时还有数据未发送完,允许等待
// 若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭
m_sLinger.l_onoff = (second > 0);
m_sLinger.l_linger = second; //设置等待时间为x秒
int ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &m_sLinger, sizeof(linger));
if (ret == -1) {
TraceL << "设置 SO_LINGER 失败!";
}
return ret;
}
组播设置
static void clearMulticastAllSocketOption(int socket) {
int multicastAll = 0;
(void)setsockopt(socket, IPPROTO_IP, IP_MULTICAST_ALL, (void*)&multicastAll, sizeof multicastAll);
}
template<typename A, typename B>
static inline void write4Byte(A &&a, B &&b) {
memcpy(&a, &b, sizeof(a));
}
setMultiTTL:设置组播ttl
/**
* 设置组播ttl
* @param sock socket fd号
* @param ttl ttl值
* @return 0代表成功,-1为失败
*/
int SockUtil::setMultiTTL(int fd, uint8_t ttl = 64) {
int ret = -1;
#if defined(IP_MULTICAST_TTL)
ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &ttl, sizeof(ttl));
if (ret == -1) {
TraceL << "设置 IP_MULTICAST_TTL 失败!";
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
setMultiIF:设置组播发送网卡
/**
* 设置组播发送网卡
* @param sock socket fd号
* @param local_ip 本机网卡ip
* @return 0代表成功,-1为失败
*/
int SockUtil::setMultiIF(int fd, const char *local_ip) {
int ret = -1;
#if defined(IP_MULTICAST_IF)
struct in_addr addr;
addr.s_addr = inet_addr(local_ip);
ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char *) &addr, sizeof(addr));
if (ret == -1) {
TraceL << "设置 IP_MULTICAST_IF 失败!";
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
setMultiLOOP:设置是否接收本机发出的组播包
/**
* 设置是否接收本机发出的组播包
* @param fd socket fd号
* @param acc 是否接收
* @return 0代表成功,-1为失败
*/
static int setMultiLOOP(int fd, bool acc = false);
int SockUtil::setMultiLOOP(int fd, bool accept) {
int ret = -1;
#if defined(IP_MULTICAST_LOOP)
uint8_t loop = accept;
ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop, sizeof(loop));
if (ret == -1) {
TraceL << "设置 IP_MULTICAST_LOOP 失败!";
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
joinMultiAddr/leaveMultiAddr:加入/退出组播
/**
* 加入组播
* @param fd socket fd号
* @param addr 组播地址
* @param local_ip 本机网卡ip
* @return 0代表成功,-1为失败
*/
static int joinMultiAddr(int fd, const char *addr, const char *local_ip = "0.0.0.0");
/**
* 退出组播
* @param fd socket fd号
* @param addr 组播地址
* @param local_ip 本机网卡ip
* @return 0代表成功,-1为失败
*/
static int leaveMultiAddr(int fd, const char *addr, const char *local_ip = "0.0.0.0");
int SockUtil::joinMultiAddr(int fd, const char *addr, const char *local_ip) {
int ret = -1;
#if defined(IP_ADD_MEMBERSHIP)
struct ip_mreq imr;
imr.imr_multiaddr.s_addr = inet_addr(addr);
imr.imr_interface.s_addr = inet_addr(local_ip);
ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq));
if (ret == -1) {
TraceL << "设置 IP_ADD_MEMBERSHIP 失败:" << get_uv_errmsg(true);
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
int SockUtil::leaveMultiAddr(int fd, const char *addr, const char *local_ip) {
int ret = -1;
#if defined(IP_DROP_MEMBERSHIP)
struct ip_mreq imr;
imr.imr_multiaddr.s_addr = inet_addr(addr);
imr.imr_interface.s_addr = inet_addr(local_ip);
ret = setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq));
if (ret == -1) {
TraceL << "设置 IP_DROP_MEMBERSHIP 失败:" << get_uv_errmsg(true);
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
joinMultiAddrFilter/leaveMultiAddrFilter:加入/退出组播
/**
* 加入组播并只接受该源端的组播数据
* @param sock socket fd号
* @param addr 组播地址
* @param src_ip 数据源端地址
* @param local_ip 本机网卡ip
* @return 0代表成功,-1为失败
*/
static int joinMultiAddrFilter(int sock, const char *addr, const char *src_ip, const char *local_ip = "0.0.0.0");
/**
* 退出组播
* @param fd socket fd号
* @param addr 组播地址
* @param src_ip 数据源端地址
* @param local_ip 本机网卡ip
* @return 0代表成功,-1为失败
*/
static int leaveMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip = "0.0.0.0");
int SockUtil::joinMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip) {
int ret = -1;
#if defined(IP_ADD_SOURCE_MEMBERSHIP)
struct ip_mreq_source imr;
write4Byte(imr.imr_multiaddr, inet_addr(addr));
write4Byte(imr.imr_sourceaddr, inet_addr(src_ip));
write4Byte(imr.imr_interface, inet_addr(local_ip));
ret = setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq_source));
if (ret == -1) {
TraceL << "设置 IP_ADD_SOURCE_MEMBERSHIP 失败:" << get_uv_errmsg(true);
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
int SockUtil::leaveMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip) {
int ret = -1;
#if defined(IP_DROP_SOURCE_MEMBERSHIP)
struct ip_mreq_source imr;
write4Byte(imr.imr_multiaddr, inet_addr(addr));
write4Byte(imr.imr_sourceaddr, inet_addr(src_ip));
write4Byte(imr.imr_interface, inet_addr(local_ip));
ret = setsockopt(fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq_source));
if (ret == -1) {
TraceL << "设置 IP_DROP_SOURCE_MEMBERSHIP 失败:" << get_uv_errmsg(true);
}
#endif
clearMulticastAllSocketOption(fd);
return ret;
}
网卡相关
template<typename FUN>
void for_each_netAdapter_posix(FUN &&fun){ //type: struct ifreq *
struct ifconf ifconf;
char buf[1024 * 10];
//初始化ifconf
ifconf.ifc_len = sizeof(buf);
ifconf.ifc_buf = buf;
int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
WarnL << "创建套接字失败:" << get_uv_errmsg(true);
return;
}
if (-1 == ioctl(sockfd, SIOCGIFCONF, &ifconf)) { //获取所有接口信息
WarnL << "ioctl 失败:" << get_uv_errmsg(true);
close(sockfd);
return;
}
close(sockfd);
//接下来一个一个的获取IP地址
struct ifreq * adapter = (struct ifreq*) buf;
for (int i = (ifconf.ifc_len / sizeof(struct ifreq)); i > 0; --i,++adapter) {
if(fun(adapter)){
break;
}
}
}
getInterfaceList:获取本机网卡列表
vector<map<string, string> > SockUtil::getInterfaceList() {
vector<map<string, string> > ret;
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;
});
return ret;
}
get_local_ip:获取本机默认网卡ip
bool check_ip(string &address, const string &ip) {
if (ip != "127.0.0.1" && ip != "0.0.0.0") {
/*获取一个有效IP*/
address = ip;
uint32_t addressInNetworkOrder = htonl(inet_addr(ip.data()));
if (/*(addressInNetworkOrder >= 0x0A000000 && addressInNetworkOrder < 0x0E000000) ||*/
(addressInNetworkOrder >= 0xAC100000 && addressInNetworkOrder < 0xAC200000) ||
(addressInNetworkOrder >= 0xC0A80000 && addressInNetworkOrder < 0xC0A90000)) {
//A类私有IP地址:
//10.0.0.0~10.255.255.255
//B类私有IP地址:
//172.16.0.0~172.31.255.255
//C类私有IP地址:
//192.168.0.0~192.168.255.255
//如果是私有地址 说明在nat内部
/* 优先采用局域网地址,该地址很可能是wifi地址
* 一般来说,无线路由器分配的地址段是BC类私有ip地址
* 而A类地址多用于蜂窝移动网络
*/
return true;
}
}
return false;
}
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;
}
get_ifr_ip:获取网卡IP
/**
* 获取网卡ip
* @param if_name 网卡名
*/
string SockUtil::get_ifr_ip(const char *if_name) {
string ret;
for_each_netAdapter_posix([&](struct ifreq *adapter){
if(strcmp(adapter->ifr_name,if_name) == 0) {
ret = SockUtil::inet_ntoa(&(adapter->ifr_addr));
return true;
}
return false;
});
return ret;
}
get_ifr_ip:获获取网卡名
/**
* 获取网卡名
* @param local_op 网卡ip
*/
string SockUtil::get_ifr_name(const char *local_ip) {
string ret = "en0";
for_each_netAdapter_posix([&](struct ifreq *adapter){
string ip = SockUtil::inet_ntoa(&(adapter->ifr_addr));
if(ip == local_ip) {
ret = adapter->ifr_name;
return true;
}
return false;
});
return ret;
}
get_ifr_mask:根据网卡名获取子网掩码
/**
* 根据网卡名获取子网掩码
* @param if_name 网卡名
*/
string SockUtil::get_ifr_mask(const char *if_name) {
int fd;
struct ifreq ifr_mask;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
WarnL << "创建套接字失败:" << get_uv_errmsg(true);
return "";
}
memset(&ifr_mask, 0, sizeof(ifr_mask));
strncpy(ifr_mask.ifr_name, if_name, sizeof(ifr_mask.ifr_name) - 1);
if ((ioctl(fd, SIOCGIFNETMASK, &ifr_mask)) < 0) {
WarnL << "ioctl 失败:" << if_name << " " << get_uv_errmsg(true);
close(fd);
return "";
}
close(fd);
return SockUtil::inet_ntoa(&(ifr_mask.ifr_netmask));
}
get_ifr_brdaddr:根据网卡名获取广播地址
/**
* 根据网卡名获取广播地址
* @param if_name 网卡名
*/
string SockUtil::get_ifr_brdaddr(const char *if_name) {
int fd;
struct ifreq ifr_mask;
fd = socket( AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
WarnL << "创建套接字失败:" << get_uv_errmsg(true);
return "";
}
memset(&ifr_mask, 0, sizeof(ifr_mask));
strncpy(ifr_mask.ifr_name, if_name, sizeof(ifr_mask.ifr_name) - 1);
if ((ioctl(fd, SIOCGIFBRDADDR, &ifr_mask)) < 0) {
WarnL << "ioctl 失败:" << get_uv_errmsg(true);
close(fd);
return "";
}
close(fd);
return SockUtil::inet_ntoa(&(ifr_mask.ifr_broadaddr));
}