linux 运行ctl文件_Linux C++ 网络编程(Muduo)(二)

本文介绍了Linux C++网络编程中使用Muduo库实现基于epoll的事件驱动模型。讲解了epoll的由来、EventLoop、Channel、Poller、Acceptor、TcpServer和TcpConnection等关键组件,阐述了它们在处理网络连接、数据读写中的作用。
9afea09666ed959d44d6eea9ae7388c2.png

前言

       在上一节中以拆分的方式学习完 Linux 、C++、网络等知识后,这节会将这三个模块糅合起来,站在项目的基础上再次去学习这三个模块。Linux 网路编程比较经典的有 Redis、Muduo、TeamTalk等开源项目。本文将以 Muduo 来介绍 网络编程的框架,学习完 Muduo 再去学习其他框架就容易许多了。注意, 文中贴的代码均为核心代码,且注释详细,认真阅读。Linux C++ 网络编程 (一)Linux C++ 开发环境搭建

下载路径

github 地址:https://github.com/smilew12/muduo.git

项目结构

项目主要分为两个模块:base 模块:主要封装互斥锁、条件变量、线程池、日志等基础类;net    模块:主要根据 reactor 模型对 Linux 平台下 Epoll 的封装;本文主要介绍服务器模型,所以只引导读者学习 net 模块, base 模块主要使用 RAII 技法封装的常用类。

epoll 由来

先来说说传统的迭代式服务器模型以及一个连接一个线程的模型的缺点,以下图一个连接一个线程为例说明:

cbe95ebc394430b13efed295063ce696.png

有图中可以看到,进程将阻塞在 accept 函数处,当客户端调用 connect 函数时,accept 返回,当前进程创建一个线程用来处理本次会话的任务。但是创建线程是有一定的开销的,在 32 位 Linux 系统下用户空间有 3G, 一个线程栈 10M, 那么总共可以创建 300 多个线程,同时切换线程也会有时间开销,所以并发连接数不会很高。IO 多路复用的产生有没有一种方式:
  • 我将我想要监听的链接、读写等事件全权委托给它;
  • 有连接到来事件的时候通知我?我直接调用 accept 函数去接收。
  • 有数据到来的时候也通知我?  我直接调用 recv 函数去收取数据。
  • 发送缓冲区有空间时也通知我?我直接调用 send 函数去发送数据。
为了解决这种相当于代理一样的东西,linux 内核给用户提供了一系列系统调用,当有事件时,该系统调用会返回相应事件,用户只需拿到事件进行处理即可。那么这就是 IO 多路复用函数(select / poll / epoll)因为本文是将 Linux 平台下的网络编程,所以选择了 Epoll:
epoll_create()函数用来创建一个代理对象;
epoll_wait()     函数就是当有事件到来时,会返回响应事件的代理;
epoll_ctl()主要是往这个代理中注册你想要监听的事件;
     下面是一个根据 Reactor 模型对 Epoll 的封装例子:
