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 createcb_;
std::unique_ptr codec_;
TcpServer::TcpServer(EventBases* bases):
base_(bases->allocBase()),
bases_(bases),
listen_channel_(NULL),
createcb_([]{ return TcpConnPtr(new TcpConn); })
{
}
TcpServer::~TcpServer() { delete listen_channel_; }
TcpConn代表一个新连接。
该函数是一个static function,负责创建TcpServer,并返回一个shared_ptr
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;
}
服务器的套路: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
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();
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