muduo源碼學(xué)習(xí)(二) 實(shí)現(xiàn)TCP網(wǎng)絡(luò)庫(上)

概 述

muduo在實(shí)現(xiàn)非阻塞TCP連接時(shí),對socket相關(guān)的內(nèi)容進(jìn)行了非常詳盡的封裝,本文梳理下muduo中Acceptor InetAddress Socket SocketsOps四個(gè)類的相關(guān)實(shí)現(xiàn)。
由于muduo源碼的封裝比較復(fù)雜,本人在其基礎(chǔ)上進(jìn)行了簡化,保留其中核心的代碼供于學(xué)習(xí),因此示例代碼非muduo源碼。

實(shí) 現(xiàn)

1. InetAddress
InetAddress是對網(wǎng)絡(luò)地址的相關(guān)封裝,包括初始化網(wǎng)絡(luò)地址結(jié)構(gòu),設(shè)置/獲取網(wǎng)絡(luò)地址數(shù)據(jù)等等。源碼中區(qū)分對IVP6和IVP4的實(shí)現(xiàn),以及對IP地址封裝了StringArg類。
這里只保留對IVP4的實(shí)現(xiàn),IP地址使用字符串代替,簡化代碼如下:

//InetAddress.h
#include <netinet/in.h> //uint16_t sockaddr_in
#include <string.h> //memset()
#include <string>

class InetAddress {
public:
    explicit InetAddress(uint16_t port = 0, bool ifLoopback = false); //通過端口號構(gòu)造
    InetAddress(const std::string& ip, uint16_t port); //通過IP地址 和 端口號構(gòu)造

    const struct sockaddr* getSockAddr() const; //獲取網(wǎng)絡(luò)地址數(shù)據(jù)
    void setSockAddr(struct sockaddr_in& addr); //設(shè)置網(wǎng)絡(luò)地址

private:
    sockaddr_in m_addr;
};

//InetAddress.cpp
#include <endian.h>
#include <arpa/inet.h> //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<const struct sockaddr*>(&m_addr);
}

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

說明:
源碼對htobe32()、htobe16()等字節(jié)序轉(zhuǎn)換函數(shù)也進(jìn)行了封裝,在\net\Endian.h中,這里直接使用系統(tǒng)函數(shù)。
源碼將getSockAddr()等函數(shù)的具體實(shí)現(xiàn)封裝到了SocketsOps類中,這里因?yàn)楹途W(wǎng)絡(luò)地址直接相關(guān),在本類中實(shí)現(xiàn)。

2. Socket
Socket是一個(gè)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; //設(shè)置是否地址復(fù)用
    void setPortReusable(bool on) const; //設(shè)置是否端口復(fù)用

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<socklen_t>(sizeof optVal));
}

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

Socket類只提供接口,不做任何操作,相關(guān)操作封裝在SocketsOps中。

3. SocketsOps
SocketsOps是對所有socket相關(guān)api的封裝,簡化代碼如下:

//SocketsOps.h
#include <arpa/inet.h>
#include <unistd.h>

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(); //創(chuàng)建非阻塞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) {
        //...錯(cuò)誤處理
    }
    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;
}

說明:
對于將文件描述符設(shè)置為非阻塞,我們可以使用fcntl()來設(shè)置,如下:

// 將文件描述符設(shè)置為非阻塞的
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;
}

對于現(xiàn)在的Linux(Linux 2.6.28版本以后,參考文檔Linux手冊),可以一步設(shè)置為非阻塞,如上文示例代碼,muduo源碼中分別提供了兩種實(shí)現(xiàn)方式,這里簡化只保留了更便捷的實(shí)現(xiàn)方式。

4. Acceptor
Acceptor類是對連接接收相關(guān)接口的封裝,用于accept新的TCP連接。其構(gòu)造函數(shù)與成員函數(shù)Acceptor::listen()執(zhí)行創(chuàng)建TCP服務(wù)端的傳統(tǒng)步驟,即調(diào)用socket()bind()、listen(),數(shù)據(jù)成員Channel觀察此socket上的可讀事件,并回調(diào)Acceptor::handleRead(),后者調(diào)用accept()來接收連接,并回調(diào)用戶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<void (int sockfd, const InetAddress&)> NewConnectionCallback;

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

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

private:
    void handleRead(); //可讀事件回調(diào)

private:
    EventLoop* m_loop;
    Socket m_acceptSocket;
    Channel m_acceptChannel;
    NewConnectionCallback m_newConnectionCallback; //用戶回調(diào)
};

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

更多內(nèi)容,詳見github NetLib
參考:
《Linux多線程服務(wù)端編程》陳碩 著
muduo源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容