//头文件#ifndef __MYREACTOR_H__#define __MYREACTOR_H__#include #include #include #include #include #define WORKER_THREAD_NUM   5class CMyReactor
{public:
    CMyReactor();
    ~CMyReactor();bool init(const char* ip, short nport);bool uninit();bool close_client(int clientfd);static void* main_loop(void* p);private://no copyable
    CMyReactor(const CMyReactor& rhs);
    CMyReactor& operator = (const CMyReactor& rhs);bool create_server_listener(const char* ip, short port);static void accept_thread_proc(CMyReactor* pReatcor);static void worker_thread_proc(CMyReactor* pReatcor);private://C11语法可以在这里初始化int                          m_listenfd = 0;int                          m_epollfd  = 0;bool                         m_bStop    = false;std::shared_ptr<std::thread> m_acceptthread;std::shared_ptr<std::thread> m_workerthreads[WORKER_THREAD_NUM];std::condition_variable      m_acceptcond;std::mutex                   m_acceptmutex;std::condition_variable      m_workercond ;std::mutex                   m_workermutex;std::list<int>               m_listClients;
};#endif //!__MYREACTOR_H__
//.cpp文件#include "myreactor.h"#include #include #include #include #include #include   //for htonl() and htons()#include #include #include #include #include #include #include  //for std::setw()/setfill()#include #define min(a, b) ((a <= b) ? (a) : (b))
CMyReactor::CMyReactor()
{//m_listenfd = 0;//m_epollfd = 0;//m_bStop = false;
}
CMyReactor::~CMyReactor()
{
}bool CMyReactor::init(const char* ip, short nport)
{//创建监听socket,并将监听socket挂载到 epoll 上if (!create_server_listener(ip, nport))
    {std::cout <"Unable to bind: " <":" <"." <std::endl;return false;
    }//打印当前线程idstd::cout <"main thread id = " <std::this_thread::get_id() <std::endl;//启动接收新连接的线程
    m_acceptthread.reset(new std::thread(CMyReactor::accept_thread_proc, this));//启动工作线程(收发数据)for (auto& t : m_workerthreads)
    {
        t.reset(new std::thread(CMyReactor::worker_thread_proc, this));
    }return true;
}//释放资源bool CMyReactor::uninit()
{
    m_bStop = true;
    m_acceptcond.notify_one();
    m_workercond.notify_all();
    m_acceptthread->join();for (auto& t : m_workerthreads)
    {
        t->join();
    }
    ::epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_listenfd, NULL);//TODO: 是否需要先调用shutdown()一下?
    ::shutdown(m_listenfd, SHUT_RDWR);
    ::close(m_listenfd);
    ::close(m_epollfd);return true;
}bool CMyReactor::close_client(int clientfd)
{if (::epoll_ctl(m_epollfd, EPOLL_CTL_DEL, clientfd, NULL) == -1)
    {std::cout <"close client socket failed as call epoll_ctl failed" <std::endl;//return false;
    }
    ::close(clientfd);return true;
}//主 loopvoid* CMyReactor::main_loop(void* p)
{std::cout <"main thread id = " <std::this_thread::get_id() <std::endl;
    CMyReactor* pReatcor = static_cast(p);//在一个 while 循环中 不断根据 epoll_wait的返回值去处理相应的事件while (!pReatcor->m_bStop)
    {struct epoll_event ev[1024];int n = ::epoll_wait(pReatcor->m_epollfd, ev, 1024, 10);if (n == 0)continue;else if (n 0)
        {
        }int m = min(n, 1024);for (int i = 0; i         {//判断返回的如果是接受链接事件,则通知接收连接线程接收新连接if (ev[i].data.fd == pReatcor->m_listenfd)
                pReatcor->m_acceptcond.notify_one();//如果是收发数据事件,则通知普通工作线程接收数据else
            {//使用大括号将锁的粒度变细,将临界区变小
                {//m_listClients队列是共享资源,需要加锁std::unique_lock<std::mutex> guard(pReatcor->m_workermutex);
                    pReatcor->m_listClients.push_back(ev[i].data.fd);
                }//通知消费者 m_workercond 消费
                pReatcor->m_workercond.notify_one();//std::cout <
            }// end if
        }// end for-loop
    }// end whilestd::cout <"main loop exit ..." <std::endl;return NULL;
}void CMyReactor::accept_thread_proc(CMyReactor* pReatcor)
{std::cout <"accept thread, thread id = " <std::this_thread::get_id() <std::endl;//接受链接线程在一个死循环中不断接受客户端的连接while (true)
    {int newfd;struct sockaddr_in clientaddr;socklen_t addrlen;
        {std::unique_lock<std::mutex> guard(pReatcor->m_acceptmutex);//如果没有连接,进行将阻塞在这里等待。当m_acceptcond被唤醒时,//说明有新连接到来,那么调用 accept 接受连接
            pReatcor->m_acceptcond.wait(guard);if (pReatcor->m_bStop)break;//std::cout <
            newfd = ::accept(pReatcor->m_listenfd, (struct sockaddr *)&clientaddr, &addrlen);
        }if (newfd == -1)continue;std::cout <"new client connected: " <":" <std::endl;//将新socket设置为non-blockingint oldflag = ::fcntl(newfd, F_GETFL, 0);int newflag = oldflag | O_NONBLOCK;if (::fcntl(newfd, F_SETFL, newflag) == -1)
        {std::cout <"fcntl error, oldflag =" <", newflag = " <std::endl;continue;
        }struct epoll_event e;memset(&e, 0, sizeof(e));
        e.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
        e.data.fd = newfd;//将 accept 的连接fd ,继续加入 epoll 中监听他的 读写事件if (::epoll_ctl(pReatcor->m_epollfd, EPOLL_CTL_ADD, newfd, &e) == -1)
        {std::cout <"epoll_ctl error, fd =" <std::endl;
        }
    }std::cout <"accept thread exit ..." <std::endl;
}void CMyReactor::worker_thread_proc(CMyReactor* pReatcor)
{std::cout <"new worker thread, thread id = " <std::this_thread::get_id() <std::endl;while (true)
    {int clientfd;
        {std::unique_ lock<std::mutex> guard(pReatcor->m_workermutex);//注意此处应使用while, 避免虚假唤醒while (pReatcor->m_listClients.empty())
            {if (pReatcor->m_bStop)
                {std::cout <"worker thread exit ..." <std::endl;return;
                }
                pReatcor->m_workercond.wait(guard);
            }
            clientfd = pReatcor->m_listClients.front();
            pReatcor->m_listClients.pop_front();
        }//gdb调试时不能实时刷新标准输出,用这个函数刷新标准输出,使信息在屏幕上实时显示出来std::cout <std::endl;std::string strclientmsg;char buff[256];bool bError = false;while (true)
        {memset(buff, 0, sizeof(buff));int nRecv = ::recv(clientfd, buff, 256, 0);if (nRecv == -1)
            {if (errno == EWOULDBLOCK)break;else
                {std::cout <"recv error, client disconnected, fd = " <std::endl;
                    pReatcor->close_client(clientfd);
                    bError = true;break;
                }
            }//对端关闭了socket,这端也关闭。else if (nRecv == 0)
            {std::cout <"peer closed, client disconnected, fd = " <std::endl;
                pReatcor->close_client(clientfd);
                bError = true;break;
            }
            strclientmsg += buff;
        }//出错了,就不要再继续往下执行了if (bError)continue;std::cout <"client msg: " <//将消息加上时间标签后发回time_t now = time(NULL);struct tm* nowstr = localtime(&now);std::ostringstream ostimestr;
        ostimestr <"[" <tm_year + 1900 <"-"
            <std::setw(2) <std::setfill('0') <tm_mon + 1 <"-"
            <std::setw(2) <std::setfill('0') <tm_mday <" "
            <std::setw(2) <std::setfill('0') <tm_hour <":"
            <std::setw(2) <std::setfill('0') <tm_min <":"
            <std::setw(2) <std::setfill('0') <tm_sec <"]server reply: ";
        strclientmsg.insert(0, ostimestr.str());//对于数据的发送:LT不注册可写事件,有数据直接发送,调用send和write的时候可能会阻塞,但是没有关系//睡一会继续发,一直尝试,到数据发送出去while (true)
        {int nSent = ::send(clientfd, strclientmsg.c_str(), strclientmsg.length(), 0);if (nSent == -1)
            {if (errno == EWOULDBLOCK)
                {std::this_thread::sleep_for(std::chrono::milliseconds(10));continue;
                }else
                {std::cout <"send error, fd = " <std::endl;
                    pReatcor->close_client(clientfd);break;
                }
            }std::cout <"send: " <            strclientmsg.erase(0, nSent);if (strclientmsg.empty())break;
        }
    }
}//根据 ip、port 创建监听socket,和 epollfd, 并将监听socket 挂载到 epollfd 上bool CMyReactor::create_server_listener(const char* ip, short port)
{
    m_listenfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);if (m_listenfd == -1)return false;int on = 1;//设置监听 socket 的地址和端口的可重用
    ::setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
    ::setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEPORT, (char *)&on, sizeof(on));struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(ip);
    servaddr.sin_port = htons(port);//绑定if (::bind(m_listenfd, (sockaddr *)&servaddr, sizeof(servaddr)) == -1)return false;//监听if (::listen(m_listenfd, 50) == -1)return false;//创建 epollfd
    m_epollfd = ::epoll_create(1);if (m_epollfd == -1)return false;struct epoll_event e;memset(&e, 0, sizeof(e));
    e.events = EPOLLIN | EPOLLRDHUP;
    e.data.fd = m_listenfd;//将m_listenfd(监听socketfd)挂载到 epollfd 上面,让epoll_wait 进行监听if (::epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_listenfd, &e) == -1)return false;return true;
}
//main.cpp #include #include      //for signal()#include#include         //for exit()#include #include #include #include "myreactor.h"
CMyReactor g_reator;void prog_exit(int signo){std::cout <"program recv signal " <" to exit." <std::endl; 
    g_reator.uninit();
}void daemon_run(){int pid;
    signal(SIGCHLD, SIG_IGN);//1)在父进程中,fork返回新创建子进程的进程ID;//2)在子进程中,fork返回0;//3)如果出现错误,fork返回一个负值;
    pid = fork();if (pid 0)
    {std:: cout <"fork error" <std::endl;exit(-1);
    }//父进程退出,子进程独立运行else if (pid > 0) {exit(0);
    }//之前parent和child运行在同一个session里,parent是会话(session)的领头进程,//parent进程作为会话的领头进程,如果exit结束执行的话,那么子进程会成为孤儿进程,并被init收养。//执行setsid()之后,child将重新获得一个新的会话(session)id。//这时parent退出之后,将不会影响到child了。
    setsid();int fd;
    fd = open("/dev/null", O_RDWR, 0);if (fd != -1)
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
    }if (fd > 2)
        close(fd);
}int main(int argc, char* argv[]){  //设置信号处理
    signal(SIGCHLD, SIG_DFL);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGINT, prog_exit);
    signal(SIGKILL, prog_exit);
    signal(SIGTERM, prog_exit);short port = 0;int ch;bool bdaemon = false;//根据传入参数,判断是否开启守护进程模式  -dwhile ((ch = getopt(argc, argv, "p:d")) != -1)
    {switch (ch)
        {case 'd':
            bdaemon = true;break;case 'p':
            port = atol(optarg);break;
        }
    }if (bdaemon)
        daemon_run();if (port == 0)
        port = 12345;//根据ip、port 初始化socket,创建 epoll_wait()if (!g_reator.init("0.0.0.0", 12345))return -1;//进入 loop 循环
    g_reator.main_loop(&g_reator);return 0;
}
为了方便理解,我画了一个大体流程图,如下所示:

