muduo笔记 网络库(六)Socket类及SocketsOps库函数封装
Socket类
Socket类是socket 文件描述符(sock fd)的一个轻量级封装,提供操作底层sock fd的常用方法。采用RTII方式管理sock fd,但本身并不创建sock fd,也不打开它,只负责关闭。
提供的public方法主要包括:获取tcp协议栈信息(tcp_info);绑定ip地址(bind);监听套接字(listen);接收连接请求(accept);关闭连接写方向(shutdown),等等。
值得注意的是,Socket并不提供close sock fd的public方法,因为析构时,调用close关闭套接字。
/**
* socket fd 包装类
* RAII机制管理socket fd: 对close socket fd负责, 但不包括open/create.
* 监听socket, 常用于server socket.
*/
class Socket : noncopyable
{
public:
explicit Socket(int sockfd)
: sockfd_(sockfd)
{ }
// Socket(Socket&&) // move ctor in c++11
~Socket();
int fd() const { return sockfd_; }
/* 获取tcp信息, 存放到tcp_info结构 */
// return true if success.
bool getTcpInfo(struct tcp_info*) const;
/* 获取tcp信息字符串形式(NUL结尾), 存放到字符串数组buf[len] */
bool getTcpInfoString(char* buf, int len) const;
/* 绑定socket fd与本地ip地址,端口, 核心调用bind(2).
* 失败则终止程序
*/
// abort if address in use
void bindAddress(const InetAddress& addr);
/* 监听socket fd, 核心调用listen(2).
* 失败则终止程序
*/
// abort if address in use
void listen();
/**
* 接受连接请求, 返回conn fd(连接文件描述符). 核心调用accept(2).
*/
/**
* On success, returns a non-negative integer that is
* a descriptor for the accepted socket, which has been
* set to non-blocking and close-on-exec. *peeraddr is assigned.
* On error, -1 is returned, and *peeraddr is untouched.
*/
int accept(InetAddress* peeraddr);
/**
* 关闭连接写方向
*/
void shutdownWrite();
/**
* Enable/disable TCP_NODELAY
*/
void setTcpNoDelay(bool on);
/**
* Enable/disable SO_REUSEADDR
*/
void setReuseAddr(bool on);
/**
* Enable/disable SO_REUSEPORT
*/
void setReusePort(bool on);
/**
* Enable/disable SO_KEEPALIVE
* @param on
*/
void setKeepAlive(bool on);
private:
const int sockfd_;
};
其实现主要转交给SocketsOps包装库函数后的包装函数。
Socket的构造与析构
Socket构造和析构很简单:
explicit Socket(int sockfd)
: sockfd_(sockfd)
{ }
Socket::~Socket()
{
sockets::close(sockfd_);
}
Socket类不创建sockfd,其含义取决于构造Socket对象的调用者。如果是由调用socket(2)创建的sockfd,那就是本地套接字;如果是由accept(2)返回的sockfd,那就是accepted socket,代表一个连接。
例如,Acceptor持有的Socket对象,是由socket(2)创建的,代表一个套接字;
TcpConnection只有一个Socket对象,是由TcpServer在新建TcpConnection对象时传入,而由Acceptor::handleRead()中通过Socket::accept()创建的sockfd参数的实参。
Socket获取Tcp协议栈信息
利用getsockopt + TCP_INFO选项,获取tcp协议栈信息。
方法一:getTcpInfo 获取tcp协议栈信息,存放到tcp_info结构对象;
方法二:getTcpInfoString 获取tcp协议栈字符串形式,存放到数组buf[len];
/**
* 获取TcpInfo(Tcp信息), 存放到tcp_info结构对象tcpi中.
* @details 调用getsockopt获取TcpInfo, 对应opt name为TCP_INFO.
* @param tcpi [in] 指向tcp_info结构, 用来存放TcpInfo
* @return 获取TcpInfo结果
* - true 获取成功
* - false 获取失败
*/
bool Socket::getTcpInfo(struct tcp_info *tcpi) const
{
socklen_t len = sizeof(*tcpi);
memZero(tcpi, len);
return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}
/**
* 将从getTcpInfo得到的TcpInfo转换为字符串, 存放到长度为len的buf中.
* @param buf 存放TcpInfo字符串的缓存
* @param len 缓存buf的长度, 单位是字节数
* @return 调用结果
* - true 返回存放到buf的TcpInfo有效
* - false 调用getsockopt()出错
* @see
* https://blog.csdn.net/zhangskd/article/details/8561950
*/
bool Socket::getTcpInfoString(char* buf, int len) const
{
struct tcp_info tcpi;
bool ok = getTcpInfo(&tcpi);
if (ok)
{
snprintf(buf, static_cast<size_t>(len), "unrecovered=%u "
"rto=%u ato=%u snd_mss=%u rcv_mss=%u "
"lost=%u retrans=%u rtt=%u rttvar=%u "
"sshthresh=%u cwnd=%u total_retrans=%u",
tcpi.tcpi_retransmits, // 重传数, 当前待重传的包数, 重传完成后清零 // Number of unrecovered [RTO] timeouts
tcpi.tcpi_rto, // 重传超时时间(usec) // Retransmit timeout in usec
tcpi.tcpi_ato, // 延时确认的估值(usec) // Predicted tick of soft clock in usec
tcpi.tcpi_snd_mss, // 发送端MSS(最大报文段长度)
tcpi.tcpi_rcv_mss, // 接收端MSS(最大报文段长度)
tcpi.tcpi_lost, // 丢失且未恢复的数据段数 // Lost packets
tcpi.tcpi_retrans, // 重传且未确认的数据段数 // Retransmitted packets out
tcpi.tcpi_rtt, // 平滑的RTT(usec) // Smoothed round trip time in usec
tcpi.tcpi_rttvar, // 1/4 mdev (usec) // Medium deviation
tcpi.tcpi_snd_ssthresh, // 慢启动阈值
tcpi.tcpi_snd_cwnd, // 拥塞窗口
tcpi.tcpi_total_retrans // 本连接的总重传个数 // Total retransmits for entire connection
);
}
return ok;
}
其他常用接口
比如bindAddress、listen、accept等,与基础网络编程接口bind、listen、accept类似,不过由SocketOps进行轻度包裹。
值得一提的是setTcpNoDelay(),用于设置TCP_NODELAY选项,以禁用Nagle算法,从而不会等到收到ACK才进行下一次数据发送,而是tcp协议栈缓冲存中有数据就立即发送。
InetAddress类
InetAddress类对地址信息进行了包装,是sockaddr_in的包装类。
既然是表示ip地址,可以直接用sockaddr_in,为什么要用InetAddress重新包装一下?
因为表示IPv4地址的sockaddr_in是C语言数据类型,并不包含对数据类型的操作,另外,支持IPv6的地址结构是sockaddr_in6。
如果直接使用C风格的sockaddr_in,那么其他类要用来表示地址,不得不使用大量底层C接口。而使用C++ 类InetAddress包装sockaddr_in/sockaddr_in6,提供必要的C++接口,可以有效解决参数兼容问题。
InetAddress 声明如下:
/**
* sockaddr_in包装类.
*
* POD(Plain Old Data) 接口类, 便于C++和C之间数据类型的兼容性.
*/
class InetAddress : public muduo::copyable
{
public:
/**
* 用给定端口号构造一个端(ip + port).
* 常用于TcpServer, 监听本地地址.
*/
explicit InetAddress(uint16_t portArg = 0, bool loopbackOnly = false, bool ipv6 = false);
/**
* 构建一个InetAddress对象, 用于将一个port + 字符串形式的ip地址转化为InetAddress对象.
*/
InetAddress(StringArg ip, uint16_t portArg, bool ipv6 = false);
explicit InetAddress(const struct sockaddr_in& addr)
: addr_(addr)
{ }
explicit InetAddress(const struct sockaddr_in6& addr)
: addr6_(addr)
{ }
/* 获取协议族类型 */
sa_family_t family() const { return addr_.sin_family; }
/* 获取ip地址字符串形式.
* 对于ipv4, 为点分十进制; 对于ipv6, 为冒号十六进制. */
string toIp() const;
/* 获取ip地址+port字符串形式. */
string toIpPort() const;
/* 获取端口号(主机端) */
uint16_t port() const;
// default copy/assignment are Okay
const struct sockaddr* getSockAddr() const { return sockets::sockaddr_cast(&addr6_); }
void setSockAddrInet6(const struct sockaddr_in6& addr6) { addr6_ = addr6; }
/* 获取ipv4地址网络端(大端) */
uint32_t ipv4NetEndian() const;
/* 获取端口号网络端(大端) */
uint16_t portNetEndian() const { return addr_.sin_port; }
/**
* 将hostname转换为IP地址, 存放到result, 而不改变port或sin_family.
* 线程安全.
*/
static bool resolve(StringArg hostname, InetAddress* result);
/* 设置IPv6 ScopeID(域ID) */
void setScopedId(uint32_t scope_id);
private:
union
{
struct sockaddr_in addr_;
struct sockaddr_in6 addr6_;
};
};
InetAddress是值语义的,便于在传递时拷贝。数据成员是一个union,对于IPv4,使用addr_;对于IPv6,则使用addr6_。
5个静态断言(static_assert)确保数据成员大小及联合体内部位段偏移,因为后面会直接将sockaddr_in6转换为sockaddr_in。
InetAddress构造
static const in_addr_t kInaddrAny = INADDR_ANY;
static const in_addr_t kInaddrLoopback = INADDR_LOOPBACK;
/**
* 构造InetAddress对象
* @param portArg 端口号
* @param loopbackOnly 决定是否为回环地址
* @param ipv6 决定是否为ipv6地址
* @note 注意addr_/addr6_中存放的是网络字节序
*/
InetAddress::InetAddress(uint16_t portArg, bool loopbackOnly, bool ipv6)
{
// 确保addr6_/addr_ 在InetAddress class内存中的偏移
static_assert(offsetof(InetAddress, addr6_) == 0, "addr6_ offset 0");
static_assert(offsetof(InetAddress, addr_) == 0, "addr_ offset 0");
if (ipv6)
{ // ipv6地址
memZero(&addr6_, sizeof(addr6_));
addr6_.sin6_family = AF_INET6;
// in6addr_loopback: 回环地址; in6addr_any: 任意地址
in6_addr ip = loopbackOnly ? in6addr_loopback : in6addr_any;
addr6_.sin6_addr = ip; // ip地址
addr6_.sin6_port = sockets::hostToNetwork16(portArg); // 端口号
}
else
{ // ipv4地址
memZero(&addr_, sizeof(addr_));
addr_.sin_family = AF_INET;
// kInaddrLoopback: 回环地址; kInaddrAny: 任意地址
in_addr_t ip = loopbackOnly ? kInaddrLoopback : kInaddrAny;
addr_.sin_addr.s_addr = sockets::hostToNetwork32(ip); // ip地址
addr_.sin_port = sockets::hostToNetwork16(portArg); // 端口号
}
}
/*
* 根据ip地址字符串形式 + 端口号, 构造InetAddress对象
* IPv6: 2409:8a4c:662f:2900:9b2:63:9618:56c0
* IPv4: 127.0.0.1
*/
InetAddress::InetAddress(StringArg ip, uint16_t portArg, bool ipv6)
{
if (ipv6 || strchr(ip.c_str(), ':'))
{ // 指定为ipv6地址类型, 或ip地址字符串中包含':'
memZero(&addr6_, sizeof(addr6_));
sockets::fromIpPort(ip.c_str(), portArg, &addr6_); // 将冒号16进制表示的ip地址+port, 转化为addr6_结构
}
else
{
memZero(&addr_, sizeof(addr_));
sockets::fromIpPort(ip.c_str(), portArg, &addr_); // 将冒号16进制表示的ip地址+port, 转化为addr6_结构
}
}
/* 根据sockaddr_in构造InetAddress对象 */
explicit InetAddress(const struct sockaddr_in& addr)
: addr_(addr)
{ }
/* 根据sockaddr_in6构造InetAddress对象 */
explicit InetAddress(const struct sockaddr_in6& addr)
: addr6_(addr)
{ }
将IP地址信息转换为字符串
将IP地址、端口号转换为字符串形式,这种打印log、debug的时候,是需要常用的方法,可以调用toIp(), toIpPort()。
/**
* 当对象ip地址转换为字符串, 如果是IPv4, 就转换为点分十进制; 如果是IPv6, 就转换为冒号十六进制.
*/
string InetAddress::toIp() const
{
char buf[64] = "";
sockets::toIp(buf, sizeof(buf), getSockAddr());
return buf;
}
/**
* 当对象ip地址&port信息 转换为字符串, 如果是IPv4, 就转换为点分十进制; 如果是IPv6, 就转换为冒号十六进制.
*/
string InetAddress::toIpPort() const
{
char buf[64] = "";
sockets::toIpPort(buf, sizeof(buf), getSockAddr());
return buf;
}
将主机名或IPv4地址转换为InetAddress结构对象
可以用InetAddress::resolve
/* 转换用的临时缓存. 因为占用空间比较大, 不用函数栈; 又要确保线程安全, 有可能经常调用, 因此用thread local 变量 */
static __thread char t_resolveBuffer[64 * 1024]; // 64KB = 64*1024
/**
* hostname转换为InetAddress
* @param hostname 主机名, 可以是本地主机名, 如"localhost"; 也可以是远程域名, 如"google.com", "192.168.0.10",
* @param out[out] 指向一个InetAddress对象, 存放地址信息
* @return 转换结果. true: 表示转换成功; false: 表示转换失败.
*/
bool InetAddress::resolve(StringArg hostname, InetAddress *out)
{
assert(out != NULL);
struct hostent hent;
struct hostent* he = NULL;
int herrno = 0;
memZero(&hent, sizeof(hent));
/* 将主机名或IPv4地址(点分十进制)转换为hostent结构 */
int ret = gethostbyname_r(hostname.c_str(), &hent, t_resolveBuffer, sizeof(t_resolveBuffer),
&he, &herrno);
if (ret == 0 && he != NULL)
{
assert(he->h_addrtype == AF_INET && he->h_length == sizeof(uint32_t)); // AF_INET: ipv4
out->addr_.sin_addr = *reinterpret_cast<struct in_addr*>(he->h_addr);
return true;
}
else
{ // error, gai_strerror(3) 能获取错误字符串信息
if (ret)
{
LOG_SYSERR << "InetAddress::resolve";
}
}
return false;
}
SocketsOps模块
SocketsOps准确来说是一个模块,而不是一个class,在sockets命名空间封装了系统底层提供的socket操作,比如socket(), bind(), listen(), accept(), connect(), close(), read(), readv(), write(), close(), shutdown()等等。
包裹函数的主要意义是为函数提供基本的出错处理,避免每次调用都要重写一次异常处理,使之更容易融入程序的框架。
我把包裹的函数分为4类:
1)基础的sock fd操作
2)便于交互的转换操作
3)地址类型转型
4)协议栈信息
// 基础的sock fd操作
/* 包裹socket(2), 创建非阻塞sockfd. 失败终止程序(LOG_SYSFATAL) */
int createNonblockingOrDie(sa_family_t family);
/* 包裹connect(2), 连接指定对端地址(addr) */
int connect(int sockfd, const struct sockaddr* addr);
/* 包裹bind(2), 绑定本地sockfd与本地ip地址addr. 失败终止程序(LOG_SYSFATAL) */
void bindOrDie(int sockfd, const struct sockaddr* addr);
/* 包裹listen(2), 监听本地sockfd. 失败终止程序(LOG_SYSFATAL) */
void listenOrDie(int sockfd);
/* 包裹accept(2)/accept4(2), 接受客户端请求连接, 返回连接sockfd */
int accept(int sockfd, struct sockaddr_in6* addr);
/* 包裹read(2), 从sockfd读取数据, 存放到数组buf[count] */
ssize_t read(int sockfd, void* buf, size_t count);
/* 包裹readv(2), 从sockfd读取数据, 存放到不连续内存iov[iovcnt]中 */
ssize_t readv(int sockfd, const struct iovec* iov, int iovcnt);
/* 包裹write(2), 将buf[count]中的数据写到sockfd连接 */
ssize_t write(int sockfd, const void* buf, size_t count);
/* 包裹close(2), 关闭sockfd */
void close(int sockfd);
/* 包裹shutdown(8), 关闭连接写方向 */
void shutdownWrite(int sockfd);
// 便于交互的转换操作
/* 将地址addr中包含的ip地址+port信息转换为C风格字符串, 存放到数组buf[size] */
void toIpPort(char* buf, size_t size, const struct sockaddr* addr);
/* 将地址addr中包含的ip地址转换为C风格字符串, 存放到数组buf[size] */
void toIp(char* buf, size_t size, const struct sockaddr* addr);
/* 将参数ip, port转换为sockaddr_in结构的addr */
void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in* addr);
/* 将参数ip, port转换为sockaddr_in6结构的addr */
void fromIpPort(const char* ip, uint16_t port, struct sockaddr_in6* addr);
// 地址类型转型
/* const sockaddr_in转型为const sockaddr */
const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr);
/* const sockaddr_in6转型为const sockaddr */
const struct sockaddr* sockaddr_cast(const struct sockaddr_in6* addr);
/* sockaddr_in6转型为sockaddr */
struct sockaddr* sockaddr_cast(struct sockaddr_in6* addr);
/* const sockaddr转型为const sockaddr_in */
const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
/* const sockaddr转型为const sockaddr_in6 */
const struct sockaddr_in6* sockaddr_in6_cast(const struct sockaddr* addr);
// 协议栈信息
/* 获取tcp/ip协议栈错误 */
int getSocketError(int sockfd);
/* 获取sockfd对应的本地地址 */
struct sockaddr_in6 getLocalAddr(int sockfd);
/* 获取sockfd对应的对端地址 */
struct sockaddr_in6 getPeerAddr(int sockfd);
/* 判断sockfd是否为自连接 */
bool isSelfConnect(int sockfd);
- sockets::createNonblockingOrDie()
创建非阻塞sock fd
/**
* 创建一个非阻塞sock fd
* @param family 协议族, 可取值AF_UNIX/AF_INET/AF_INET6 etc.
* @return 成功, 返回sock fd; 失败, 程序终止(LOG_SYSFATAL)
*/
int sockets::createNonblockingOrDie(sa_family_t family)
{
#if VALGRIND // a kind of memory test tool
int sockfd = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
setNonBlockAndCloseOnExec(sockfd);
#else
int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
#endif
return sockfd;
}
- connect
请求连接服务器端addr
int sockets::connect(int sockfd, const struct sockaddr *addr)
{
return ::connect(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}
- bindOrDie
绑定sock fd与本地地址addr
void sockets::bindOrDie(int sockfd, const struct sockaddr *addr)
{
int ret = ::bind(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
if (ret < 0)
{
LOG_SYSFATAL << "sockets::bindOrDie";
}
}
- listenOrDie
监听本地sock fd。如果协议支持重传(如TCP协议),那么listen第二个参数backlog会被忽略。
void sockets::listenOrDie(int sockfd)
{
int ret = ::listen(sockfd, SOMAXCONN);
if (ret < 0)
{
LOG_SYSFATAL << "sockets::listenOrDie";
}
}
- accept
接受连接请求。
被包裹函数accept或accep4,其区别为:accept4一次调用能同时指定SOCK_NONBLOCK和SOCK_CLOEXEC选项;如果要用accept,则还需要额外调用setNonBlockAndCloseOnExec(),来设置sock fd的non-block、close-on-exec属性。
accept调用出错时,跟log记录错误号。
/**
* accept(2)/accept4(2)包裹函数, 接受连接并获取对端ip地址
* @param sockfd 服务器sock fd, 指向本地监听的套接字资源
* @param addr ip地址信息
* @return 由sockfd接收连接请求得到到连接fd
*/
int sockets::accept(int sockfd, struct sockaddr_in6 *addr)
{
socklen_t addrlen = static_cast<socklen_t>(sizeof(*addr));
#if VALGRIND || defined(NO_ACCEPT4) // VALGRIND: memory check tool
int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen);
setNonBlockAndCloseOnExec(connfd);
#else
// set flags for conn fd returned by accept() at one time
int connfd = ::accept4(sockfd, sockaddr_cast(addr),
&addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#endif
if (connfd < 0)
{
int savedErrno = errno;
LOG_SYSERR << "Socket::accept";
switch (savedErrno)
{
case EAGAIN:
case ECONNABORTED:
case EINTR:
case EPROTO:
case EMFILE:
// expected errors
errno = savedErrno;
break;
case EBADF:
case EFAULT:
case EINVAL:
case ENFILE:
case ENOBUFS:
case ENOMEM:
case ENOTSOCK:
case EOPNOTSUPP:
// unexpected errors
LOG_FATAL << "unexpected error of ::accept " << savedErrno;
default:
LOG_FATAL << "unknown error of ::accept " << savedErrno;
break;
}
}
return connfd;
}
- read、readv
read直接转发给read(2),没有特殊处理;
readv直接转发给readv(2),没有特殊处理。
ssize_t sockets::read(int sockfd, void *buf, size_t count)
{
return ::read(sockfd, buf, count);
}
ssize_t sockets::readv(int sockfd, const struct iovec *iov, int iovcnt)
{
return ::readv(sockfd, iov, iovcnt);
}
- write
write直接转发给write(2),没有特殊处理;
ssize_t sockets::write(int sockfd, const void *buf, size_t count)
{
return ::write(sockfd, buf, count);
}
为何有readv,而没有writev?
推测是因为read的时候,可能希望尽量读更多的数据,但由于Buffer大小限制,增加了额外的空间,就用readv来读取;
write的时候,如果要write数据过多,可以保存进度,然后在回调中继续write。
- close
关闭sockfd。
void sockets::close(int sockfd)
{
if (::close(sockfd) <0)
{
LOG_SYSERR << "sockets::close";
}
}
- shutdownWrite
shutdownWrite关闭连接写方向:shutdown(2) + SHUT_WR
void sockets::shutdownWrite(int sockfd)
{
if (::shutdown(sockfd, SHUT_WR) < 0)
{
LOG_SYSERR << "sockets::shutdownWrite";
}
}
- toIpPort, toIp
将ip地址、port信息由sockaddr对象,转换为字符串。核心调用inet_ntop(2), 将IPv4、IPv6地址由二进制转化为文本。
利用snprintf,将ip地址和port文本信息组装到一起。
/**
* convert struct sockaddr containing ip info to ip string pointed by buf
* @param buf [out] point to ip string buffer
* @param size size of buf (bytes)
* @param addr [in] point to struct sockaddr containing ip address and port info
* @note port of struct sockaddr is network byte order, but local operation needs
* host byte order.
*/
void sockets::toIpPort(char *buf, size_t size, const struct sockaddr *addr)
{
if (addr->sa_family == AF_INET6)
{ // IPv6
buf[0] = '[';
toIp(buf + 1, size - 1, addr);
size_t end = ::strlen(buf);
const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
uint16_t port = sockets::networkToHost16(addr6->sin6_port);
assert(size > end);
snprintf(buf + end, size - end, "]:%u", port);
return;
}
// IPv4
toIp(buf, size, addr);
size_t end = ::strlen(buf);
const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
uint16_t port = sockets::networkToHost16(addr4->sin_port);
assert(size > end);
snprintf(buf + end, size - end, ":%u", port);
}
/**
* convert IP address info from struct sockaddr to string buffer
* @param buf [out] string buffer with NUL-byte
* @param size length of string buffer
* @param addr [in] point to struct sockaddr, which contains ip, port info
*/
void sockets::toIp(char* buf, size_t size,
const struct sockaddr* addr)
{
if (addr->sa_family == AF_INET)
{
assert(size >= INET_ADDRSTRLEN);
const struct sockaddr_in* addr4 = sockaddr_in_cast(addr);
::inet_ntop(AF_INET, &addr4->sin_addr, buf, static_cast<socklen_t>(size));
}
else if (addr->sa_family == AF_INET6)
{
assert(size >= INET6_ADDRSTRLEN);
const struct sockaddr_in6* addr6 = sockaddr_in6_cast(addr);
::inet_ntop(AF_INET6, &addr6->sin6_addr, buf, static_cast<socklen_t>(size));
}
}
- fromIPPort
将ip地址、端口号文本转换为二进制(sockaddr_in/sockaddr_in6),sockaddr_in适用于IPv4,sockaddr_in6适用于IPv6。
2个重载函数是toIpPort()的逆过程。
/**
* convert ipv4 string to struct sockaddr_in
* @param ip ipv4 address string with format like "127.0.0.1"
* @param port local port for TCP/UDP
* @param addr [out] store ipv4 info
*/
void sockets::fromIpPort(const char *ip, uint16_t port, struct sockaddr_in *addr)
{
addr->sin_family = AF_INET;
addr->sin_port = hostToNetwork16(port);
if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
{
LOG_SYSERR << "sockets::fromIpPort";
}
}
/**
* convert ipv6 string to struct socket_in6
* @param ip ipv6 address string with format like "2409:8a4c:662f:2900:b42c:a0d9:fe5:2037"
* @param port local port for TCP/UDP
* @param addr [out] store ipv6 info
*/
void sockets::fromIpPort(const char *ip, uint16_t port, struct sockaddr_in6 *addr)
{
addr->sin6_family = AF_INET6;
addr->sin6_port = hostToNetwork16(port);
if (::inet_pton(AF_INET6, ip, &addr->sin6_addr) <= 0)
{
LOG_SYSERR << "sockets::fromIpPort";
}
}
- 地址转型
提供不同地址类型之间的转型,如sockaddr_in/sockaddr_in6/sockaddr*,要求成员内存布局必须是一样的。这也是为什么前面用static_assert来断言sockaddr_in/sockaddr_in6成员偏移的原因(offsetof),因为如果成员偏移不一样,也就是说对象的内存布局不一样,通过指针直接转型是不对的。
为什么用static_cast对指针进行转型,而不用reinterpret_cast?
单独的static_cast,是无法将一种指针类型转换为另一种指针类型的,需要先利用implicit_cast(隐式转型)/static_cast(显式转型)将指针类型转换为void/const void (无类型)指针,然后才能转换为模板类型指针。
而reinterpret_cast可以直接做到,但reinterpret_cast通常并不安全,编译期也不会在编译期报错,通常不推荐使用。
// const sockaddr_in6* => const sockaddr*
const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in6* addr)
{
// reinterpret_cast<const struct sockaddr*>();
return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}
// sockaddr_in6* => sockaddr*
struct sockaddr* sockets::sockaddr_cast(struct sockaddr_in6* addr)
{
return static_cast<struct sockaddr*>(implicit_cast<void*>(addr));
}
// const sockaddr_in* => const sockaddr*
const struct sockaddr* sockets::sockaddr_cast(const struct sockaddr_in* addr)
{
return static_cast<const struct sockaddr*>(implicit_cast<const void*>(addr));
}
// const sockaddr* => const sockaddr_in*
const struct sockaddr_in* sockets::sockaddr_in_cast(const struct sockaddr* addr)
{
return static_cast<const struct sockaddr_in*>(implicit_cast<const void*>(addr));
}
// const sockaddr* => const sockaddr_in6*
const struct sockaddr_in6* sockets::sockaddr_in6_cast(const struct sockaddr *addr)
{
return static_cast<const struct sockaddr_in6*>(implicit_cast<const void*>(addr));
}
- getSocketError
获取tcp协议栈错误。利用getsockopt + SO_ERROR选项,获取tcp协议栈内部错误。
通常,在处理连接的读写事件时调用,检查是否发生错误。
int sockets::getSocketError(int sockfd)
{
int optval;
socklen_t optlen = static_cast<socklen_t>(sizeof(optval));
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen))
{
return errno;
}
else
{
return optval;
}
}
- getLocalAddr
从连接获取本地ip地址(包括端口号)。不论IPv4,还是IPv6,统一存放到sockaddr_in6结构对象中,因为该对象长度最长。
核心调用getsockname(2)。
/**
* Get a local ip address from an opened sock fd
* @param sockfd an opened sockfd
* @return local ip address info
*/
struct sockaddr_in6 sockets::getLocalAddr(int sockfd)
{
struct sockaddr_in6 localaddr;
memZero(&localaddr, sizeof(localaddr));
socklen_t addrlen = static_cast<socklen_t>(sizeof(localaddr));
// get local ip addr info bound to sockfd
if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0)
{
LOG_SYSERR << "sockets::getLocalAddr";
}
return localaddr;
}
- getPeerAddr
获取连接对端的ip地址(包括端口号)。类似于getLocalAddr,地址信息都存放到sockaddr_in6结构对象中。
核心调用getpeername(2)。
/**
* Get a peer ip address from an opened sock fd
* @param sockfd an opened sockfd
* @return peer ip address info
*/
struct sockaddr_in6 sockets::getPeerAddr(int sockfd)
{
struct sockaddr_in6 peeraddr;
memZero(&peeraddr, sizeof(peeraddr));
socklen_t addrlen = static_cast<socklen_t>(sizeof(peeraddr));
if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen))
{
LOG_SYSERR << "sockets::getPeerAddr";
}
return peeraddr;
}
- isSelfConnect
检查是否为自连接。利用了getLocalAddr()和getPeerAddr(),检查ip地址是否相同,来判断连接对端地址信息是否为本机。
同样分IPv4和IPv6两种情况,依据是sockaddr_in6的sin6_family成员。
注意:对于IPv4,地址sin_addr.s_addr是32bit,能用“”判断是否相等;而对于IPv6,地址sin6_addr是28byte,无法用“”判断,需要用memcmp来比较二进制位。
/**
* 检查是否为自连接, 判断连接sockfd两端ip地址信息是否相同.
* @param sockfd 连接对应的文件描述符
* @return true: 是自连接; false: 不是自连接
*/
bool sockets::isSelfConnect(int sockfd)
{
struct sockaddr_in6 localaddr = getLocalAddr(sockfd);
struct sockaddr_in6 peeraddr = getPeerAddr(sockfd);
if (localaddr.sin6_family == AF_INET)
{ // IPv4
const struct sockaddr_in* laddr4 = reinterpret_cast<struct sockaddr_in*>(&localaddr);
const struct sockaddr_in* raddr4 = reinterpret_cast<struct sockaddr_in*>(&peeraddr);
return laddr4->sin_port == raddr4->sin_port
&& laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr;
}
else if (localaddr.sin6_family == AF_INET6)
{ // IPv6
return localaddr.sin6_port == peeraddr.sin6_port
&& memcmp(&localaddr.sin6_addr, &peeraddr.sin6_addr, sizeof(localaddr.sin6_addr)) != 0;
}
else
{
return false;
}
}