当前位置: 首页 > news >正文

TcpConnection的读写操作【深度剖析】

文章目录

  • 前言
  • 一、TcpConnection的读
  • 二、TcpConnection的写
  • 三、TcpConnection的关闭


前言

今天总结TcpConnection类的读写事件。

一、TcpConnection的读

当Poller检测到套接字的Channel处于可读状态时,会调用Channel的回调函数,回调函数中根据不同激活原因调用不同的函数,这些函数都由TcpConnection在创建Channel之初提供,当可读时,调用TcpConnection的可读函数handleRead,而在这个函数中,读缓冲区就会从内核的tcp缓冲区读取数据。


void TcpConnection::handleRead(Timestamp receiveTime)
{int savedErrno = 0;ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);if (n > 0){// 已建立连接的用户,有可读事件发生了,调用用户传入的回调操作onMessagemessageCallback_(shared_from_this(), &inputBuffer_, receiveTime);}else if (n == 0){handleClose();}else{errno = savedErrno;LOG_ERROR("TcpConnection::handleRead");handleError();}
}

TcpConnection::handleRead( )函数首先调用Buffer_.readFd(channel_->fd(), &saveErrno),该函数底层调用Linux的函数readv( ),将Tcp接收缓冲区数据拷贝到用户定义的缓冲区中(inputBuffer_)。如果在读取拷贝的过程中发生了什么错误,这个错误信息就会保存在savedErrno中。
对于缓冲区 Buffer::readFd()函数之前的文章已经剖析过了。

二、TcpConnection的写

TcpConnection::send() 方法是用户调用发送接口,会调用TcpConnection::sendInLoop() 方法来处理具体的发送操作。如果在当前线程直接发送,就会调用 sendInLoop() 方法处理,否则需要把发送任务加入到事件循环中,等待对应的线程处理。