5ade884bff387bca6c2ac9b40d5abcc1.png

执行流程:1、创建监听 socket,并绑定、监听;2、调用 epoll_create() , 创建 epollfd 代理;3、将想要监听的 listenfd,通过 epoll_ctl() , 挂载到 epollfd 上,让 epollfd 代理监听;4、在一个 while 循环中,调用 epoll_wait(), 程序阻塞在这里,等待客户端连接到来;5、当某个客户端连接到来,epoll_wait() 第一次返回的是接受连接的 listenfd, 调用 accept 函数接受连接,将       accpet 返回的 connfd,挂载到 epollfd 上,继续让代理监听 connfd 的读写事件;6、当再次到达 5 时,如果是接受连接的 listenfd,那么继续 accept, 如果是读写事件,则进行 7, 接受或发送数据。希望读者先掌握上面的 Reactor 模型,然后再学习下面的模块。

至此,一个 reactor 模型基本已经完成了,相信你应该已经理解了,大体的流程了。那么在来看 Muduo 是怎养封装这个 epoll 的?

EventLoop

先来介绍最重要的模块 EventLoop :  他是上面第四步的实现:
//在一个循环中让 epoll_wait() 不断检测事件void EventLoop::loop() {while() {//vector, 用来返回 epoll_wait 中监听到的 有活动的事件(fd)
        m_activeChannels.clear();//m_activeChannels 是一个输入输出参数
        m_poller->poll(kPollTimeMs, &m_activeChannels);//遍历 epoll_wait 返回的结果,然后进行 accept / send / recvfor(ChannelList::iterator it = m_activeChannels.begin();
        it != m_activeChannels.end(); ++it)
           {
               (*it)->handleEvent();
        }//处理其他事件, 此时不必关心
        doPendingFunctors();
    }
}

