9.添加Connection类

 在第7节中,我们知道,一个简单的服务器端程序可由两部分构成,一部分是用accept(2)函数来与客户端进行连接,另一部分是与已连接的客户端(即是TCP连接)进行服务处理等等。那么,这一节我们分离出与已连接的客户端(即是TCP连接)进行服务处理的这部分,可以添加一个类,叫做Connection。 其内容基本是按照muduo中的TcpConnection类来设计。

首先看看这一节,我们所使用的服务端的代码例子。

#include"src/Server.h"
#include<stdio.h>

void onMessage(const ConnectionPtr& conn, Buffer* buf) {
	std::string msg(buf->retrieveAllAsString());
	printf("onMessage() %ld bytes reveived:%s\n", msg.size(), msg.c_str());

	conn->send(msg);  // 回送接受的数据
}

int main()
{
	InetAddr servAddr(10000);
	EventLoop loop;
	Server server(servAddr, &loop);
   
    //比上一节多了这句代码和上面的onMessage函数
	server.setMessageCallback([=](const ConnectionPtr& conn, Buffer* buf) {onMessage(conn, buf); });
	loop.loop();

	return 0;
}

在这节中Server类中多了函数setMessageCallback(),这是设置业务逻辑函数,即是可以自定义业务逻辑。这里还是回显服务器的业务逻辑,也可以用户自定定义其他的业务逻辑。我们需要记住这个函数。

onMessage函数中的ConnectionPtr是std::shared_ptr<Connection>的别名。

Connection class

class Connection:public std::enable_shared_from_this<Connection>
{
public:
	enum class StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
public:
	Connection(EventLoop* loop,int sockfd);

	void setMessageCallback(const MessageCallback& cb)
	{
		messageCallback_ = cb;
	}
	void setCloseCallback(const CloseCallback& cb)
	{
		closeCallback_ = cb;
	}

	void setState(StateE state) { state_ = state; }
	void send(Buffer* message);
	void send(const std::string& messgage);

	void connectEstablished();

	void connectDestroyed();


	int fd()const { return socket_->fd(); }
private:

	void handleRead();
	void handleWrite();
	void handleClose();
private:
	EventLoop* loop_;

	StateE state_;  //连接的状态

	std::unique_ptr<Socket> socket_;
	std::unique_ptr<Channel> channel_;

	MessageCallback messageCallback_;
	CloseCallback closeCallback_;

	Buffer inputBuffer_;
	Buffer outputBuffer_;
};

using ConnectionPtr = std::shared_ptr<Connection>;

class Connection继承的类先不解释了,之后会说。

先说说 TCP连接的4个连接状态吧。

enum class States { kDisconnected, kConnecting, kConnected, kDisconnecting };

分别代表:已经断开、初始状态、已连接、正在断开。

Connection是一个TCP连接,必然会有sockfd,那就会有封装了fd的Socket类对象,这里使用智能指针std::unique_ptr管理,std::unique_ptr<Socket>。

那也必然会有一个Channel,也是通过Channel来获得socket上的IO事件。

还有两个缓冲区:输入缓冲区,输出缓冲区,使用Buffer类。

还有两个回调函数messageCallback_,closeCallback_。

这两个回调先按下不表,后面会细说。

1.Connection构造函数

Connection::Connection(EventLoop* loop, int sockfd)
	:loop_(loop)
	,state_(StateE::kConnecting)
	,socket_(std::make_unique<Socket>(sockfd))
	,channel_(std::make_unique<Channel>(loop,sockfd))
{
	//channel_设置好 读,写,的回调函数,当发生读或写事件时,就调用对应的事件
	channel_->SetReadCallback([this]() {handleRead(); });
	channel_->setWriteCallback([this]() {handleWrite(); });
}

到现在了,应该是熟悉这些设置回调函数的操作了的。

之前说了服务器主要分成两大部分嘛,所以Acceptor和Connection是同一等级的。那来看看会在什么时刻生成Connection类对象。

2.Connection类对象的创建时刻

先看看Server类中如何管理Connection的。

class Server
{
public:
    //......还有其他的,省略
    using MessageCallback=std::function<void()>;

    using ConnectionPtr=std::shared_ptr<Connection>;
    using connectionMap=std::unordered_map<int,ConnectionPtr>;//key是该连接的sockfd,value是该连接的Connection的智能指针
    void newConnection(int sockfd);
    void setMessageCallback(MessageCallback& cb);

private:
    EventLoop* loop_;
    std::unique_ptr<Acceptor> acceptor_;
    connectionMap connetions_;
    MessageCallback messageCallback_;
}