//给用户提供的 发送接口
void TcpConnection::send(const std::string &buf)
{if (state_ == kConnected){if (loop_->isInLoopThread()){// 判断当前的线程 是不是在对应的线程里面// 有一些情况sendInLoop(buf.c_str(), buf.size());}else{loop_->runInLoop(std::bind(&TcpConnection::sendInLoop,this,buf.c_str(),buf.size()));}}
}

在TcpConnection::sendInLoop() 方法的实现中接通过系统调用 write() 发送数据,如果有剩余未发送的数据,则会将数据添加到发送缓冲区中,并注册 channel 的可写事件,等待事件循环通知空闲后再进行发送。


/*** 发送数据  应用写的快, 而内核发送数据慢, 需要把待发送数据写入缓冲区,*  而且设置了水位回调*/ 
void TcpConnection::sendInLoop(const void* data, size_t len)
{ssize_t nwrote = 0;// remaining是没发送完的数据size_t remaining = len;bool faultError = false;// 之前调用过该connection的shutdown,不能再进行发送了if (state_ == kDisconnected){LOG_ERROR("disconnected, give up writing!");return;}// 表示channel_第一次开始写数据,而且缓冲区没有待发送数据if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0){// 返回的是具体发送的 数据nwrote = ::write(channel_->fd(), data, len);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{faultError = true;}}}}// 说明当前这一次write,并没有把数据全部发送出去,// 剩余的数据需要保存到缓冲区当中,然后给channel// 注册epollout事件,poller发现tcp的发送缓冲区有空间,// 因为是lt模式  如果缓存区空余 就会不断地提醒// 会通知相应的sock-channel,调用writeCallback_回调方法也就是hanldwrite方法// 也就是调用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_.append((char*)data + nwrote, remaining);if (!channel_->isWriting()){// 这里一定要注册channel的写事件,否则poller不会给channel通知epolloutchannel_->enableWriting(); }}
}

发送缓冲区中有数据时,TcpConnection::handleWrite() 方法会被调用来处理具体的发送操作。在该方法中,首先会判断 channel 是否可写,如果可写则通过系统调用 writeFd() 将发送缓冲区中的数据写入到套接字中。如果写入成功,就会从发送缓冲区中删除已经发送的数据,并判断是否还有剩余数据,如果没有,则禁用 channel 的写事件,并执行可写回调函数。如果还有剩余数据,则会继续等待事件循环通知空闲后再次进行发送。


// 对outputBuffer_ 进行发送
void TcpConnection::handleWrite()
{if (channel_->isWriting()){int savedErrno = 0;ssize_t n = outputBuffer_.writeFd(channel_->fd(), &savedErrno);if (n > 0){// 有数据发送成功  n个数据已经处理过了 把readable 向右移outputBuffer_.retrieve(n);if (outputBuffer_.readableBytes() == 0){// 已经发送完成了 编程不可写   执行回调写完回调writeCompleteCallback_channel_->disableWriting();if (writeCompleteCallback_){// 唤醒loop_对应的thread线程,执行回调// 唤醒线程 执行写完之后的回调事件loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));}if (state_ == kDisconnecting){// 如果还有数据但是 就调用了 shutdown//   state_就变成了 == kDisconnecting// 但是 需要等待 数据传输完成 再调用shutdownInLoopshutdownInLoop();}}}else{LOG_ERROR("TcpConnection::handleWrite");}}else{LOG_ERROR("TcpConnection fd=%d is down, no more writing \n", channel_->fd());}
}

这里的细节问题就是如果想要关闭连接,那么通常是先关闭读端,等到将写缓冲区所有数据都写到tcp缓冲区后,再关闭写端,否则这些数据就不能发送给对端了

三、TcpConnection的关闭

需要关闭时候setState(kDisconnecting);把状态设置为kDisconnecting但是没有立即关闭,而是判断是否还有数据可写。

// 关闭连接
void TcpConnection::shutdown()
{if (state_ == kConnected){setState(kDisconnecting);loop_->runInLoop(std::bind(&TcpConnection::shutdownInLoop, this));}
}
void TcpConnection::shutdownInLoop()
{// 如果buffer还有数据,这个就是writing状态// 会一直发, 知道发完 然后监控到状态是kDisconnecting // 再次调用这个函数 ,就会关闭了// 保证数据发送完if (!channel_->isWriting()) // 说明outputBuffer中的数据已经全部发送完成{socket_->shutdownWrite(); // 关闭写端}
}

如果buffer还有数据,这个就是writing状态会一直被epoll提醒发送,直到发完 然后监控到状态是kDisconnecting 再次调用这个函数 ,就会关闭了
保证数据发送完。


// 对outputBuffer_ 进行发送
void TcpConnection::handleWrite()
{if (channel_->isWriting()){int savedErrno = 0;ssize_t n = outputBuffer_.writeFd(channel_->fd(), &savedErrno);if (n > 0){// 有数据发送成功  n个数据已经处理过了 把readable 向右移outputBuffer_.retrieve(n);if (outputBuffer_.readableBytes() == 0){// 已经发送完成了 编程不可写   执行回调写完回调writeCompleteCallback_channel_->disableWriting();if (writeCompleteCallback_){// 唤醒loop_对应的thread线程,执行回调// 唤醒线程 执行写完之后的回调事件loop_->queueInLoop(std::bind(writeCompleteCallback_, shared_from_this()));}if (state_ == kDisconnecting){// 如果还有数据但是 就调用了 shutdown//   state_就变成了 == kDisconnecting// 但是 需要等待 数据传输完成 再调用shutdownInLoopshutdownInLoop();}}}else{LOG_ERROR("TcpConnection::handleWrite");}}else{LOG_ERROR("TcpConnection fd=%d is down, no more writing \n", channel_->fd());}
}

相关文章:

TcpConnection的读写操作【深度剖析】

文章目录 前言一、TcpConnection的读二、TcpConnection的写三、TcpConnection的关闭 前言 今天总结TcpConnection类的读写事件。 一、TcpConnection的读 当Poller检测到套接字的Channel处于可读状态时&#xff0c;会调用Channel的回调函数&#xff0c;回调函数中根据不同激活…...

k8s面试题

1 简述etcd及其特点 ETCD是高可用分布式的键值存储系统 特点 1)强一致性:即使部分节点故障,etcd仍能正常工作,并保持数据一致 (强一致性:分布式系统中,更新操作,所有节点读取的数据都是最新的,一致的) 2)高可用:etcd支持数据多副本复制,一个节点挂,其他节点接…...

OpenCV 4.x 版本的新特性都有哪些?

文章大纲 V 4. 0DNNV 4.0 - 4. 8cheatsheetvideo analysis参考文献与学习路径2016年的时候我快研究生毕业了,那时候OpenCV 2.4.x 版本非常的流行,当时3.x 的版本刚发布,很多人都没有用习惯。 我写过一遍笔记: OpenCV 3.0 3.1版本的改进家里还有一本书:《OpenCV 3 计算机视…...

Redisson—分布式集合

7.1. 映射&#xff08;Map&#xff09; 基于Redis的Redisson的分布式映射结构的RMap Java对象实现了java.util.concurrent.ConcurrentMap接口和java.util.Map接口。与HashMap不同的是&#xff0c;RMap保持了元素的插入顺序。该对象的最大容量受Redis限制&#xff0c;最大元素数…...

93、Redis 之 使用连接池管理Redis6.0以上的连接 及 消息的订阅与发布

★ 使用连接池管理Redis连接 从Redis 6.0开始&#xff0c;Redis可支持使用多线程来接收、处理客户端命令&#xff0c;因此应用程序可使用连接池来管理Redis连接。 上一章讲的是创建单个连接来操作redis数据库&#xff0c;这次使用连接池来操作redis数据库 Lettuce连接池 支持…...

doris动态分区开启历史分区

举例说明&#xff1a; CREATE TABLE tbl1 (k1 DATE,... ) PARTITION BY RANGE(k1) () DISTRIBUTED BY HASH(k1) PROPERTIES ("dynamic_partition.enable" "true","dynamic_partition.time_unit" "DAY","dynamic_partition.sta…...

Linux用户与权限(认知root用户、修改权限控制 - chmod、修改权限控制 - chown)

目录 1. 认知root用户 1.1 什么是root用户&#xff08;超级管理员&#xff09; 1.2 用户切换命令 1.3 sudo命令 1.3.1 为普通用户配置sudo认证 2. 用户、用户组管理 2.1 理解用户、用户组的概念 2.2 掌握用户、用户组管理的相关命令 2.2.1 用户组管理 2.2.2 …...

处理conda安装工具的动态库问题——解决记录 libssl.1.0.0 系统中所有openssl位置全览 whereis openssl

处理conda安装工具的动态库问题——解决记录 处理conda安装工具的动态库问题——解决记录 - 简书 解决libssl.so.1.0.0: cannot open shared object file: No such file or directory问题 - 简书 openssl 默认版本问题&#xff08;Anaconda相关&#xff09;_anaconda openssl-…...

如何在Go中格式化字符串

由于字符串通常由书面文本组成&#xff0c;在很多情况下&#xff0c;我们可能希望通过标点符号、换行和缩进来更好地控制字符串的外观&#xff0c;以使其更易于阅读。 在本教程中&#xff0c;我们将介绍一些处理go字符串的方法&#xff0c;以确保所有输出文本的格式正确。 字…...

C程序设计内容与例题讲解 -- 第四章--选择结构程序设计第二部分(第五版)谭浩强

前言:在前面我们学习了选择结构和条件判断&#xff0c;用if语句实现选择结构&#xff0c;关系运算符和关系表达式&#xff0c;逻辑运算符和逻辑表达式等知识。今天我们将接着上一篇未讲完的继续讲解。 鸡汤:种一棵树最好的时间是十年前&#xff0c;其次是现在&#xff01;加油各…...

接雨水问题

接雨水问题 问题背景 LeetCode 42. 接雨水 接雨水问题是一个经典的计算雨水滞留量的问题&#xff0c;通常使用柱状图来表示不同高度的柱子。在下雨的情况下&#xff0c;柱子之间的凹陷部分能够存储雨水&#xff0c;问题的目标是计算这些柱子所能接收的雨水总量。 相关知识 …...

小谈设计模式(9)—工厂方法模式

小谈设计模式&#xff08;9&#xff09;—工厂方法模式 专栏介绍专栏地址专栏介绍 工厂方法模式角色分类抽象产品&#xff08;Abstract Product&#xff09;具体产品&#xff08;Concrete Product&#xff09;抽象工厂&#xff08;Abstract Factory&#xff09;具体工厂&#x…...

Android etc1tool之png图片转换pkm 和 zipalign简介

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、etc1tool2.1、用法 三、zipalign3.1 使用 四…...

Spring Boot快速入门:构建简单的Web应用

SpringBoot Spring Boot是一个用于简化Spring应用程序开发的框架&#xff0c;它通过提供开箱即用的配置和一组常用的功能&#xff0c;使得构建高效、可维护的应用变得非常容易。在本篇博客中&#xff0c;我们将一步步地介绍如何快速入门Spring Boot&#xff0c;并构建一个简单的…...

JAVA 泛型、序列化和复制

泛型提供了编译时类型安全检测机制&#xff0c;该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型&#xff0c;也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法&#xff0c;能够对整型数组、字符串数组甚至其他任何类型的数组进行排序&a…...

以太网基础学习(二)——ARP协议

一、什么是MAC地址 MAC地址&#xff08;英语&#xff1a;Media Access Control Address&#xff09;&#xff0c;直译为媒体访问控制位址&#xff0c;也称为局域网地址&#xff08;LAN Address&#xff09;&#xff0c;MAC位址&#xff0c;以太网地址&#xff08;Ethernet Addr…...

【Java-LangChain:使用 ChatGPT API 搭建系统-4】评估输入-分类

第三章&#xff0c;评估输入-分类 如果您正在构建一个允许用户输入信息的系统&#xff0c;首先要确保人们在负责任地使用系统&#xff0c;以及他们没有试图以某种方式滥用系统&#xff0c;这是非常重要的。 在本章中&#xff0c;我们将介绍几种策略来实现这一目标。 我们将学习…...

嵌入式Linux应用开发-驱动大全-第一章同步与互斥③

嵌入式Linux应用开发-驱动大全-第一章同步与互斥③ 第一章 同步与互斥③1.4 Linux锁的介绍与使用1.4.1 锁的类型1.4.1.1 自旋锁1.4.1.2 睡眠锁 1.4.2 锁的内核函数1.4.2.1 自旋锁1.4.2.2 信号量1.4.2.3 互斥量1.4.2.4 semaphore和 mutex的区别 1.4.3 何时用何种锁1.4.4 内核抢占…...

树的存储结构以及树,二叉树,森林之间的转换

目录 1.双亲表示法 2.孩子链表 3.孩子兄弟表示法 4.树与二叉树的转换 &#xff08;1&#xff09;树转换为二叉树 &#xff08;2&#xff09;二叉树转换成树 5.二叉树与森林的转化 &#xff08;1&#xff09;森林转换为二叉树 以下树为例 1.双亲表示法 双亲表示法定义了…...

【AI视野·今日NLP 自然语言处理论文速览 第四十二期】Wed, 27 Sep 2023

AI视野今日CS.NLP 自然语言处理论文速览 Wed, 27 Sep 2023 Totally 50 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Attention Satisfies: A Constraint-Satisfaction Lens on Factual Errors of Language Models Authors Mert …...

华为云云耀云服务器L实例评测|部署个人在线电子书库 calibre

华为云云耀云服务器L实例评测&#xff5c;部署个人在线电子书库 calibre 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 应用场景1.3 支持镜像 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 calibre3.1 calibre 介绍3.2 Docker 环境搭建3.3 c…...

代码随想录刷题 Day28

216.组合总和III 和前一个题一样&#xff0c;照着自己就能写出来&#xff0c;就多了一个判断结果是不是等于n的逻辑。有两个地方可以剪纸&#xff0c;一个是当和已经大于要找的时候直接返回&#xff0c;另一个是当剩余元素少于三个的时候直接返回&#xff08;第一层递归是少于…...

【生命周期】

生命周期 1 引出生命周期2 分析生命周期3 总结生命周期 1 引出生命周期 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta …...

【C语言 模拟实现memcpy函数、memcpy函数】

C语言程序设计笔记---027 C语言之模拟实现memcpy函数、memcpy函数1、介绍memcpy函数1.1、模拟实现memcpy函数 2、介绍memmove函数2.1、模拟实现memmove函数 3、结语 C语言之模拟实现memcpy函数、memcpy函数 前言&#xff1a; 通过C语言内存函数的知识&#xff0c;这篇将对memc…...

opencv视频文件的读取,处理与保存

文章目录 opencv视频文件的读取&#xff0c;处理与保存一、视频文件的读取&#xff1a;1、cv::VideoCapture是OpenCV库中用于处理视频输入的类&#xff0c;它提供了一种简单的方法来从摄像头&#xff0c;视频文件、或图像序列中读取帧&#xff1b;&#xff08;1&#xff09;打开…...

java - 七大比较排序 - 详解

前言 本篇介绍了七大比较排序&#xff0c;直接插入排序&#xff0c;希尔排序&#xff0c;冒泡排序&#xff0c;堆排序&#xff0c;选择排序&#xff0c;快速排序&#xff0c;归并排序&#xff0c;一些简单思想代码实现&#xff0c;如有错误&#xff0c;请在评论区指正&#xf…...

项目集成七牛云存储sdk

以PHP为例 第一步&#xff1a;下载sdk PHP SDK_SDK 下载_对象存储 - 七牛开发者中心 sdk下载成功之后&#xff0c;将sdk放入项目中&#xff0c;目录选择以自己项目实际情况而定。 注意&#xff1a;在examples目录中有各种上传文件的参考示例&#xff0c;这里我们主要参考的是…...

docker-compose一键启动neo4j

下载镜像 docker pull neo4j:3.5.22-community 编写配置文件 参考文档 编写docker-compose.yml文件 version: "3"services:neo4j:image: neo4j:3.5.22-communitycontainer_name: neo4j restart: alwaysports:- 7474:7474- 7687:7687environment:- NEO4J_AUTH:ne…...

深入剖析@ConfigurationProperties注解

当我们构建Spring Boot应用程序时&#xff0c;配置属性通常是不可或缺的一部分。Spring Boot提供了多种方式来管理这些属性&#xff0c;其中之一是使用ConfigurationProperties注解。这篇博客将详细解释ConfigurationProperties注解以及如何使用它来管理和映射配置属性。 什么…...

北京开发APP需要多少钱

北京开发一个移动应用&#xff08;APP&#xff09;的费用因多种因素而异&#xff0c;包括项目的规模、复杂性、所需功能、设计要求、技术选择、开发团队的经验和地理位置等。一般来说&#xff0c;北京的APP开发费用通常较高&#xff0c;因为这是中国的主要技术和创新中心之一&a…...

广州网站建设比较好的公司/企业建站系统模板

Django rest framework 源码分析 ----认证 一、基础 django 2.0官方文档 1 https://docs.djangoproject.com/en/2.0/ 安装 pip3 install djangorestframework 假如我们想实现用户必须是登陆后才能访问的需求&#xff0c;利用restframework该如何的去实现&#xff0c;具体的…...

网站建设 康盛设计/媒体软文发稿

一、避免或简化排序 应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时&#xff0c;优化器就避免了排序的步骤。以下是一些影响因素&#xff1a;1.索引中不包括一个或几个待排序的列。2.GROUP BY或ORDER BY子句中列的次序与索引的次序不一样。…...

经营网站挣钱/seo公司排名教程

使用宝塔面板安装mysqlLinux基本内容&#xff0c;里面有涉及到安装Mysql修改密码而且也要在数据库的菜单中设置root密码修改后密码后进行登录&#xff0c;就不会出现下面的报错了[rootcentos7 ~]# mysql -u root -pEnter password:ERROR 1045 (28000): Access denied for user …...

营销型网站建设需要懂什么/新的营销方式有哪些

ERP-ORACLE-EBS-AP预付款管理--操作AP预付款管理--操作预付款发票录入&#xff1a;预付款的录入和标准发票一样&#xff0c;只是在类型时选择“预付款”&#xff1b;对于预付款类型的发票&#xff0c;实际支付了多少预付款就录入多少&#xff0c;因为预付款必须要全额支付才能核…...

年栾洪全单页做网站教程/东莞推广系统

1.返回第一页的10条 select t2.* from (select t1.* from youtable t1 ) t2where rownum<返回的条数2.返回非第一页的10条select t2.* from (select rownum rn,t1.* from youtable t1 where rownum<大数) t2where rn>小数如果想要返回10到20条&#xff0c;则大数为20&…...

怎么用vs2015做网站/杭州seo网站建设

count 最简单的聚合工具&#xff0c;返回集合中的文档数量&#xff0c;速度非常快。 >db.coll.count() 计算查询条件为name是xiaozhe的总数&#xff0c;有条件的查询速度会变慢。 >db.coll.count({"name":"xiaozhe"}) distinct 用来找出给定键的所有…...