Channel

对于上面 epoll_wait() 返回的就绪事件,怎么去处理呢?那么 Channel 类主要负责把不同的 IO 事件分发给不同的回调,例如 ReadCallback、 WriteCallBack 等;同时提供向 epollfd 中注册可读可写事件的接口。每个 Channel 自始至终只负责一个文件描述符的 IO 事件分发。
//主要处理 epoll_wait 返回的事件,并将他们分发到不同的回调void Channel::handleEvent()
{//如果出错,那么分发到错误的回调if(m_revents & (POLLERR | POLLNVAL)){if(m_errorCallBack) m_errorCallBack();
      }//如果是 POLLIN, 说明是数据收发的回调if(m_revents & (POLLIN | POLLPRI | POLLRDHUP)){if(m_readCallBack) m_readCallBack();
      }//如果是 POLLOUT, 那么是可写事件的回调if(m_revents & POLLOUT){if(m_writeCallBack) m_writeCallBack();
      }
}//设置回调函数,供其他模块注册回调函数void setReadCallback(const ReadEventCallback& cb){ readCallback_ = cb; }void setWriteCallback(const EventCallback& cb){ writeCallback_ = cb; }void setCloseCallback(const EventCallback& cb){ closeCallback_ = cb; }void setErrorCallback(const EventCallback& cb){ errorCallback_ = cb; }//往 epoll 中注册可读事件void enableReading() { m_events |= kReadEvent; update(); }//从 epoll 中移除可读事件void disableReading() { m_events &= ~kReadEvent; update(); }void enableWriting() { m_events |= kWriteEvent; update(); }bool isWriting() { return m_events &= kWriteEvent; }bool isReading() { return m_events &= kReadEvent; }void disableWriting() { m_events &= ~kWriteEvent; update(); }//最终都调用 uodate函数,该函数会调用 EventLoop::uodateChannel(), 后者在调用 Poller::updateChannel();void Channel::update()
{
  m_addedToLoop = true;
  p_loop->runInLoop(std::bind(&EventLoop::updateChannel, p_loop, this));
}

