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的各种操作
常见的操作争取记住,记不住也要有印象