muduo中封装fd的Socket类和分发Channel的Acceptor类

62 阅读2分钟

一、Socket类

Socket类用于封装fd、bind、listen、accept等操作

  1. Socket.h
#pragma once
#include "noncopyable.h"

class InetAddress;

//封装Socket fd
class Socket : noncopyable
{
public:
    explicit Socket(int sockfd)
        :sockfd_(sockfd)
    {}
    ~Socket();

    int fd()const { return sockfd_; }
    void bindAddress(const InetAddress &localaddr);
    void listen();
    int accept(InetAddress *peeraddr);
    
    void shutdownWrite();

    //数据直接发送,不做TCP缓冲
    void setTcpNoDelay(bool on);
    void setReuseAddr(bool on);
    void setReusePort(bool on);
    void setKeepAlive(bool on);
private:
    const int sockfd_;
};
  1. Socket.cc

shutdown函数 image.png

#include "Socket.h"
#include "Logger.h"
#include "InetAddress.h"

#include<unistd.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include<strings.h>
#include <netinet/tcp.h>
#include <netinet/in.h>

Socket::~Socket()
{
    close(sockfd_);
}
// 将服务器本地ip port绑定到listenfd
void Socket::bindAddress(const InetAddress &localaddr)
{
    if(0 != ::bind(sockfd_, (sockaddr*)localaddr.getSockAddr(),sizeof(sockaddr_in)))
    {
        LOG_FATAL("bind sockfd:%d fail \n",sockfd_);
    }
}

void Socket::listen()
{
    if(0 != ::listen(sockfd_,1024))
    {
        LOG_FATAL("listen sockfd:%d fail \n",sockfd_);
    }
}

int Socket::accept(InetAddress *peeraddr)
{
    sockaddr_in addr;
    socklen_t len;
    bzero(&addr,sizeof addr);
    int connfd = ::accept(sockfd_, (sockaddr*)&addr, &len);
    if(connfd >= 0)
    {
        peeraddr->setSockAddr(addr);
    }
    return connfd;
}
// tcp socket是全双工通信,这是关闭监听套接字sockfd_写端
void Socket::shutdownWrite()
{
    if(::shutdown(sockfd_, SHUT_WR) < 0)
    {
        LOG_ERROR("sockets::shutdownWrite");
    }
}

//数据直接发送,不做TCP缓冲
void Socket::setTcpNoDelay(bool on)
{
    int optval = on ? 1 : 0;
    // TCP_NODELAY禁用Nagle算法,TCP_NODELAY包含在 <netinet/tcp.h>
    ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);
}
void Socket::setReuseAddr(bool on)
{
    int optval = on ? 1 : 0;
    ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
}
void Socket::setReusePort(bool on)
{
    int optval = on ? 1 : 0;
    ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval);
}
void Socket::setKeepAlive(bool on)
{
    int optval = on ? 1 : 0;
    ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);
}

二、Acceptor类

image.png

Acceptor工作在mainReactor,用于监听新用户的连接,将与客户端通信的fd打包成Channel,muduo采用轮询算法找一个subloop,将其唤醒(每一个loop都有一个wakeupfd,wakeupfd是通过linux的API eventfd创建的,一个带有线程间通知机制的fd),把打包好的Channel给subloop

  1. Acceptor.h
#pragma once
#include "noncopyable.h"
#include "Socket.h"
#include "Channel.h"

#include<functional>

class EventLoop;
class InetAddress;

class Acceptor : noncopyable
{
public:
    using NewConnectionCallback = std::function<void(int sockfd, const InetAddress&)>;
    Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport);
    ~Acceptor();

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

    bool listenning()const { return listenning_; }
    void listen();
