muduo源码学习(二) 实现TCP网络库(上)

概 述

muduo在实现非阻塞TCP连接时,对socket相关的内容进行了非常详尽的封装,本文梳理下muduo中Acceptor InetAddress Socket SocketsOps四个类的相关实现。
由于muduo源码的封装比较复杂,本人在其基础上进行了简化,保留其中核心的代码供于学习,因此示例代码非muduo源码。

实 现

1. InetAddress
InetAddress是对网络地址的相关封装,包括初始化网络地址结构,设置/获取网络地址数据等等。源码中区分对IVP6和IVP4的实现,以及对IP地址封装了StringArg类。
这里只保留对IVP4的实现,IP地址使用字符串代替,简化代码如下:

//InetAddress.h
#include  //uint16_t sockaddr_in
#include  //memset()
#include 

class InetAddress {
public:
    explicit InetAddress(uint16_t port = 0, bool ifLoopback = false); //通过端口号构造
    InetAddress(const std::string& ip, uint16_t port); //通过IP地址 和 端口号构造

    const struct sockaddr* getSockAddr() const; //获取网络地址数据
    void setSockAddr(struct sockaddr_in& addr); //设置网络地址

private:
    sockaddr_in m_addr;
};

//InetAddress.cpp
#include 
#include  //inet_pton()
#include "InetAddress.h"

InetAddress::InetAddress(uint16_t port, bool ifLoopback) {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sin_family = AF_INET;
    in_addr_t ipType = ifLoopback ? INADDR_LOOPBACK : INADDR_ANY;
    m_addr.sin_addr.s_addr = htobe32(ipType);
    m_addr.sin_port = htobe16(port);
}

InetAddress::InetAddress(const std::string& ip, uint16_t port) {
    memset(&m_addr, 0, sizeof(m_addr));
    m_addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip.c_str(), &m_addr.sin_addr);
    m_addr.sin_port = htobe16(port);
}

const struct sockaddr *InetAddress::getSockAddr() const {
    return (const struct sockaddr*)&m_addr;
    // return reinterpret_cast(&m_addr);
}

void InetAddress::setSockAddr(struct sockaddr_in& addr) {
    m_addr = addr;
}

说明:
源码对htobe32()htobe16()等字节序转换函数也进行了封装,在\net\Endian.h中,这里直接使用系统函数。
源码将getSockAddr()等函数的具体实现封装到了SocketsOps类中,这里因为和网络地址直接相关,在本类中实现。

2. Socket
Socket是一个RAII handle,封装了socket文件描述符的生命周期。简化代码如下:

//Socket.h
#include "../Base/Noncopyable.h"

class InetAddress;

class Socket : Noncopyable {
public:
    explicit Socket(int fd);

    int  fd() const { return m_sockfd; }

    void bindAddress(const InetAddress& addr) const; //bind接口
    void listenAddress() const; //listen接口
    int  acceptAddress(InetAddress* peeraddr); //accept接口

    void setAddrReusable(bool on) const; //设置是否地址复用
    void setPortReusable(bool on) const; //设置是否端口复用

private:
    const int m_sockfd;
};

//Socket.cpp
Socket::Socket(int fd) :
    m_sockfd(fd)
{
}

void Socket::bindAddress(const InetAddress& addr) const {
    sockets::bind(m_sockfd, addr.getSockAddr());
}

void Socket::listenAddress() const {
    sockets::listen(m_sockfd);
}

int Socket::acceptAddress(InetAddress* peeraddr) {
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    int connfd = sockets::accept(m_sockfd, &addr);
    if (connfd >= 0) {
        peeraddr->setSockAddr(addr);
    }
    return connfd;
}

void Socket::setAddrReusable(bool on) const {
    int optVal = on ? 1 : 0;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optVal, static_cast(sizeof optVal));
}

void Socket::setPortReusable(bool on) const {
    int optval = on ? 1 : 0;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, static_cast(sizeof optval));
}

Socket类只提供接口,不做任何操作,相关操作封装在SocketsOps中。

3. SocketsOps
SocketsOps是对所有socket相关api的封装,简化代码如下:

//SocketsOps.h
#include 
#include 

