handy : TcpServer

https://github.com/yedf/handy/blob/master/handy/conn.h
https://github.com/yedf/handy/blob/master/handy/conn.cc

如果你已经理解了EventBase、Channel、Poller,那么理解TcpServer应该不成问题。因为TcpServer内部使用的就是EventBase、Channel、Poller。(与muduo类似)

在handy的echo server中是这么使用TcpServer的:

    EventBase base; // 创建EventBase
    Signal::signal(SIGINT, [&]{ base.exit(); }); // 信号处理
    TcpServerPtr svr = TcpServer::startServer(&base, "", 99); // 创建TcpSever
    exitif(svr == NULL, "start tcp server failed");
    svr->onConnRead([](const TcpConnPtr& con) { // 设置有新连接到来时的回调函数
        con->send(con->getInput()); // 将收到的数据发送回去
    });
    base.loop(); // 进入事件循环

成员变量

private:
    EventBase* base_;
    EventBases* bases_;
    Ip4Addr addr_;  // 服务器地址
    Channel* listen_channel_; // 监听channel,对应socket中的监听套接字
    TcpCallBack statecb_, readcb_;
    MsgCallBack msgcb_;
    std::function<TcpConnPtr()> createcb_; 
    std::unique_ptr<CodecBase> codec_;

构造函数/析构函数

TcpServer::TcpServer(EventBases* bases):
    base_(bases->allocBase()),
    bases_(bases),
    listen_channel_(NULL),
    createcb_([]{ return TcpConnPtr(new TcpConn); })
{
}

TcpServer::~TcpServer() { delete listen_channel_; }

TcpConn代表一个新连接。

TcpServer::startServer

该函数是一个static function,负责创建TcpServer,并返回一个shared_ptr<TcpServer>

TcpServerPtr TcpServer::startServer(EventBases* bases, const std::string& host, short port, bool reusePort) {
    TcpServerPtr p(new TcpServer(bases));   // 创建TcpServer
    int r = p->bind(host, port, reusePort); // 服务器的套路:socket、bind、listen...
    if (r) {
        error("bind to %s:%d failed %d %s", host.c_str(), port, errno, strerror(errno));
    }
    return r == 0 ? p : NULL;
}

TcpServer::bind

服务器的套路:socket、bind、listen…

int TcpServer::bind(const std::string &host, short port, bool reusePort) {
    addr_ = Ip4Addr(host, port);
    int fd = socket(AF_INET, SOCK_STREAM, 0);    // 1. socket
    int r = net::setReuseAddr(fd);
    fatalif(r, "set socket reuse option failed");
    r = net::setReusePort(fd, reusePort);
    fatalif(r, "set socket reuse port option failed");
    r = util::addFdFlag(fd, FD_CLOEXEC);
    fatalif(r, "addFdFlag FD_CLOEXEC failed");
    r = ::bind(fd,(struct sockaddr *)&addr_.getAddr(),sizeof(struct sockaddr));     // 2. bind
    if (r) {
        close(fd);
        error("bind to %s failed %d %s", addr_.toString().c_str(), errno, strerror(errno));
        return errno;
    }
    r = listen(fd, 20);  // 3. listen
    fatalif(r, "listen failed %d %s", errno, strerror(errno));
    info("fd %d listening at %s", fd, addr_.toString().c_str());

    // 4. 创建channel,监听套接字可读(即有新连接到来时)时调用handleAccept
    listen_channel_ = new Channel(base_, fd, kReadEvent);
    listen_channel_->onRead([this]{ handleAccept(); });
    return 0;
}

步骤1,2,3只不过是例行公事。
步骤4是对Channel的使用,new了一个Channel(要想想在哪里delete的,答案是在TcpSever的析构函数),并设置了监听套接字可读(即有新连接到来时)时的回调函数为:handleAccept

TcpServer::handleAccept

epoll_wait发现TcpSever注册的监听套接字可读了,就会调用之前设置好的handleAccept来处理新连接。对每个连接创建一个TcpConn来处理。

void TcpServer::handleAccept() {
    struct sockaddr_in raddr;
    socklen_t rsz = sizeof(raddr);
    int lfd = listen_channel_->fd();
    int cfd;
    while (lfd >= 0 && (cfd = accept(lfd,(struct sockaddr *)&raddr,&rsz))>=0) { // accept ,接收此连接得到已连接套接字cfd
        sockaddr_in peer, local;
        socklen_t alen = sizeof(peer);
        int r = getpeername(cfd, (sockaddr*)&peer, &alen);
        if (r < 0) {
            error("get peer name failed %d %s", errno, strerror(errno));
            continue;
        }
        r = getsockname(cfd, (sockaddr*)&local, &alen);
        if (r < 0) {
            error("getsockname failed %d %s", errno, strerror(errno));
            continue;
        }
        r = util::addFdFlag(cfd, FD_CLOEXEC);
        fatalif(r, "addFdFlag FD_CLOEXEC failed");
        EventBase* b = bases_->allocBase();
        // addcon是一个lambda,感觉写在一起好臃肿,写成独立的函数不好吗?
        auto addcon = [=] {
            TcpConnPtr con = createcb_();
            con->attach(b, cfd, local, peer);
            if (statecb_) {
                con->onState(statecb_);
            }
            if (readcb_) {
                con->onRead(readcb_);
            }
            if (msgcb_) {
                con->onMsg(codec_->clone(), msgcb_);
            }
        };
        if (b == base_) { // 是同一个EventBase,说明在同一个线程,就直接调用它
            addcon();
        } else { // 否则,将该lambda移动到b所属的线程执行
            b->safeCall(move(addcon));
        }
    }
    if (lfd >= 0 && errno != EAGAIN && errno != EINTR) {
        warn("accept return %d  %d %s", cfd, errno, strerror(errno));
    }
}

注意下面这句,这是为了方便支持多线程。例子中的echo server使用的是EventBase,是单线程的。
TODO: 分析MultiBase

EventBase* b = bases_->allocBase();

addcon

auto addcon = [=] {
      TcpConnPtr con = createcb_();
      con->attach(b, cfd, local, peer);
      if (statecb_) {
          con->onState(statecb_);
      }
      if (readcb_) {
          con->onRead(readcb_);
      }
      if (msgcb_) {
          con->onMsg(codec_->clone(), msgcb_);
      }

显然addcon是一个lambda。createcb_在构造函数里定义了,就是new一个TcpConn,返回的是一个shared_ptr。(不明白为什么要用createcb,直接把new写在这里不就好了吗)。然后就是调用TcpConn的attach、设置一个回调函数。下篇文章再分析TcpConn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值