private:
    void handleRead();

    EventLoop *loop_; //Acceptor用的就是用户定义的baseLoop,也称作mainLoop
    Socket acceptSocket_;
    Channel acceptChannel_;
    NewConnectionCallback newConnectionCallback_;
    bool listenning_;
};
  • 成员变量acceptSocket_:封装了listenfd,用于监听新用户的连接
  • 成员变量acceptChannel_:封装了listenfd,以及感兴趣的事件events和发生的事件revents
  • 成员变量loop_:通过事件循环监听listenfd,也就是mainLoop
  • 成员变量newConnectionCallback_:如果一个客户端连接成功,Acceptor返回一个Channel给TcpServer,TcpServer会通过轮询唤醒一个subLoop,并把新客户端的Channel给subLoop,而这些事情都是交给一个回调函数做的,即newConnectionCallback_
  • 成员函数handleRead:服务器的listenfd发生读事件调用,即新用户连接
  1. Acceptor.cc

socket函数第二个参数type

image.png

image.png SOCK_NONBLOCK:非阻塞形式,没有事件发生立刻返回
SOCK_CLOEXEC:fork之后子进程会继承父进程所有的文件描述符,此时子进程会关闭这些继承来的文件描述符

#include "Acceptor.h"
#include "Logger.h"
#include "InetAddress.h"

#include <sys/types.h>         
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>

//创建listenfd
static int createNonblocking()
{
    int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
    if(sockfd < 0)
    {
        LOG_FATAL("%s:%s:%d listen socket create err:%d \n", __FILE__, __FUNCTION__, __LINE__,errno);
    }
    return sockfd;
}

Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
    :loop_(loop)
    ,acceptSocket_(createNonblocking()) // Socket的构造函数需要一个int参数,createNonblocking()返回值就是int
    ,acceptChannel_(loop,acceptSocket_.fd()) // 第一个参数就是Channel所属的EventLoop
    ,listenning_(false)
{
    acceptSocket_.setReuseAddr(true);
    acceptSocket_.setReusePort(true);
    acceptSocket_.bindAddress(listenAddr);// bind
    /**当我们TcpServer调用start方法时,就会启动Acceptor.listen()方法
     * 有新用户连接时,要执行一个回调,这个方法会将和用户连接的fd打包成Channel,wakeup subloop,
     * 然后交给subloop,下面就是注册包装了listenfd的Channel发生读事件后,需要执行的回调函数,
     * Acceptor只管理封装了listenfd的Channel,只需要注册读事件的回调
    **/
   //在listenfd还没有发生事件的时候,我们就给他预先注册一个事件回调,当真正listenfd有客户端连接的话,
   //底层反应堆会帮我们调用这个回调,即event对应的事件处理器
    acceptChannel_.setReadCallBack(std::bind(&Acceptor::handleRead, this));
}

Acceptor::~Acceptor()
{
    acceptChannel_.disableAll();
    acceptChannel_.remove();
}

void Acceptor::listen()
{
    listenning_ = true;  //开始监听
    acceptSocket_.listen(); // 调用底层的::listen
    //将acceptChannel_注册到poller里面,poller才能帮忙监听是否有事件发生,
    //如果有事件发生,channel就会调用事先被注册的readcallback
    //就会执行到handleRead函数,这个函数就会拿到新用户连接的fd以及客户端的ip+port
    //直接执行回调
    acceptChannel_.enableReading();
}

//listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{
    InetAddress peerAddr; //客户端地址
    int connfd = acceptSocket_.accept(&peerAddr);
    if(connfd >= 0)
    {
        if(newConnectionCallback_) // 轮询找到subloop,唤醒并分发当前新客户端connfd的Channel
        {
            newConnectionCallback_(connfd,peerAddr);
        }
        else
        {
            ::close(connfd);// 如果没有设置新用户连接的回调操作,就关闭连接
        }
    }
    else //accept出错
    {
        LOG_ERROR("%s:%s:%d accept err:%d \n", __FILE__, __FUNCTION__, __LINE__,errno);
        if(errno == EMFILE)//当前进程没有可用的fd再分配
        {
            /**出现这种错误
             * 1.调整当前文件描述符的上限
             * 2.单台服务器已经不足以支撑现有的流量,需要做集群或者分布式部署
            **/
            LOG_ERROR("%s:%s:%d sockfd reached limit\n", __FILE__, __FUNCTION__, __LINE__);
        }
    }
}