namespace sockets {
    void bind(int sockfd, const struct sockaddr* addr);
    void listen(int sockfd);
    int  accept(int sockfd, struct sockaddr_in* addr);
    void close(int sockfd);
    int  createNonblockingSocket(); //创建非阻塞socket
}

//SocketsOps.cpp
#include "../Log/Log.h"
#include "SocketOps.h"

void sockets::bind(int sockfd, const struct sockaddr* addr) {
    int ret = ::bind(sockfd, addr, sizeof(struct sockaddr_in));
    if (ret < 0) {
        LOG_ERROR("Bind error!")
    }
}

void sockets::listen(int sockfd) {
    int ret = ::listen(sockfd, SOMAXCONN);
    if (ret < 0) {
        LOG_ERROR("Listen error!")
    }
}

int sockets::accept(int sockfd, struct sockaddr_in* addr) {
    socklen_t addrlen = sizeof(*addr);
    int connfd = ::accept4(sockfd, (struct sockaddr*)addr, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
    if (connfd < 0) {
        //...错误处理
    }
    return connfd;
}

void sockets::close(int sockfd) {
    int ret = ::close(sockfd);
    if (ret < 0) {
        LOG_ERROR("Close error!")
    }
}

int sockets::createNonblockingSocket() {
    int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
    if (sockfd < 0) {
        LOG_ERROR("Create Nonblocking Socket error!")
    }
    return sockfd;
}

说明:
对于将文件描述符设置为非阻塞,我们可以使用fcntl()来设置,如下:

// 将文件描述符设置为非阻塞的
int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

对于现在的Linux(Linux 2.6.28版本以后,参考文档Linux手册),可以一步设置为非阻塞,如上文示例代码,muduo源码中分别提供了两种实现方式,这里简化只保留了更便捷的实现方式。

4. Acceptor
Acceptor类是对连接接收相关接口的封装,用于accept新的TCP连接。其构造函数与成员函数Acceptor::listen()执行创建TCP服务端的传统步骤,即调用socket()bind()listen(),数据成员Channel观察此socket上的可读事件,并回调Acceptor::handleRead(),后者调用accept()来接收连接,并回调用户callback。简化代码如下:

//Acceptor.h
#include "../Base/Noncopyable.h"
#include "../Net/EventLoop.h"
#include "../Net/Channel.h"
#include "InetAddress.h"
#include "Socket.h"

class Acceptor : Noncopyable {
public:
    typedef std::function NewConnectionCallback;

    Acceptor(EventLoop* loop, const InetAddress& addr);
    ~Acceptor();

    void setNewConnectionCallback(const NewConnectionCallback& cb) { m_newConnectionCallback = cb; }
    void listen();

private:
    void handleRead(); //可读事件回调

private:
    EventLoop* m_loop;
    Socket m_acceptSocket;
    Channel m_acceptChannel;
    NewConnectionCallback m_newConnectionCallback; //用户回调
};

//Acceptor.cpp
#include "SocketOps.h"
#include "Acceptor.h"

Acceptor::Acceptor(EventLoop *loop, const InetAddress &addr) :
    m_loop(loop),
    m_acceptSocket(sockets::createNonblockingSocket()),
    m_acceptChannel(m_loop, m_acceptSocket.fd())
{
    m_acceptSocket.setAddrReusable(true);
    m_acceptSocket.bindAddress(addr);
    m_acceptChannel.setReadCallback(std::bind(&Acceptor::handleRead, this));
}

Acceptor::~Acceptor() {
    m_acceptChannel.disableAll();
    // m_acceptChannel.remove();
}

void Acceptor::listen() {
    m_loop->assertInLoopThread();
    m_acceptSocket.listenAddress();
    m_acceptChannel.enableReading();
}

void Acceptor::handleRead() {
    m_loop->assertInLoopThread();
    InetAddress peerAddr;
    int connfd = m_acceptSocket.acceptAddress(&peerAddr);
    if (connfd >= 0) {
        if (m_newConnectionCallback) m_newConnectionCallback(connfd, peerAddr);
    } else {
        sockets::close(connfd);
    }
}

更多内容,详见github NetLib
参考:
《Linux多线程服务端编程》陈硕 著
muduo源码

你可能感兴趣的:(muduo源码学习(二) 实现TCP网络库(上))