Poller

Poller类是 IO multiplexing 的封装。在 Muduo 中是一个抽象类,因为 Muduo 同时支持 poll 和 epoll 两种 IO 多路复用机制,他们是真正调用 epoll_wait() 的地方
TimeStamp Poller::poll(int timeoutMs, ChannelList* activeChannels)
{//真正的调用 epoll_wait / poll 的地方。int numEvents = ::poll(/*&*m_pollfds.begin()*/m_pollfds.data(), m_pollfds.size(), timeoutMs);TimeStamp now(TimeStamp::now());if(numEvents > 0){//将返回的结果封装成 channel 返回给 Eventloop::loop 函数
    fillActiveChannels(numEvents, activeChannels);
  }else if(numEvents == 0){
    LOG_TRACE <" nothing happended";
  }else{
    LOG_SYSERR <"Poller::poll()";
  }return now;
}void Poller::fillActiveChannels(int numEvents, ChannelList* activeChannels) const
{for(PollFdList::const_iterator pfd = m_pollfds.begin();
      pfd != m_pollfds.end() && numEvents > 0; ++pfd)
  {if(pfd->revents > 0)
    {
      --numEvents;
      ChannelMap::const_iterator ch = m_channels.find(pfd->fd);
      assert(ch != m_channels.end());
      Channel* channel = ch->second;
      assert(channel->fd() == pfd->fd);
      channel->set_revents(pfd->revents);//将返回的结果封装成 channel 返回给 Eventloop::loop 函数
      activeChannels->push_back(channel);
    }
  }
}
讲解完 EventLoop、Poller、Channel 和 Poller 后,在从下面的时序图,看看他们的执行流程