Server中是使用std::unordered_map管理该Connection的智能指针。

先要回想下第七节中Acceptor在Server构造函数中设置的newConnection(),这是绑定了Server::newConnection(int sockfd)。

所以是当accpetor_可读时候,即是有新客户端想建立连接时,其就会调用Accpetor::handleRead(),该函数内部会调用Server::newConnection(int sockfd)函数。

void Server::newConnection(int sockfd)
{
	setNonblock(sockfd);

	auto conn = std::make_shared<Connection>(loop_, sockfd);
	connections_[sockfd] = conn;

	conn->setMessageCallback(messageCallback_);    //设置其业务处理回调函数
	conn->setCloseCallback([this](const ConnectionPtr& connection) {removeConnection(connection); });
	conn->connectEstablished();	//建立连接,这个函数目前和channel->enableReading()是差不多的


    //这是之前的,可以对比学习
	//Channel* channel = new Channel(loop_, sockfd);	//这版本没有delete,会内存泄漏,之后版本会修改
	//auto cb = [this,channel]() {handleEvent(channel); };
	//channel->SetReadCallback(cb);	//设置回调
	//channel->enableReading();
}

//connectEstablished函数的实现
void Connection::connectEstablished()
{
	assert(state_ == StateE::kConnecting);
	setState(StateE::kConnected);
	channel_->enableReading();
}

 创建了Connection对象后,设置其业务处理回调函数和关闭连接回调函数,并让其Channel可读。

业务处理的回调函数关闭连接的回调函数比较重要,后面会细说

3.创建Connection的相关函数的调用顺序图

 现在有了Connection类,来看看创建出Connection的相关函数的调用顺序图,这省略了epoll的调用(即是epoll_wait())。其中Channel::handleEvent()的触发条件是listening socket可读,表示有新连接到来。Server会为新连接创建对应的Connection对象。

4.messageCallback_的设置流程。

messageCallback_是用户设置的业务逻辑函数,是通过最外层Server类调用Server::setMessageCallback()来设置绑定给Server::messageCallback_,接着Connection类再把Server::messageCallback_通过Connection::setMessageCallback()设置为自己的messageCallback_。

那可能有疑惑了哈,为什么不就Connection对象一步到位,直接调用Connection::setMessageCallback()设置给自己呢,为什么需要先通过Server设置呢???

这是因为Connecton类对于用户来说是看不见的,用户是不知道Connection类的。对外使用的是Server。你再看看使用例子,是不是没有Connecton类供你对外使用(除了那onMessage()函数)。所以就需要通过Server类来间接设置,接着内部的Connection类才能设置。

可能说的有点别捏,要结合上面的代码看才好理解。

5. handleRead()函数

void Connection::handleRead()
{
	int savedErrno = 0;
	auto n = inputBuffer_.readFd(fd(), &savedErrno);
	if (n > 0) {
		messageCallback_(shared_from_this(), &inputBuffer_);	//这个是用户设置好的函数
	    
        /*
        可以把messageCallback_(shared_from_this(), &inputBuffer_)当做下面的onMessage()函数
        void onMessage(const ConnectionPtr& conn, Buffer* buf) {
	        std::string msg(buf->retrieveAllAsString());
	        printf("onMessage() %ld bytes reveived:%s\n", msg.size(), msg.c_str());

	        conn->send(msg);  // 回送接受的数据
        }*/
    }
	else if (n == 0) {
		handleClose();    //表示客户端关闭了连接
	}
	else {
		printf("readFd  error\n");    		//表示出错了,现在不处理
	}
}

当有读事件发生了,就回调handleRead()函数,其内先经过成员变量inputBuffer_调用readFd()函数读取数据。

若读取数据长度大于0,就调用Connection设置好的messageCallback_函数。若长度为0则表明客户端断开了连接,那就调用handlClose()函数。

6.Connection::handleClose()函数

其内部是调用了Server::removeConnection()函数。

void Connection::handleClose()
{
	setState(StateE::kDisconnected);
	channel_->disableAll();

	ConnectionPtr guardThis(shared_from_this());    //shared_from_this()先按下不表
	closeCallback_(guardThis);	//closeCallback_就是Server::removeConnection()函数
}

void Server::removeConnection(const ConnectionPtr& conn)
{
	auto n = connections_.erase(conn->fd());	//connections_是Server类的成员
	conn->connectDestroyed();
}

