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;
    }
}
posted @ 2022-03-24 08:45  明明1109  阅读(869)  评论(4编辑  收藏  举报