TcpServer类的主要功能是管理accept(2)获得的TcpConnection,TcpConnection则表示的是“一次TCP连接”,一旦连接断开,这个TcpConnection对象就没啥用了。
由于TcpConnection类较复杂,本篇我们先学习TcpConnection类关于连接建立的处理。
当有新连接到来时,TcpServer新建连接的相关函数的调用时序图如下:
分析:当一个连接到来,EventLoop的事件循环loop()函数返回一个活跃的通道Channel,然后调用Channel的handleEvent()函数来处理这个事件。连接到来属于可读事件,又会回调Acceptor的handleRead()函数,在handleRead()函数中又调用accept()来接受这个新的连接,然后又回调了TcpServer的newConnection()函数。newConnection()函数会创建一个TcpConnection对象,然后这个对象调用TcpConnection的成员函数connectEstablished(),在这个函数中,回调了用户设置的连接到来回调函数connCb()。
TcpServer类
TcpServer类的接口简单易懂,如下:
代码片段1:TcpServer的接口
文件名:TcpServer.h
class TcpServer : boost::noncopyable
{
public:
// 构造函数,InetAddress是对网际地址sockaddr_in的封装
TcpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& nameArg);
// 析构函数
~TcpServer();
const string& hostport() const { return hostport_; }
const string& name() const { return name_; }
void start(); // 启动TcpServer
// 设置连接到来或者连接关闭回调函数
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
// 设置消息到来回调函数
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
private:
// 此函数会创建TcpConnection对象,下面会着重分析
void newConnection(int sockfd, const InetAddress& peerAddr);
// 连接列表
// key为TcpConnection的名字,value是指向TcpConnectinon对象的指针
typedef std::map<string, TcpConnectionPtr> ConnectionMap;
EventLoop* loop_;
const string hostport_; // 服务端口
const string name_; // 服务名
boost::scoped_ptr<Acceptor> acceptor_; // Acceptor中有对accept()的封装,获得新的连接
// 用户提供的ConnectionCallback和MessageCallback
// 在新建TcpConnection时会原样传给后者
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
bool started_; // TcpServer是否启动
int nextConnId_; // 下一个连接ID
ConnectionMap connections_; // 连接列表
};
代码片段2:TcpServer::newConnection()
文件名:TcpServer.cc
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
char buf[32];
snprintf(buf, sizeof buf, ":%s#%d", hostport_.c_str(), nextConnId_);
++nextConnId_;
// 连接名称以 服务名+服务端口+连接ID 格式命名
string connName = name_ + buf;
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toIpPort();
InetAddress localAddr(sockets::getLocalAddr(sockfd));
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary,此处使用make_shared()可以节约一次new
// 创建TcpConnection对象
TcpConnectionPtr conn(new TcpConnection(loop_,
connName,
sockfd,
localAddr,
peerAddr));
// 把TcpConnection对象加入ConnectionMap
connections_[connName] = conn;
// 设置ConnectionCallback和MessageCallback
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
// 此函数会回调用户提供的ConnectionCallback
conn->connectEstablished();
}
TcpConnection类
本篇暂只讨论TcpConnection类关于连接建立的处理,接口也很简单:
代码片段3:TcpConnection的接口
文件名:TcpConnection.h
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 string& name,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr);
// 析构函数
~TcpConnection();
EventLoop* getLoop() const { return loop_; }
const 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; }
// called when TcpServer accepts a new connection
void connectEstablished(); // should be called only once
private:
enum StateE { kConnecting, kConnected }; // 连接状态
void handleRead(Timestamp receiveTime);
void setState(StateE s) { state_ = s; } // 设置连接状态
EventLoop* loop_; // 所属EventLoop
string name_; // 连接名
StateE state_; // FIXME: use atomic variable
// we don't expose those classes to client.
boost::scoped_ptr<Socket> socket_;
boost::scoped_ptr<Channel> channel_;
InetAddress localAddr_; // 本地地址
InetAddress peerAddr_; // 对等方地址
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
};
代码片段4:TcpConnection.cc(处理连接建立事件)
文件名:TcpConnection.cc
#include <muduo/net/TcpConnection.h>
#include <muduo/base/Logging.h>
#include <muduo/net/Channel.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/Socket.h>
#include <muduo/net/SocketsOps.h>
#include <boost/bind.hpp>
#include <errno.h>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
// 构造函数
// TcpConnection没有发起连接的功能
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)
{
// 通道可读事件到来的时候,回调TcpConnection::handleRead,_1是事件发生时间
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true); // 设置SO_KEEPALIVE选项
}
// 析构函数
TcpConnection::~TcpConnection()
{
LOG_DEBUG << "TcpConnection::dtor[" << name_ << "] at " << this
<< " fd=" << channel_->fd();
}
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
// 断言连接状态为正在建立连接
assert(state_ == kConnecting);
// 设置连接状态为已连接
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading(); // 将TcpConnection所对应的通道加入到Poller关注
// 回调用户设置的函数
connectionCallback_(shared_from_this());
}
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
char buf[65536];
// 调用read将消息读到buf中
ssize_t n = ::read(channel_->fd(), buf, sizeof buf);
messageCallback_(shared_from_this(), buf, n);
}
示例
测试程序:
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
// 连接建立回调函数
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
printf("onConnection(): new connection [%s] from %s\n",
conn->name().c_str(),
conn->peerAddress().toIpPort().c_str());
}
else
{
printf("onConnection(): connection [%s] is down\n",
conn->name().c_str());
}
}
// 消息到来回调函数
void onMessage(const TcpConnectionPtr& conn,
const char* data,
ssize_t len)
{
printf("onMessage(): received %zd bytes from connection [%s]\n",
len, conn->name().c_str());
}
int main()
{
printf("main(): pid = %d\n", getpid());
InetAddress listenAddr(8888); // 构造地址INADDR_ANY,端口号为8888
EventLoop loop; // 构造一个EventLoop对象
TcpServer server(&loop, listenAddr, "TestServer"); // 构造一个TcpServer对象,传入EventLoop对象、地址和服务名
// 设置回调函数
server.setConnectionCallback(onConnection);
server.setMessageCallback(onMessage);
// 启动TcpServer
server.start();
// 启动事件循环
loop.loop();
}
测试结果:
1.启动我们的测试服务
2.客户端使用telnet发起连接
3.TcpServer接受新连接,处理连接到来事件
4.断开客户端连接,由于还未实现对连接断开事件的处理,服务端会陷入busy loop状态