这里可能会有疑惑,为什么不能一步到位,直接就在Connection类中调用函数关闭呢,为什么还需要使用到Server类的成员函数呢??

因为是Server类管理着Connection连接,要删除Connection对象需要在Server中进行部分处理。也就是说是Server类管理着Connection。那要删除Connection对象,就要经过Server这个管理者去删除嘛, 所以才需要有设置回调,才会有使用到Server类的函数

其回调中也有使用了其Connection类自己的成员函数connectDestroyed。

void Connection::connectDestroyed()
{
	if (state_ == StateE::kConnected) {
		setState(StateE::kDisconnected);
		channel_->disableAll();    //注销读写事件,所以什么事件都不往epoll中注册
	}

	channel_->remove();    //在epoll中移走其channel,即是使用EPOLL_CTL_DEL删除其channel对应的sockfd
}

 Connection::connectDestroyed()先判断连接的状态是否是已连接,若是就执行以上的操作,并最后移走其channel。

7.触发写事件的时刻

说了这么多,那还剩下epoll是如何去触发EPOLLOUT的,即是服务器端是会在什么时候设置EPOLLOUT的。这里就要看看服务端的主动发送数据函数Connection::send()。

void Connection::send(const std::string& message)
{
	if (state_ == StateE::kDisconnected)
		return;

	bool faultError = false;
	ssize_t nwrote = 0;
	size_t reamining = message.size();
	//如果当前channel没有写事件发生,并且发送缓冲区无待发送的数据,那就可以直接发送
	if (!channel_->isWrite() && outputBuffer_.readableBytes() == 0) {
		nwrote = ::write(fd(), message.data(), message.size());
		if (nwrote >= 0) {
			reamining = message.size() - nwrote;
			if (reamining == 0) {
				//表示数据已完全发送出去,没有操作,之后可以设置数据发送完毕,可以通知用户的事件
			}
		}
		else {	//nwrote<0
			nwrote = 0;
			if (errno != EWOULDBLOCK) {
				if (errno == EPIPE || errno == ECONNRESET) {
					faultError = true;
				}
			}
		}
	}

	assert(reamining <= message.size());
	if (!faultError && reamining > 0) {    //表明数据还没有完全发送完毕
		outputBuffer_.append(message.data() + nwrote, reamining);
		if (!channel_->isWrite()) {
			channel_->enableWriting();    //设置可写,注册EPOLLOUT事件
		}
	}
}

若当前channel没有写事件发生,并且发送缓冲区中无待发送的数据,那就可以直接调用::write()函数去发送数据。若write()发送的f返回的数据长度是小于要发送的数据的长度,即是表明数据还没有完全发送完毕,这时就把剩余未发送的数据append()到输出Buffer(outputBuffer_)中,接着设置其channel可写,即是注册EPOOLOUT事件。这样用户就无需关注数据要发送多少次才能完全发送完毕,只要是调用了Connection::send()就一定会发送完毕

那接着下一个疑问就出来了,那要是我所有的数据都发送完毕了,那EPOLLOUT事件还在触发,就会一直busy loop

我们在Connection构造函数是设置了写事件的,当socket变得可写时候,channel会调用Connection::handleWrite()函数,在其函数内,我们会继续发送outputBuffer_中的数据。当数据发送完毕,即是outputBuffer_.readableBytes() == 0,就立即停止观察writeable事件,这样就可以避免busy loop。

void Connection::handleWrite()
{
	if (!channel_->isWrite()) {
		printf("Connection fd = % d is down, no more writing\n", channel_->Fd());
		return;
	}

	auto n = ::write(fd(), outputBuffer_.peek(), outputBuffer_.readableBytes());
	if (n > 0) {
		//更新readerIndex
		outputBuffer_.retrieve(n);
		if (outputBuffer_.readableBytes() == 0) {//表明要发送的数据已全部发送完毕,所以取消写事件
			channel_->disableWriting();    //取消写事件
		}
		else {
			printf("read to write more data\n");
		}
	}
	else {
		printf("handleWrite error\n");
	}
}

8. Connection断开连接的相关函数调用顺序图

目前只有被动关闭这种方式,即是对方先关闭连接,本地read(2)返回0,触发了关闭逻辑。

现在的Connection类已是基本成型,这一节的程序是可以使用的了。还有Channel类中会有些变化,如添加了enableWriting()等等,还有Connection继承std::enable_shared_from_this<Connection>等等问题,会在下一节进行讲解。

完整源代码:https://github.com/liwook/CPPServer/tree/main/code/server_v9

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值