概 述
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源碼