目录
TcpConnection::connectEstablished()
TcpConnection::connectDestroyed()
TcpConnection::shutdownInLoop()编辑
TcpConnection功能:
- TcpConnection主要负责连接,就是客户端和服务器之间建立的连接;
- TcpConnection表示的是通信连接成功,用户在服务端的一种数据的封装的表示;
- mainloop通过acceptor获取一个新用户的连接以后,就会将相应的socket、Channel装在TcpConnection,通过轮询算法交给subloop;
TCP connection是用在什么地方呢?
它主要就是用来打包呢,成功连接服务器的客户端的这么一条通信链路,
1.TCP server调用new connection,mainloop拿到连接成功的通信connfd,mainloop通过轮询选择出来的一个subloop。
2. 最终将connfd打包成TopConnection,将这个新的TcpConnection保存到TcpServer中的connections_哈希表中,并将connfd和它的Channel注册到subloop的Poller,也就是epoll上; (TcpServer:newConnection函数就是做这个事的)
* TcpServer => Acceptor => 有一个新用户连接,通过accept函数拿到connfd
* =》 TcpConnection 设置回调 =》 Channel =》 Poller =》
Channel的回调操作(Channel::handleEventWithGuard())
TcpConnection成员变量:
- Socket和Channel: TcpConnection成员变量包含Socket和Channel (Channel中的fd就是封装好的Socket)
- 回调函数:TcpConnection里面有用户预设置好的回调函数 (ConnectionCalback: 连接成功执行的回调; MessageCallback: 连接上有数据发生执行的回调; WriteCompleteCallback:服务端给客户端将数据send完之后的回调;HighWaterMarkCallback:高水位执行的回调;CloseCallback: 断开连接的回调)
- Buffer缓冲区
TcpConnection构造函数
①: 检查传入loop是否为空,注意这里TcpConnection都是在subloop中的;
②: 创建一个Socket;
③: 打包一个channel;
④:给channel设置的回调函数。
TcpConnection回调函数
TcpConnection::handleRead
①:把fd中数据端读到inputBuffer接收数据缓冲区中,成功,返回接收数据大小;
②: 设置成功读取数据的回调函数messageCallback,share_from_this返回当前TcpConnection对象的一个智能指针;
③:客户端断开连接回调;
④:错误回调。
TcpConnection::handleWrite
①:发送缓冲区通过fd发送数据;
②: 发送缓冲区中可读数据为0,说明发送完成;
③: 执行消息发送完后的回调;
④: 读完数据正在关闭状态,关闭写端
⑤: 调用handleWrite但是channel此时是不可写状态,打印日志
TcpConnection::handleClose
①: 设置连接状态为已经断开连接;
②: 把channel设置为不感兴趣,相当于从poller中退出;
③:执行连接关闭的回调;这个回调在连接成功和连接关闭都会响应;
④:专门另做了一个关闭连接的回调。实际上在④中也可以完成。
TcpConnection::handleError
::getsockopt() 是一个系统调用函数,用于获取指定套接字选项的值。
具体解释如下:
channel_->fd() 是一个套接字文件描述符,它表示要获取选项值的套接字。
SOL_SOCKET 是一个常量,表示选项所属的协议层,它指示 getsockopt() 获取的是套接字级别的选项。
SO_ERROR 是一个选项名,表示要获取的具体选项。
&optval 是一个指向变量的指针,用于接收获取到的选项值。
&optlen 是一个指向变量的指针,表示 optval 缓冲区的大小,也用于返回实际选项值的大小。
TcpConnection::send
用户会给TcpSever注册一个MessageCallback回调函数,就是已建立连接有读写事件的时候,MessageCalback会响应,MessageCallback处理完业务代码后,会通过这个send方法给客户端用户返回信息!
state_必须要是kConnected状态,必须是已连接状态才能使用send函数将数据放到发送缓冲区中。
①:判断一下该loop是不是在当前线程下,如果是,直接调用TcpConnection::sendInLoop函数发送数据;
②:如果不是,通过runInLoop找到loop_所在线程,并通过绑定函数bind将TcpConnection::sendInLoop绑定到loop中。
TcpConnection::sendInLoop
①:判断此时我们的channel不处于正在读的状态,且现在读缓冲区中没有数据,说明这是第一次开始写数据;
②: nwrote >= 0说明发送成功,nwrote 是发送成功的数据大小,更新remaining剩余还没有发送完的数据大小,如果为空说明发送完毕,调用发送完的回调函数writeCompleteCallback_;
③: nwrote < 0发送失败,nwrote 重置为0,因为nwrote代表已发送数据量,打印错误信息;
④: 表示没有发生错误但是remaining>0代表上一次数据还没发送完毕,要进行水位控制操作,
如果oldLen(已经在发送缓冲区中的数据部分)小于highWaterMark_(水位线),但是oldLen+remaining>highWaterMark_这个时候就要控制数据发送的速度了。这里主要是因为我们数据从应用到发送过来是非阻塞IO速度很快,但内核处理数据相对较满,需要控制两者速度一直才能更好的处理事件。
⑤: 把remaining剩余数据append添加到outputBuffer_之上。
TcpConnection::connectEstablished()
创建连接的时候使用
将TcpConnection的一个shared_ptr通过tie函数传过去,并使用一个weak_ptr接收,用一个弱智能指针绑定TcpConnection资源,监视TcpConnection资源作用!防止TcpConnection资源被释放;防止TcpConnection被remove掉之后,Channel底层调用的回调函数就没了!(强弱智能指针搭配使用,既可以监控对象的状态,还可以解决多线程访问共享对象的安全问题! )
TcpConnection::connectDestroyed()
TcpConnection::shutdown()
TcpConnection::shutdownInLoop()
TcpConnection.h
#pragma once
#include "noncopyable.h"
#include "InetAddress.h"
#include "Callbacks.h"
#include "Buffer.h"
#include "Timestamp.h"
#include <memory>
#include <string>
#include <atomic>
class Channel;
class EventLoop;
class Socket;
/**
* TcpServer => Acceptor => 有一个新用户连接,通过accept函数拿到connfd
* =》 TcpConnection 设置回调 =》 Channel =》 Poller =》 Channel的回调操作
*
*/
class TcpConnection : noncopyable, public std::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() const { return localAddr_; }
const InetAddress& peerAddress() const { return peerAddr_; }
bool connected() const { return state_ == kConnected; }//查看是否成功了
//发送数据
void send(const std::string &buf);
//关闭连接
void shutdown();
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
void setWriteCompleteCallback(const WriteCompleteCallback& cb)
{ writeCompleteCallback_ = cb; }
void setHighWaterMarkCallback(const HighWaterMarkCallback& cb, size_t highWaterMark)
{ highWaterMarkCallback_ = cb; highWaterMark_ = highWaterMark; }
void setCloseCallback(const CloseCallback& cb)
{ closeCallback_ = cb; }
//连接建立
void connectEstablished();
//连接销毁
void connectDestroyed();
private:
//连接状态 : 已经断开 正在连接 连接成功 正在断开
enum StateE {kDisconnected, kConnecting, kConnected, kDisconnecting};
void setState(StateE state) { state_ = state; }
void handleRead(Timestamp receiveTime);
void handleWrite();
void handleClose();
void handleError();
void sendInLoop(const void* message, size_t len);
void shutdownInLoop();
EventLoop *loop_;//这里绝对不是baseLoop, 因为TcpConnection都是在subLoop里面管理的
const std::string name_; //该连接名字
std::atomic_int state_; // 枚举 标识连接状态
bool reading_;
//这里和Acceptor类似 Acceptor=》mainLoop TcpConenction=》subLoop
std::unique_ptr<Socket> socket_;
std::unique_ptr<Channel> channel_;
const InetAddress localAddr_;//当前主机IP地址端口号
const InetAddress peerAddr_;//对端IP地址端口号
ConnectionCallback connectionCallback_;//有新连接时的回调
MessageCallback messageCallback_;//有读写消息时的回调
WriteCompleteCallback writeCompleteCallback_;//消息发送完成以后的回调
HighWaterMarkCallback highWaterMarkCallback_;//水位 控制发送数据的速度
CloseCallback closeCallback_;
size_t highWaterMark_;//水位标志
Buffer inputBuffer_;//接收数据的缓冲区
Buffer outputBuffer_;//发送数据的缓冲区
};
TcpConnection.cc
#include "TcpConnection.h"
#include "Logger.h"
#include "Socket.h"
#include "Channel.h"
#include "EventLoop.h"
#include <functional>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <string>
static EventLoop* CheckLoopNotNull(EventLoop *loop)
{
if (loop == nullptr)
{
LOG_FATAL("%s:%s:%d TcpConnection Loop is null! \n", __FILE__, __FUNCTION__, __LINE__);
}
return loop;
}
TcpConnection::TcpConnection(EventLoop *loop, //构造函数
const std::string &nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CheckLoopNotNull(loop))
, name_(nameArg)
, state_(kConnecting)
, reading_(true)
, socket_(new Socket(sockfd))
, channel_(new Channel(loop, sockfd))
, localAddr_(localAddr)
, peerAddr_(peerAddr)
, highWaterMark_(64*1024*1024) //超过64M就到水位线了,要停止发送
{
//下面给channel设置相应的回调函数,poller给channel通知感兴趣的事件发生了,channel会回调相应的操作函数
channel_->setReadCallback(
std::bind(&TcpConnection::handleRead, this, std::placeholders::_1)
);
channel_->setWriteCallback(
std::bind(&TcpConnection::handleWrite, this)
);
channel_->setCloseCallback(
std::bind(&TcpConnection::handleClose, this)
);
channel_->setErrorCallback(
std::bind(&TcpConnection::handleError, this)
);
LOG_INFO("TcpConnection::ctor[%s] at fd=%d\n", name_.c_str(), sockfd);
socket_->setKeepAlive(true);//启动Tcp Socket的保活机制
}
TcpConnection::~TcpConnection()//析构函数
{
LOG_INFO("TcpConnection::dtor[%s] at fd=%d state=%d \n",
name_.c_str(), channel_->fd(), (int)state_);
}
// onmessage处理完业务结束 通过send给客户端返回处理结果数据
// 对外都提供string类型的
void TcpConnection::send(const std::string &buf)
{
if (state_ == kConnected)//连接成功状态
{
if (loop_->isInLoopThread())//当前loop是不是在对应的线程
{
sendInLoop(buf.c_str(), buf.size());
}
else
{
loop_->runInLoop(std::bind(
&TcpConnection::sendInLoop,
this,
buf.c_str(),
buf.size()
));
}
}
}
/**
* 发送数据 应用因为是非阻塞IO它写的快, 而内核发送数据慢,需要两者速度匹配
* 需要把待发送数据写入缓冲区, 而且设置了水位回调
*/
void TcpConnection::sendInLoop(const void* data, size_t len)
{
ssize_t nwrote = 0; //已发送数据
size_t remaining = len; //还没发送的数据
bool faultError = false;
//之前调用过该connection的shutdown,不能再进行发送了
if (state_ == kDisconnected)
{
LOG_ERROR("disconnected, give up writing!");
return;
}
//表示channel_第一次开始写数据,而且缓冲区没有待发送数据
//(Channel向缓冲区写数据,给客户端应用响应)
if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
{
nwrote = ::write(channel_->fd(), data, len); //向发送缓冲区中 传入data
if (nwrote >= 0) //发送成功
{
remaining = len - nwrote; //剩余还没有发送完的数据
if (remaining == 0 && writeCompleteCallback_)
{
//既然在这里数据全部发送完成,就不用再给channel设置epollout事件了
loop_->queueInLoop(
std::bind(writeCompleteCallback_, shared_from_this())
);
}
}
else//nwrote < 0发送失败
{
nwrote = 0;
if (errno != EWOULDBLOCK)
{
LOG_ERROR("TcpConnection::sendInLoop");
if (errno == EPIPE || errno == ECONNRESET) // SIGPIPE RESET, 表示对端的socket的重置
{
faultError = true;
}
}
}
}
//说明当前这一次write,并没有把数据全部发送出去,剩余的数据需要保存到缓冲区当中,然后给channel
//注册epollout事件,poller发现tcp的发送缓冲区有空间,会通知相应的sock-channel,调用writeCallback_回调方法
//也就是调用TcpConnection::handleWrite方法,把发送缓冲区中的数据全部发送完成
if (!faultError && remaining > 0)
{
//目前发送缓冲区剩余的待发送数据的长度
size_t oldLen = outputBuffer_.readableBytes();
//发送缓冲区剩余数据 + 剩余还没发送的数据 >= 水位线
//且发送缓冲区剩余数据小于水位线 就原本是小于的加上这一些大于了
if (oldLen + remaining >= highWaterMark_
&& oldLen < highWaterMark_
&& highWaterMarkCallback_)
{
loop_->queueInLoop(
std::bind(highWaterMarkCallback_, shared_from_this(), oldLen+remaining)
);
}
//把待发送数据发送到outputBuffer缓冲区上
outputBuffer_.append((char*)data + nwrote, remaining);
if (!channel_->isWriting())
{
channel_->enableWriting();//这里一定要注册channel的写事件,否则poller不会给channel通知epollout
}
}
}
//关闭连接
void TcpConnection::shutdown()
{
if (state_ == kConnected)
{
setState(kDisconnecting); // 正在断开连接 不会正真的断开连接需要等待发送缓冲区为空
loop_->runInLoop(
std::bind(&TcpConnection::shutdownInLoop, this)
);
}
}
//回去底层调用handleClose方法
void TcpConnection::shutdownInLoop()
{
//调用了shutdown并不是真正断开连接 只是把状态设置为 kDisconnecting
//等待数据发送完,标志就是!channel_->isWriting() channel没在写了 调用shutdownWrite()彻底关闭
if (!channel_->isWriting())//说明outputBuffer中的数据已经全部发送完成
{
socket_->shutdownWrite();//关闭写端
}
}
//连接建立 创建连接
void TcpConnection::connectEstablished()
{
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading();//向poller注册channel的epollin事件
//新连接建立,执行回调
connectionCallback_(shared_from_this());
}
//连接销毁 连接关闭
void TcpConnection::connectDestroyed()
{
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll(); // 把channel的所有感兴趣的事件,从poller中del掉
connectionCallback_(shared_from_this());
}
channel_->remove();//把channel从poller中删除掉
}
//表示fd有数据可读
void TcpConnection::handleRead(Timestamp receiveTime)
{
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
//已建立连接的用户,有可读事件发生了,调用用户传入的回调操作onMessage
//shared_from_this:获取当前TcpConnection对象的一个智能指针
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0)//客户端断开连接
{
handleClose();
}
else
{
errno = savedErrno;
LOG_ERROR("TcpConnection::handleRead");
handleError();
}
}
//表示fd可写数据
void TcpConnection::handleWrite()
{
if (channel_->isWriting())
{
int savedErrno = 0;
ssize_t n = outputBuffer_.writeFd(channel_->fd(), &savedErrno);//发送数据
if (n > 0)
{
outputBuffer_.retrieve(n);
if (outputBuffer_.readableBytes() == 0) //发送完成
{
channel_->disableWriting();
if (writeCompleteCallback_)
{
//唤醒loop_对应的thread线程,执行回调
loop_->queueInLoop(
std::bind(writeCompleteCallback_, shared_from_this())
);
}
if (state_ == kDisconnecting) //读完数据正在断开状态(发送缓冲区为空且是正在断开连接状态)
{
shutdownInLoop(); //真正关闭连接
}
}
}
else
{
LOG_ERROR("TcpConnection::handleWrite");
}
}
else //调用handleWrite但是channel此时是不可写状态
{
LOG_ERROR("TcpConnection fd=%d is down, no more writing \n", channel_->fd());
}
}
//poller => channel::closeCallback => TcpConnection::handleClose
void TcpConnection::handleClose()
{
LOG_INFO("TcpConnection::handleClose fd=%d state=%d \n", channel_->fd(), (int)state_);
setState(kDisconnected);
channel_->disableAll();
TcpConnectionPtr connPtr(shared_from_this());
connectionCallback_(connPtr);//执行连接关闭的的回调(connectionCallback_回调函数 连接成功和关闭都会响应)
closeCallback_(connPtr);//关闭连接的回调 执行的是TcpServer::removeConnection回调方法
}
void TcpConnection::handleError()
{
int optval;
socklen_t optlen = sizeof optval;
int err = 0;
if (::getsockopt(channel_->fd(), SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
{
err = errno;
}
else
{
err = optval;
}
LOG_ERROR("TcpConnection::handleError name:%s - SO_ERROR:%d \n", name_.c_str(), err);
}