8e0c9f0d7a6a924d3c416ef7175155bc.png

上面提到的 Channel::handleEvent() 会将事件分发给注册此回调函数的模块,那么谁都需要注册呢?
  • 接受客户端连接到来的类 ,需要注册 Channel::setReadCallback(),  以调用accept 进行接受;
  • 接受有数据到来的类 ,需要注册 Channel::setReadCallback(), 以调用 recv 接收数据;
  • 检测发送缓冲区是否可写, 需要注册 Channel::WriteCallback(), 以调用 send 函数发送数据;

Acceptor

接受客户端连接到来的类就是 Muduo::net::Acceptor 类
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr)
  : loop_(loop),//在构造函数中同时创建 acceptsocket,并调用下面的 acceptChannel_.setReadCallback 将acceptSocket添加到 epoll 中,监听可读事件
    acceptSocket_(sockets::createNonblockingOrDie()),
    acceptChannel_(loop, acceptSocket_.fd()),
    listenning_(false),
    idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
  assert(idleFd_ >= 0);
  acceptSocket_.setReuseAddr(true);//绑定监听
  acceptSocket_.bindAddress(listenAddr);//注册可读事件,当有事件到来时, 调用 handleRead() 回调接受连接
  acceptChannel_.setReadCallback(
      boost::bind(&Acceptor::handleRead, this));
}void Acceptor::handleRead()
{
  loop_->assertInLoopThread();InetAddress peerAddr(0);//真正 调用 accept 的地方int connfd = acceptSocket_.accept(&peerAddr);if (connfd >= 0)
  {// string hostport = peerAddr.toIpPort();// LOG_TRACE <if (newConnectionCallback_)
    {//将接受的新连接,分配给 TcpConnection 函数。即代表一路连接
      newConnectionCallback_(connfd, peerAddr);
    }else
    {
      sockets::close(connfd);
    }
  }else
  {// Read the section named "The special problem of// accept()ing when you can't" in libev's doc.// By Marc Lehmann, author of livev.if (errno == EMFILE)
    {
      ::close(idleFd_);
      idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
      ::close(idleFd_);
      idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
    }
  }
}
下面的时序图将更好的为你展示 Acceptor 的作用:

8c19ef2a8d6f5bdb1c39aa5661f1e184.png

Acceptor::accpet()  会返回一个 connfd(客户端连接fd),同时会暴露setNewConnectionCallback() 回调函数,供需要使用 connfd 的类使用;那么那个类需要使用 connfd 呢?  首先想一想 connfd 主要是干嘛的?它是 accpet 返回的文件描述符,可以用来完成数据收发,收取和发送用户的数据,完成具体业务。现在需要一个类去管理(TcpServer) connfd,并且每创建一个 connfd,都会创建这个文件描述符所对应的连接类(TcpConnection).用来管理本次会话。那么 TcpServer 和 TcpConnection 类就相应而生:

