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;
}
}