muduo网络库浅谈(二)
第二章 muduo网络库之网络通信
muduo网络库的重要组件之一便是非阻塞的TCP网络库,该节将介绍muduo中TCP网络库的设计,该网络库主要由几个类主要的类组成:class Acceptor,class TcpServer,class TcpConnection,class Buffer。
底层库的实现
在介绍class Acceptor之前,需要先简单介绍一下几个底层的库,包括:class Socket,class InetAddress,namespace sockets中定义函数,这几个class来管理socket。
#先来看namespace sockets的源码,namespace sockets中定义的函数是class Socket,class InetAddress功能实现的前提:
namespace sockets
#ifndef MUDUO_NET_SOCKETSOPS_H
#define MUDUO_NET_SOCKETSOPS_H
#include <arpa/inet.h>
#include <endian.h>
// struct sockaddr_in {
// sa_family_t sin_family; /* address family: AF_INET */协议族
// uint16_t sin_port; /* port in network byte order */端口号
// struct in_addr sin_addr; /* internet address */地址
// };
//struct sockaddr {
// sa_family_t sin_family;//地址族
// char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
//struct sockaddr与struct sockaddr_in 两者可以相互转化
namespace muduo
{
namespace sockets
{
inline uint64_t hostToNetwork64(uint64_t host64)//主机字节序转网络字节序
{
return htobe64(host64);
}
inline uint32_t hostToNetwork32(uint32_t host32)//同上
{
return htonl(host32);
}
inline uint16_t hostToNetwork16(uint16_t host16)//同上
{
return htons(host16);
}
inline uint64_t networkToHost64(uint64_t net64)//网络字节序转主机
{
return be64toh(net64);
}
inline uint32_t networkToHost32(uint32_t net32)//同上
{
return ntohl(net32);
}
inline uint16_t networkToHost16(uint16_t net16)//同上
{
return ntohs(net16);
}
int createNonblockingOrDie();//创建无阻塞的sockfd即套接字文件描述符
void bindOrDie(int sockfd, const struct sockaddr_in& addr);//绑定地址和端口
void listenOrDie(int sockfd);//监听sockfd
int accept(int sockfd, struct sockaddr_in* addr);//接受连接,并返回目标的地址端口至addr中
void close(int sockfd);//关闭sockfd
void toHostPort(char* buf, size_t size,
const struct sockaddr_in& addr);//addr中地址等信息输出到buf[]中
void fromHostPort(const char* ip, uint16_t port,
struct sockaddr_in* addr);//将ip,port等输出到addr中
}
}
#endif
namespace
{
typedef struct sockaddr SA;
const SA* sockaddr_cast(const struct sockaddr_in* addr)//将sockaddr_in*类型转换为sockaddr*类型
{
return static_cast<const SA*>(implicit_cast<const void*>(addr));
}
SA* sockaddr_cast(struct sockaddr_in* addr)//同上
{
return static_cast<SA*>(implicit_cast<void*>(addr));
}
void setNonBlockAndCloseOnExec(int sockfd)//设置sockfd为无阻塞NONBLOCK且CLOEXEC
{
// non-block
int flags = ::fcntl(sockfd, F_GETFL, 0);
flags |= O_NONBLOCK;
int ret = ::fcntl(sockfd, F_SETFL, flags);
// FIXME check
// close-on-exec
flags = ::fcntl(sockfd, F_GETFD, 0);
flags |= FD_CLOEXEC;
ret = ::fcntl(sockfd, F_SETFD, flags);
// FIXME check
}
}
int sockets::createNonblockingOrDie()//创建一个sockfd,
{
// socket
#if VALGRIND
int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
setNonBlockAndCloseOnExec(sockfd);
#else
int sockfd = ::socket(AF_INET,
SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
IPPROTO_TCP);
if (sockfd < 0)
{
LOG_SYSFATAL << "sockets::createNonblockingOrDie";
}
#endif
return sockfd;
}
void sockets::bindOrDie(int sockfd, const struct sockaddr_in& addr)//绑定地址和端口
{
int ret = ::bind(sockfd, sockaddr_cast(&addr), sizeof addr);
if (ret < 0)
{
LOG_SYSFATAL << "sockets::bindOrDie";
}
}
void sockets::listenOrDie(int sockfd)//监听
{
int ret = ::listen(sockfd, SOMAXCONN);
if (ret < 0)
{
LOG_SYSFATAL << "sockets::listenOrDie";
}
}
int sockets::accept(int sockfd, struct sockaddr_in* addr)//接受连接,并返回目标的地址端口至addr中
{
socklen_t addrlen = sizeof *addr;
#if VALGRIND
int connfd = ::accept(sockfd, sockaddr_cast(addr), &addrlen);
setNonBlockAndCloseOnExec(connfd);
#else
int connfd = ::accept4(sockfd, sockaddr_cast(addr),
&addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#endif
if (connfd < 0)
{
int savedErrno = errno;
LOG_SYSERR << "Socket::accept";
switch (savedErrno)
{
case EAGAIN:
case ECONNABORTED:
case EINTR:
case EPROTO: // ???
case EPERM:
case EMFILE: // per-process lmit of open file desctiptor ???
// expected errors
errno = savedErrno;
break;
case EBADF:
case EFAULT:
case EINVAL:
case ENFILE:
case ENOBUFS:
case ENOMEM:
case ENOTSOCK:
case EOPNOTSUPP:
// unexpected errors
LOG_FATAL << "unexpected error of ::accept " << savedErrno;
break;
default:
LOG_FATAL << "unknown error of ::accept " << savedErrno;
break;
}
}
return connfd;
}
void sockets::close(int sockfd)//关闭
{
if (::close(sockfd) < 0)
{
LOG_SYSERR << "sockets::close";
}
}
void sockets::toHostPort(char* buf, size_t size,
const struct sockaddr_in& addr)
{
char host[INET_ADDRSTRLEN] = "INVALID";
::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof host);
uint16_t port = sockets::networkToHost16(addr.sin_port);
snprintf(buf, size, "%s:%u", host, port);
}
void sockets::fromHostPort(const char* ip, uint16_t port,
struct sockaddr_in* addr)
{
addr->sin_family = AF_INET;
addr->sin_port = hostToNetwork16(port);
if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
{
LOG_SYSERR << "sockets::fromHostPort";
}
}
namespace sockets提供了一系列函数来提供socket的设置监听功能,以及地址转换函数,提供给class InetAddress极大地便利,class InetAddress提供了地址端口等信息的保存,只有几个简单的成员函数和一个类成员struct sockaddr_in addr_,源码如下:
class InetAddress
#ifndef MUDUO_NET_INETADDRESS_H
#define MUDUO_NET_INETADDRESS_H
#include "datetime/copyable.h"
#include <string>
#include <netinet/in.h>
namespace muduo
{
class InetAddress : public muduo::copyable
{
public:
explicit InetAddress(uint16_t port);
InetAddress(const std::string& ip, uint16_t port);
InetAddress(const struct sockaddr_in& addr)
: addr_(addr)
{ }
std::string toHostPort() const;将sockaddr_in转换为string返回
const struct sockaddr_in& getSockAddrInet() const { return addr_; }//返回sockaddr_in
void setSockAddrInet(const struct sockaddr_in& addr) { addr_ = addr; }//设置sockaddr_in
private:
struct sockaddr_in addr_;//保存由地址和端口信息
};
}
#endif
成员函数的具体功能实现实际上都是由namespace sockets提供的,就不一一注释了,class InetAddress的成员函数实现如下:
InetAddress::InetAddress(uint16_t port)
{
bzero(&addr_, sizeof addr_);
addr_.sin_family = AF_INET;
addr_.sin_addr.s_addr = sockets::hostToNetwork32(kInaddrAny);
addr_.sin_port = sockets::hostToNetwork16(port);
}
InetAddress::InetAddress(const std::string& ip, uint16_t port)
{
bzero(&addr_, sizeof addr_);
sockets::fromHostPort(ip.c_str(), port, &addr_);
}
std::string InetAddress::toHostPort() const
{
char buf[32];
sockets::toHostPort(buf, sizeof buf, addr_);
return buf;
}
class Socket
class Socket提供了sockfd套接字文件描述符的的管理功能,即对sockfd提供绑定地址端口,listen监听,accept接受连接等功能,当然,底层的功能仍旧是namespace sockets中函数提供的。class Socket源码如下:
namespace muduo
{
class InetAddress;
class Socket : boost::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 setReuseAddr(bool on);//为文件描述符设置重用
private:
const int sockfd_;//文件描述符
};
}
#endif
成员函数实现如下,但不作注释,实现都是namespace sockets提供的:
Socket::~Socket()
{
sockets::close(sockfd_);
}
void Socket::bindAddress(const InetAddress& addr)
{
sockets::bindOrDie(sockfd_, addr.getSockAddrInet());
}
void Socket::listen()
{
sockets::listenOrDie(sockfd_);
}
int Socket::accept(InetAddress* peeraddr)
{
struct sockaddr_in addr;
bzero(&addr, sizeof addr);
int connfd = sockets::accept(sockfd_, &addr);
if (connfd >= 0)
{
peeraddr->setSockAddrInet(addr);
}
return connfd;
}
void Socket::setReuseAddr(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
&optval, sizeof optval);
}
以上便是网络库关键底层的实现。
class Accetpor
class Accetpor顾名思义拥有socket中的accept的行为,不过稍显不同的是,这种accept行为是使用在poll()中,使得网络通信的行为统一经由EventLoop的loop的poll()函数中处理,先来看看源码:
namespace muduo
{
class EventLoop;
class InetAddress;
class Acceptor : boost::noncopyable
{
public:
typedef boost::function<void (int sockfd,
const InetAddress&)> NewConnectionCallback;
Acceptor(EventLoop* loop, const InetAddress& listenAddr);//初始化,绑定端口
void setNewConnectionCallback(const NewConnectionCallback& cb)//设置当有connect时,调用的函数
{ newConnectionCallback_ = cb; }
bool listenning() const { return listenning_; }//是否处于监听端口中
void listen();//监听端口
private:
void handleRead();//回调函数,当有连接时,sockfd可读,poll从阻塞返回,并调用handleRead()(即handleRead()会注册到Channel的回调函数中)
EventLoop* loop_;
Socket acceptSocket_;//
Channel acceptChannel_;//
NewConnectionCallback newConnectionCallback_;//
bool listenning_;//
};
}
#endif
直接从头文件定义中可以看出,class Acceptor的功能只有提供回调函数和注册Channel而已,之后的行为就是Channel,Poller等类来处理,以及socket等类来初始化本机地址等参数。
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr)
: loop_(loop),
acceptSocket_(sockets::createNonblockingOrDie()),//创建sockfd套接字文件描述符,
acceptChannel_(loop, acceptSocket_.fd()),//初始化Channel,但并没有正式地将Channel注册到Poller中
listenning_(false)
{
acceptSocket_.setReuseAddr(true);//设置sockfd复用
acceptSocket_.bindAddress(listenAddr);//绑定地址端口等
acceptChannel_.setReadCallback(
boost::bind(&Acceptor::handleRead, this));//设置Channel的回调函数
}
void Acceptor::listen()//开始监听,
{
loop_->assertInLoopThread();
listenning_ = true;
acceptSocket_.listen();//监听
acceptChannel_.enableReading();//将Channel注册到Poller,一旦sockfd由可读事件发生则返回并执行handleRead()
}
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr(0);
//FIXME loop until no more
int connfd = acceptSocket_.accept(&peerAddr);//接受连接,创建并返回连接事件描述符(connfd),服务端与客户端的通信就是在connfd上进行的
if (connfd >= 0) {
if (newConnectionCallback_) {
newConnectionCallback_(connfd, peerAddr);//调用用户提供的好的newConnectionCallback函数
} else {
sockets::close(connfd);//未设置newConnectionCallback函数则关闭connfd
}
}
}
class TcpServer
上一节中介绍了class Acceptor,但acceptor只是建立了连接,并没有将connfd连接文件描述符来实现进一步的通信,需要编写一个class TcpServer来管理Acceptor后的connfd,以及一系列用户设置的用于当连接时调用的函数ConnectionCallback以及当客户端发送信息后处理的函数MessageCallback。具体看源码:
namespace muduo
{
class Acceptor;
class EventLoop;
class TcpServer : boost::noncopyable
{
public:
TcpServer(EventLoop* loop, const InetAddress& listenAddr);
~TcpServer();
void start();//监听开始,并将sockfd加入到Poller中
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }//设置连接时的回调函数
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }//设置处理信息的回调函数
private:
void newConnection(int sockfd, const InetAddress& peerAddr);//newConnection()将会被注册到Acceptor的回调函数,继而注册到Poller中,当sockfd可读(有连接)时会调用newConnection()
typedef std::map<std::string, TcpConnectionPtr> ConnectionMap;//TcpConnectionPtr为accept后返回的connfd通信管理的类的指针
EventLoop* loop_;
const std::string name_;
boost::scoped_ptr<Acceptor> acceptor_; //只拥有1个Acceptor的实例
ConnectionCallback connectionCallback_;//回调函数
MessageCallback messageCallback_;//回调函数
bool started_;
int nextConnId_; //ID编号,即TcpConnection的名称
ConnectionMap connections_;//用来存储管理已经建立的连接TcpConnection
};
}
#endif
从以上可以看出TcpServer的作用,class Acceptor负责accept的行为,class TcpConnection负责建立连接后信息处理的行为,class TcpServer作用是管理一个Acceptor,当有客户端连接后就创建一个TcpConnection,并通过容器管理TcpConnection的共享指针,来看看其成员函数的实现:
using namespace muduo;
TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr)
: loop_(CHECK_NOTNULL(loop)),//
name_(listenAddr.toHostPort()),//本机地址端口信息
acceptor_(new Acceptor(loop, listenAddr)),//
started_(false),//开始标识符初始化
nextConnId_(1)//TcpConnection的编号的启示
{
acceptor_->setNewConnectionCallback(
boost::bind(&TcpServer::newConnection, this, _1, _2));//向Acceptor的回调函数绑定该类的newConnection函数
}
TcpServer::~TcpServer()
{
}
void TcpServer::start()//开始监听端口,//监听开始,并将sockfd以及相应的回调函数newConnection加入到Poller中
{
if (!started_)
{
started_ = true;
}
if (!acceptor_->listenning())
{
loop_->runInLoop(
boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
}
}
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)//回调函数,当sockfd有连接时,调用该函数,
{
loop_->assertInLoopThread();
char buf[32];
snprintf(buf, sizeof buf, "#%d", nextConnId_);
++nextConnId_;
std::string connName = name_ + buf;//新TcpConnection的名称
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toHostPort();
InetAddress localAddr(sockets::getLocalAddr(sockfd));
TcpConnectionPtr conn(
new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));//创建新的TcpConnection,其以共享指针的形式保存
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);//对TcpConnection设置connectionCallback_函数
conn->setMessageCallback(messageCallback_);
conn->connectEstablished();
}
class TcpServer的函数newConnection()会在sockfd有都市被调用,创建了class TcpConnection的实例,并设置了客户端和服务器端通信处理的一系列的回调函数。
class TcpConnection
初步实现
先来看看muduo一书中介绍的最开始简陋版本的class TcpConnection吧,但在说之前首先要明确他的作用,上节中class TcpServer管理class Acceptor和class TcpConnection,当Acceptor注册到Poller中且有来自客户端的connect,则会调用TcpServer中的newConnection函数,继而创建一个class TcpConnection的实例来管理此次用于通信的connfd文件描述符和双方的地址信息,同时向Poller注册该文件描述符以及相应的处理信息的回调函数。也就是说TcpConnection需要包含一个Channel保存文件描述符和注册,以及相应的调用函数(但这也只是初步的功能)。
class TcpConnection的源码如下:
#ifndef MUDUO_NET_TCPCONNECTION_H
#define MUDUO_NET_TCPCONNECTION_H
#include "Callbacks.h"
#include "InetAddress.h"
#include <boost/any.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
namespace muduo
{
class Channel;
class EventLoop;
class Socket;
class TcpConnection : boost::noncopyable,
public boost::enable_shared_from_this<TcpConnection>
{
public:
TcpConnection(EventLoop* loop,
const std::string& name,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr);//初始化
~TcpConnection();
EventLoop* getLoop() const { return loop_; }
const std::string& name() const { return name_; }
const InetAddress& localAddress() { return localAddr_; }
const InetAddress& peerAddress() { return peerAddr_; }
bool connected() const { return state_ == kConnected; }//返回当前连接状态
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }//设置由新连接时调用的回调函数
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }//设置当connfd有信息被写入时调用的回调函数
void connectEstablished(); //负责state_的改写,以及向Poller注册connfd。
private:
enum StateE { kConnecting, kConnected, };//枚举型 ,当前的连接状态
void setState(StateE s) { state_ = s; }
void handleRead();//该函数是被正式注册到Channel中的回调函数,其中包含了messageCallback_函数,即该函数当connfd有信息被写入时调用,继而调用messageCallback_
EventLoop* loop_;
std::string name_;
StateE state_;
boost::scoped_ptr<Socket> socket_;//scoped_ptr自动管理socket_和Channel的生命周期
boost::scoped_ptr<Channel> channel_;//
InetAddress localAddr_;//本机地址
InetAddress peerAddr_;//连接方地址
ConnectionCallback connectionCallback_;//连接时调用的回调函数,只调用一次
MessageCallback messageCallback_;//每次connfd可读时调用的回调函数,调用多次
};
typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;//TcpConnection使用共享指针来管理,保证不需要时自动析构
}
#endif
成员函数实现如下:
#include "TcpConnection.h"
#include "logging/Logging.h"
#include "Channel.h"
#include "EventLoop.h"
#include "Socket.h"
#include <boost/bind.hpp>
#include <errno.h>
#include <stdio.h>
using namespace muduo;
TcpConnection::TcpConnection(EventLoop* loop,
const std::string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(nameArg),
state_(kConnecting),
socket_(new Socket(sockfd)),创建以connfd文件描述符初始化的Socket类实例
channel_(new Channel(loop, sockfd)),//创建Channel,并将connfd文件描述符注册
localAddr_(localAddr),
peerAddr_(peerAddr)
{
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this));//将handleRead绑定到Channel的回调函数
}
TcpConnection::~TcpConnection()//其成员函数都是使用智能指针来管理,不需要手动调用其析构
{
LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this
<< " fd=" << channel_->fd();
}
void TcpConnection::connectEstablished()//该函数实现了Channel注册到Poller中,并执行connectionCallback_函数。connectEstablished函数由TcpServer的newConnection函数调用,且一次connect执行一次。
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);///设置状态
channel_->enableReading();//向Poller注册
connectionCallback_(shared_from_this());//调用connectionCallback_,注意该函数的参数,用户在设置是该函数时需要注意
}
void TcpConnection::handleRead()//connfd可读时调用的函数,并调用用户提供的 messageCallback_函数
{
char buf[65536];
ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
messageCallback_(shared_from_this(), buf, n);
}
TcpConnection处理网络通信断开
上节中只实现了TcpConnection的messageCallback_和connectionCallback_初级的消息处理,但TcpConnection只生不灭,需要相应的断开处理,包括移除TcpServer中保存的TcpConnection指针,以及Channel的注销,这些功能的实现需要系统自动调用,直观地实现方式是通过一个closecallback函数注册如Channel中,当连接断开时自动调用,按照正确的顺序移除和析构TcpConnection。
此处列出更改的代码的调用顺序
void Channel::handleEvent()
{
eventHandling_ = true;
if (revents_ & POLLNVAL) {
LOG_WARN << "Channel::handle_event() POLLNVAL";
}
if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) {
LOG_WARN << "Channel::handle_event() POLLHUP";
if (closeCallback_) closeCallback_();//调用Channel的closeCallback_函数
}
if (revents_ & (POLLERR | POLLNVAL)) {
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) {
if (readCallback_) readCallback_();
}
if (revents_ & POLLOUT) {
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}
Channel::handleEvent()会在通信断开时调用closeCallback_(),closeCallback_()实际注册的函数为channel_->setCloseCallback(
boost::bind(&TcpConnection::handleClose, this)),即调用TcpConnection::handleClose函数。
TcpConnection::handleClose函数如下:
void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "TcpConnection::handleClose state = " << state_;
assert(state_ == kConnected);
channel_->disableAll();//connfd设置为kNoneEvent状态,使得pollfd->fd=-channel->fd()-1,使Poller并不关注fd上的事件
closeCallback_(shared_from_this());//调用TcpConnection的 closeCallback_函数
}
以上的handleClose函数会调用closeCallback_函数,该函数的注册为TcpConnectionPtr conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)),即TcpConnection的closeCallback_函数会继续调用TcpServer::removeConnection函数。
TcpServer::removeConnection的代码如下:
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
LOG_INFO << "TcpServer::removeConnection [" << name_
<< "] - connection " << conn->name();
size_t n = connections_.erase(conn->name());//从map<std::string, TcpConnectionPtr>容器中移除TcpConnection的共享指针,但不会析构,因为当前函数输入拥有一个TcpConnection的共享指针。
assert(n == 1); (void)n;
loop_->queueInLoop(
boost::bind(&TcpConnection::connectDestroyed, conn));//调用queueInLoop,将TcpConnection::connectDestroyed加入到线程队列,使其所在的线程执行完所有活跃pollfd相应的回调函数后执行。
//该TcpConnectionPtr conn的生命周期被延长到了执行函数队列时,那时候没有任何读取需要删除的pollfd的行为(该函数必定被所属的EventLoop执行,
//Channel的nocopyable保证了pollfd亦不会在其他线程使用),可以安全的移除Channel和彻底的析构TcpConnectionPtr conn
}
在执行函数队列时,会调用TcpConnection::connectDestroyed函数,并执行完后,TcpConnectionPtr conn的引用计数将至1,正式自动析构。
TcpConnection::connectDestroyed函数的代码如下:
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
assert(state_ == kConnected);
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());//告诉用户连接已断开
loop_->removeChannel(get_pointer(channel_));//移除Channel,该函数结束后,自动析构TcpConnection
}
缓冲区的实现
TcpConnection的函数会负责读取connfd文件描述符上的内容,poll函数具有水平触发方式,messagecallback函数需要一次性读取其中的可读内容,但有时这些connfd上的可读内容并不可以解析成一条完整的指令或一条半的指令,那么这些信息需要存储在一个buffer中,即将connfd上的内容搬运到TcpConnection的buffer中,该buffer的实现在《Linux多线程服务端编程 使用muduo C++网络库》P208。
新增了读写缓冲区的TcpConnection:
namespace muduo
{
class Channel;
class EventLoop;
class Socket;
///
/// TCP connection, for both client and server usage.
///
class TcpConnection : boost::noncopyable,
public boost::enable_shared_from_this<TcpConnection>
{
public:
/// Constructs a TcpConnection with a connected sockfd
///
/// User should not create this object.
TcpConnection(EventLoop* loop,
const std::string& name,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr);
~TcpConnection();
EventLoop* getLoop() const { return loop_; }
const std::string& name() const { return name_; }
const InetAddress& localAddress() { return localAddr_; }
const InetAddress& peerAddress() { return peerAddr_; }
bool connected() const { return state_ == kConnected; }
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
/// Internal use only.
void setCloseCallback(const CloseCallback& cb)
{ closeCallback_ = cb; }
// called when TcpServer accepts a new connection
void connectEstablished(); // should be called only once
// called when TcpServer has removed me from its map
void connectDestroyed(); // should be called only once
private:
enum StateE { kConnecting, kConnected, kDisconnected, };
void setState(StateE s) { state_ = s; }
void handleRead(Timestamp receiveTime);
void handleWrite();
void handleClose();
void handleError();
EventLoop* loop_;
std::string name_;
StateE state_;
boost::scoped_ptr<Socket> socket_;
boost::scoped_ptr<Channel> channel_;
InetAddress localAddr_;
InetAddress peerAddr_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
CloseCallback closeCallback_;
Buffer inputBuffer_;//新增内容,用于存放connfd上的内容
};
typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
}
有了缓冲区后,相应的读取数据进行处理的函数需要重新实现:
void TcpConnection::handleRead(Timestamp receiveTime)
{
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);//使用buffer来存放读取的数据
if (n > 0) {
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);//读取buffer中的内容进行相应的处理
} else if (n == 0) {
handleClose();
} else {
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
发送数据和output缓冲区
发送数据则是在connfd可写时调用,源码如下:
#ifndef MUDUO_NET_TCPCONNECTION_H
#define MUDUO_NET_TCPCONNECTION_H
#include "Buffer.h"
#include "Callbacks.h"
#include "InetAddress.h"
#include <boost/any.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
namespace muduo
{
class Channel;
class EventLoop;
class Socket;
class TcpConnection : boost::noncopyable,
public boost::enable_shared_from_this<TcpConnection>
{
public:
TcpConnection(EventLoop* loop,
const std::string& name,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr);
~TcpConnection();
EventLoop* getLoop() const { return loop_; }
const std::string& name() const { return name_; }
const InetAddress& localAddress() { return localAddr_; }
const InetAddress& peerAddress() { return peerAddr_; }
bool connected() const { return state_ == kConnected; }
void send(const std::string& message);//对挖接口,调用sendInLoop函数,实现线程安全的发送数据
void shutdown();//对外接口,调用shutdownInLoop(),实现线程安全的关闭connfd的write
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
void setCloseCallback(const CloseCallback& cb)
{ closeCallback_ = cb; }
void connectEstablished();
void connectDestroyed();
private:
enum StateE { kConnecting, kConnected, kDisconnecting, kDisconnected, };
void setState(StateE s) { state_ = s; }
void handleRead(Timestamp receiveTime);
void handleWrite();//当connfd可写时,调用该函数,取出outputbuffer中的数据,进行发送
void handleClose();
void handleError();
void sendInLoop(const std::string& message);//发送数据
void shutdownInLoop();//关闭connfd的write
EventLoop* loop_;
std::string name_;
StateE state_;
boost::scoped_ptr<Socket> socket_;
boost::scoped_ptr<Channel> channel_;
InetAddress localAddr_;
InetAddress peerAddr_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
CloseCallback closeCallback_;
Buffer inputBuffer_;
Buffer outputBuffer_;//新增 outputbuffer
};
typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
}
#endif
成员函数如下:
#include "TcpConnection.h"
#include "logging/Logging.h"
#include "Channel.h"
#include "EventLoop.h"
#include "Socket.h"
#include "SocketsOps.h"
#include <boost/bind.hpp>
#include <errno.h>
#include <stdio.h>
using namespace muduo;
TcpConnection::TcpConnection(EventLoop* loop,
const std::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)
{
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback(
boost::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
boost::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
boost::bind(&TcpConnection::handleError, this));
}
TcpConnection::~TcpConnection()
{
LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this
<< " fd=" << channel_->fd();
}
void TcpConnection::send(const std::string& message)//对外接口,用于线程安全的发送数据
{
if (state_ == kConnected) {
if (loop_->isInLoopThread()) {
sendInLoop(message);
} else {
loop_->runInLoop(
boost::bind(&TcpConnection::sendInLoop, this, message));
}
}
}
void TcpConnection::sendInLoop(const std::string& message)//发送数据
{
loop_->assertInLoopThread();
ssize_t nwrote = 0;
//判断,buffer中没数据且Channel不关注可写事件时直接尝试写入message
if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) {
nwrote = ::write(channel_->fd(), message.data(), message.size());
if (nwrote >= 0) {
if (implicit_cast<size_t>(nwrote) < message.size()) {
LOG_TRACE << "I am going to write more data";
}
} else {
nwrote = 0;
if (errno != EWOULDBLOCK) {
LOG_SYSERR << "TcpConnection::sendInLoop";
}
}
}
assert(nwrote >= 0);
if (implicit_cast<size_t>(nwrote) < message.size()) {
outputBuffer_.append(message.data()+nwrote, message.size()-nwrote);//未发送完message,则将剩余的放入buffer,并时Channel重新开始关注可写事件
if (!channel_->isWriting()) {
channel_->enableWriting();
}
}
}
void TcpConnection::shutdown()//线程安全的关闭socket的write
{
if (state_ == kConnected)
{
setState(kDisconnecting);//设置状态为kDisconnecting
loop_->runInLoop(boost::bind(&TcpConnection::shutdownInLoop, this));
}
}
void TcpConnection::shutdownInLoop()
{
loop_->assertInLoopThread();
if (!channel_->isWriting())//如果当前Channel还是在关注可写事件,说明outputbuffer中还有数据未发送完,
//则暂且不执行socket_->shutdownWrite(),handlewrite函数将
//在发送完outputbuffer中数据后判断状态来执行 socket_->shutdownWrite();
{
socket_->shutdownWrite();
}
}
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->enableReading();
connectionCallback_(shared_from_this());
}
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
assert(state_ == kConnected || state_ == kDisconnecting);
setState(kDisconnected);
channel_->disableAll();
connectionCallback_(shared_from_this());
loop_->removeChannel(get_pointer(channel_));
}
void TcpConnection::handleRead(Timestamp receiveTime)
{
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0) {
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
} else if (n == 0) {
handleClose();
} else {
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
void TcpConnection::handleWrite()//当connfd可写时,调用该函数,取出outputbuffer中的数据,进行发送
{
loop_->assertInLoopThread();
if (channel_->isWriting()) {
ssize_t n = ::write(channel_->fd(),
outputBuffer_.peek(),
outputBuffer_.readableBytes());//写入数据
if (n > 0) {
outputBuffer_.retrieve(n);//根据写入数据的多少,对buffer的可读范围进行调整
if (outputBuffer_.readableBytes() == 0) {
channel_->disableWriting();//当outputbuffer没有可读内容时,关闭Channel对connfd的可写事件的关注
if (state_ == kDisconnecting) {
shutdownInLoop();//当TcpConnection为kDisconnecting状态时,关闭connfd的可写事件
}
} else {
LOG_TRACE << "I am going to write more data";
}
} else {
LOG_SYSERR << "TcpConnection::handleWrite";
}
} else {
LOG_TRACE << "Connection is down, no more writing";
}
}
void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "TcpConnection::handleClose state = " << state_;
assert(state_ == kConnected || state_ == kDisconnecting);
channel_->disableAll();
closeCallback_(shared_from_this());
}
void TcpConnection::handleError()
{
int err = sockets::getSocketError(channel_->fd());
LOG_ERROR << "TcpConnection::handleError [" << name_
<< "] - SO_ERROR = " << err << " " << strerror_tl(err);
}
数据堆积
如果发送数据时,对方的接受数据的速度与发送方的发送数据的速度不匹配,那么双方中的其中一方会出现数据堆积的情况,用户需要自己实现相应数据堆积时的回调函数(高水位回调),以及当前数据不出现堆积时的回调函数(低水位回调)。muduo中这两个函数名为:WriteCompleteCallback和HighWaterMarkCallback。
WriteCompleteCallback会在outputbuffer的可读内容为0时被调用,说明当前的发送内容全被发送完成,则相应的SendInloop和handleWrite涉及write行为的函数需要相应的更改(write完以后判断当前的buffer可读内容是否为0),而HighWaterMarkCallback则是只在涉及在对outputbuffer进行添加内容的处理,即只在sendInloop更改。
void TcpConnection::sendInLoop(const void* data, size_t len)
{
loop_->assertInLoopThread();
ssize_t nwrote = 0;
size_t remaining = len;
bool faultError = false;
if (state_ == kDisconnected)
{
LOG_WARN << "disconnected, give up writing";
return;
}
// if no thing in output queue, try writing directly
if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
{
nwrote = sockets::write(channel_->fd(), data, len);
if (nwrote >= 0)
{
remaining = len - nwrote;
if (remaining == 0 && writeCompleteCallback_)
{//缓冲区的内容被全部发送,调用低水位回调函数
loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));
}
}
else // nwrote < 0
{
nwrote = 0;
if (errno != EWOULDBLOCK)
{
LOG_SYSERR << "TcpConnection::sendInLoop";
if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
{
faultError = true;
}
}
}
}
assert(remaining <= len);
if (!faultError && remaining > 0)
{
size_t oldLen = outputBuffer_.readableBytes();
if (oldLen + remaining >= highWaterMark_
&& oldLen < highWaterMark_
&& highWaterMarkCallback_)
{//判断当前缓冲区是否过大,调用相应的highWaterMarkCallback_函数
loop_->queueInLoop(std::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
}
outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
if (!channel_->isWriting())
{
channel_->enableWriting();
}
}
}
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting()) {
ssize_t n = ::write(channel_->fd(),
outputBuffer_.peek(),
outputBuffer_.readableBytes());
if (n > 0) {
outputBuffer_.retrieve(n);
if (outputBuffer_.readableBytes() == 0) {
channel_->disableWriting();//buffer缓冲区的内容被全部发送完成,调用低水位回调函数
if (writeCompleteCallback_) {
loop_->queueInLoop(
boost::bind(writeCompleteCallback_, shared_from_this()));
}
if (state_ == kDisconnecting) {
shutdownInLoop();
}
} else {
LOG_TRACE << "I am going to write more data";
}
} else {
LOG_SYSERR << "TcpConnection::handleWrite";
}
} else {
LOG_TRACE << "Connection is down, no more writing";
}
}
至此基本的TCP网络库的基本框架完成,剩下的就是多线程和epoll的实现。
未完待续…