TcpServer

TcpServer 类会注册 Acceptor::setNewConnectionCallback,同时会为每个连接创建一个 TcpConnection 类;
//TcpServer 类
TcpServer::TcpServer(EventLoop* loop,const InetAddress& listenAddr,const string& nameArg)
  : loop_(CHECK_NOTNULL(loop)),
    hostport_(listenAddr.toIpPort()),
    name_(nameArg),
    acceptor_(new Acceptor(loop, listenAddr)),
    threadPool_(new EventLoopThreadPool(loop)),
    connectionCallback_(defaultConnectionCallback),
    messageCallback_(defaultMessageCallback),
    started_(false),
    nextConnId_(1)
{//在构造函数中 注册 Acceptor 的 connfd 回调函数,同时调用 newconnection 函数
  acceptor_->setNewConnectionCallback(
      boost::bind(&TcpServer::newConnection, this, _1, _2));
}void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();// 按照轮叫的方式选择一个EventLoop
  EventLoop* ioLoop = threadPool_->getNextLoop();char buf[32];snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_);
  ++nextConnId_;string connName = name_ + buf;
  LOG_INFO <"TcpServer::newConnection [" <           <"] - new connection [" <           <"] from " <  InetAddress localAddr(sockets::getLocalAddr(sockfd));//每一个连接 新建一个 TcpConnection 类,同时设置他们的用来数据收发的回调函数TcpConnectionPtr conn(new TcpConnection(ioLoop,
                                          connName,
                                          sockfd,
                                          localAddr,
                                          peerAddr));
  connections_[connName] = conn;
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      boost::bind(&TcpServer::removeConnection, this, _1));// conn->connectEstablished();
  ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
  LOG_TRACE <"[5] usecount=" <
}

TcpConnection

TcpConnection 类会向 分发事件的类 Channel 注册接受数据和发送数据的回调 setReadCallback、setWriteCallback等,
// TcpConeciton 的构造函数
TcpConnection::TcpConnection(EventLoop* loop,const string& nameArg,int sockfd,const InetAddress& localAddr,const InetAddress& peerAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(nameArg),
    state_(kConnecting),
    socket_(new Socket(sockfd)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),
    peerAddr_(peerAddr),
    highWaterMark_(64*1024*1024)
{// 通道可读事件到来的时候,回调TcpConnection::handleRead,_1是事件发生时间
  channel_->setReadCallback(
      boost::bind(&TcpConnection::handleRead, this, _1));// 通道可写事件到来的时候,回调TcpConnection::handleWrite
  channel_->setWriteCallback(
      boost::bind(&TcpConnection::handleWrite, this));// 连接关闭,回调TcpConnection::handleClose
  channel_->setCloseCallback(
      boost::bind(&TcpConnection::handleClose, this));// 发生错误,回调TcpConnection::handleError
  channel_->setErrorCallback(
      boost::bind(&TcpConnection::handleError, this));
  LOG_DEBUG <"TcpConnection::ctor[" <"] at " <this
            <" fd=" <  socket_->setKeepAlive(true);
}//用于发送数据的函数void TcpConnection::handleWrite()//当有事件到来,Channel::handleEvent() 会分发事件,同时提供回调函数, TcpConnection 会注册 setReadCallback 完成真正的数据收发;void TcpConnection::handleRead(Timestamp receiveTime)
由于篇幅有限,本文先介绍了 Muduo 中大体的类, 还有 Buffer 类将会在下文介绍。

总结

读者认真阅读上文中的所有类,结合代码注释,先了解 Reactor 模型的大体工作流程,然后再了解 Muduo 中每个类的功能,希望本片文章会对你有所帮助!

后期彩蛋

应用层收发缓冲区的设计?为什么要有收发缓冲区?ET / LT 模式数据收发的过程
这是一门linuxc++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时间,所以老师会在课前先写好代码,主要的时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之间的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值