00c(00c集团创始人)

00后小伙确诊癌晚期!刚参加工作,没父母约束“放飞”了

大部分人都会认为,直肠癌是老年病,只有上了年纪才会得。然而,近年来直肠癌却渐趋年轻化,越来越多年轻人也患上了直肠癌。

最近,杭州00后小伙小佳(化名)被确诊为“直肠癌晚期”,年仅23岁。接诊的杭州市第一人民医院肿瘤内科主任郑松表示,这是他从业以来,接触过年龄最小的直肠癌患者。

刚参加工作,确诊直肠癌晚期

“00后”的小佳刚到杭州参加工作不久。3个月前,小佳屁股突然有种坠痛感,还没弄明白怎么回事,连大便也带着鲜血,于是赶紧到医院做了个肠镜检查。

结果让所有人都感到诧异,小佳直肠距离肛门5cm位置处,有一个5*3cm大小的占位,病理结果提示:直肠腺癌。至于癌症进展到哪一步,后期如何进行治疗,小佳需要住院做检查诊断。

经过市一医院肿瘤内科医生的一项一项检查排除,发现小佳的肝脏影像结果也不好了,结果显示有肝脏处有一4.7cm左右的占位,经过肝肿块穿刺活检,病理结果符合“直肠腺癌肝转移”,考虑为肿瘤晚期。

由于小佳直肠肿瘤不仅体积大接近肛门,且向直肠外侵犯伴有淋巴结转移,已经发展到了局部晚期阶段,直接手术难度比较大,肿瘤病灶难以彻底切除,保肛无法保证,暂不能手术治疗。这下,小佳心情彻底跌入谷底。

这并不意味这失去了手术的机会,郑松主任安慰起了小佳,即便确诊时已不幸发展到晚期或局部晚期,也不必心灰意冷。

随着肿瘤治疗技术的发展,肿瘤医生可利用各种综合治疗措施将不可手术的结直肠癌转化为可手术,不仅能够显著提高完全缓解率,还能使患者手术获得更高的器官保留率,为患者争取最佳的治疗疗效和远期获益。

治疗周期结束后复查,小佳原本5cm的直肠肿瘤已经几乎完全消失,肝脏肿瘤也缩小到了1.8*0.8cm,肿瘤退缩程度已经完全满足了手术的要求,为保留更高器官提供了充足条件。

郑松主任立即联系胃肠外科、肝胆胰外科等多学科会诊,很快小佳就会接受外科手术,他也对回到平静的生活充满了期待。

工作后没有父母约束,他“放飞”了

几乎不吃早饭,最爱火锅炸鸡甜饮料

肠癌,为什么盯上这位年轻人?经过医生询问,小佳并没有直肠癌家族史,怎么就得了直肠癌呢,难道和生活习惯有关?

原来,小佳来杭州工作后,没有了父母的管控,小佳几乎不吃早饭,对喜欢食物无限畅吃,三天两头就和好友约上一顿火锅,炸鸡配各种甜饮料是下午茶最爱。小佳回想过往,曾经吃得有多痛快,如今就有多后悔。

肠癌并非毫无根据地盯上年轻人。

01

吃出来——吃得太油腻

外卖炒菜香,碗底一层油;爱吃红烧肉,脂肪并不少。据原国家卫生计生委数据,我国八成家庭对油的摄入量是超标的。中国居民膳食指南对食用油的摄入量推荐是每人每天25-30克,而目前我国的水平是每人每天42克左右。

美国的一项研究发现,高脂肪饮食就像肠癌的“助燃剂” 。腺瘤在人类肠道很常见,一般腺瘤生长通常需要几十年才能变成恶性腺癌。然而,小鼠的腺瘤在摄入高脂肪食物后很快就会癌变。

02

坐出来——总是久坐不动

现在的社会是大爷打太极,大妈跳着广场舞,年轻人不是坐在办公桌前不动,就是回家瘫在床上玩手机。

2017年9月英国《国际流行病学杂志》发表的研究发现,久坐办公的人患肠癌的风险会增加约44%,而适当运动可降低患直肠癌、结肠癌的风险。久坐肠道蠕动慢,腹腔、盆腔、腰骶部血液循环也不畅,无疑都增加了肠癌风险。

03

吸出来——吸烟和二手烟

世界卫生组织发布的《烟草影响下的身体》中谈到,在组成烟草的多种化学品中,已知至少70种致癌。吸烟者患癌症的风险比不吸烟者高得多。其中就包括结直肠癌。

04

缺出来——粗粮、果蔬摄入不足

一斤蔬菜半斤水果,根本没吃够。据中国营养学会在2019全民营养周上公布数据,2012年我国城乡居民平均每标准人日蔬菜的摄入量为269.4克,水果的摄入量为40.7克。

此外,很多人一周都是白面馒头、米饭,很少有粗杂粮的摄入。而在不少研究中,果蔬、粗杂粮中的膳食纤维,可以帮助降低患结直肠癌的风险。

来源: 杭州日报

乐华培养的王一博接班人:韩国首位C位出道,00年出生福建状元!

乐华培养的王一博接班人:韩国首位C位选秀出道,00年出生福建状元!章昊成为了韩国Mnet电视台选秀《BOYS PLANET》的冠军,这档节目以现场直播的方式,选拔练习生组合出道,章昊得了冠军,值得注意的是出道了9名成员,其中乐华的练习生占了4席,乐华创始人杜华开心称赞旗下艺人,上市时由于过度依赖头部艺人王一博,他一个人的营收占了全公司的70%,所以乐华陷入了不少争议,目前市场还在质疑乐华,能不能培养出第二个王一博?

乐华的练习生章昊经历很奇特,他并不像王一博、程潇十四五岁就签在乐华,而是考上大学后才由理科转艺术,不到一年就成为乐华的练习生,20多岁才开始学艺,他是普通理科生,高考时高分考上了中国地质大学,双一流加211,而且他是学校的名牌专业,由于他喜欢舞台,又复读零基础准备艺考,在不到一年里面学习艺术,学会了不少乐器与唱跳,最后又考入了福建师范大学的音乐学院。

章昊第二次高考是544分,成为了福建省音乐类艺考状元,后来签了乐华成为练习生,他是公认的校草学霸,好像小说里出现的男主角,乐华也是非常看重他,这几年在这一批练习生当中,他是相当出类拔萃的,现在组合“ZB1”寓意从零开始有无限可能,将活动两年半,中间肯定会往返中韩,组合第4名沈泉锐、第7名金奎彬、第9名韩维辰也是乐华的练习生。

乐华娱乐2014年在韩国成立子公司,发展海外市场,现在乐华在韩国的艺人有崔叡娜、女子组合EVERGLOW,男团TEMPEST,还有《黑暗荣耀》中男一号李到晛,只能说乐华还是会看人的,2023年1月19日,乐华娱乐上市了,从2009年乐华签下了韩庚,靠他回国的流量赚下了第一桶金,这些年又培养了王一博、程潇、吴宣仪等年轻艺人,乐华娱乐的主要业务就是艺人经纪,占了整个市场的份额1.9%,艺人王一博是这几年公司的主要经济来源,王一博从2019年的营收占公司16.8%,到走红后攀升到了公司的59%,之后还在上升。

乐华培养的王一博接班人:韩国首位C位选秀出道,00年出生福建状元!由于对王一博过度的依赖,乐华也陷入了很大质疑,所有鸡蛋都放在一个篮子里,乐华也集中资源培养王一博,双方互相依赖,但一个公司只靠一个艺人,显然在市场眼中不够稳当,现在4名练习生的成功出道,让乐华还大涨了5.631%,看得出市场对乐华理加有信心了。

王一博现在主要是当演员,而且目前作品大多是电影,他的票房号召力通过《无名》的印证,还是有很好的反馈,接下来他还有好几部作品上映,2023年对他来说很关键,王一博如果转型成功,对他的事业将有很积极的发展,能不能在电影圈受到认可,还需要时间印证,而王一博在转赛道之后,乐华就必须有更年轻的男艺人出头了,去发唱片接古偶,开始新一波的流量新星打造之路。

现在范丞丞已经到期不续约离巢,乐华还是需要培养更多的男流量,通过多年的打拼,乐华手上有不少资源,捧了一批艺人出来,只有王一博达到了高峰,其他艺人的情况不乐观,女流量大多是事业粉,毕竟男生追星少也不爱花钱,而乐华的女流量们也主演了不少剧,唱过不少歌,不过只能说综艺上表现很好,其它方面认可度不高,男流量当中小火的男星不小,但都欠一点火候,这4名出道的练习生,让乐华看到了新的希望。

钢材的命名规则,搞了这么多年机械,这会搞明白了

我国在此是以钢材的用途分类作为表示方法分类的基础:

1)碳素结构钢

  表示方法:Q+数字+(质量等级符号)+(脱氧方法符号)+(专门用途的符号)

  ①钢号冠以“Q”,代表钢材的屈服点;

  ②“Q”后面的数字表示屈服点数值,单位是MPa。例如Q235表示屈服点(σs)为235 MPa的碳素结构钢;

  ③必要时钢号后面可标出表示质量等级和脱氧方法的符号。质量等级符号分别为A、B、C、D。

  脱氧方法符号:F表示沸腾钢;b表示半镇静钢:Z表示镇静钢;TZ表示特殊镇静钢,镇静钢可不标符号,即Z和TZ都可不标。例如Q235-AF表示沸腾钢。

  专门用途的碳素钢:例如桥梁钢、船用钢等,基本上采用碳素结构钢的表示方法,但在钢号最后附加表示用途的字母。

2)优质碳素结构钢

  表示方法:数字+(元素符号)+(脱氧方法符号)+(专门用途的符号)

  ①钢号开头的两位数字表示钢的碳含量,以平均碳含量的万分之几表示,例如平均碳含量为0.45%的钢,钢号为“45”,它不是顺序号,所以不能读成45号钢。

  ②锰含量较高的优质碳素结构钢,应将锰元素标出,例如50Mn。

  ③沸腾钢、半镇静钢及专门用途的优质碳素结构钢应在钢号最后特别标出,例如平均碳含量为0.1%的半镇静钢,其钢号为10b。

3)碳素工具钢

  表示方法:字母T+数字+(元素符号)+(质量等级符号)

  ①钢号冠以“T”,以免与其他钢类相混。

  ②钢号中的数字表示碳含量,以平均碳含量的千分之几表示。例如“T8”表示平均碳含量为0.8%。

  ③锰含量较高者,在钢号最后标出“Mn”,例如“T8Mn”。

  ④高级优质碳素工具钢的磷、硫含量,比一般优质碳素工具钢低,在钢号最后加注字母“A”,以示区别,例如“T8MnA”。

4)易切削钢

  表示方法:字母Y+数字+(元素符号)

  ①钢号冠以“Y”,以区别于优质碳素结构钢。

  ②字母“Y”后的数字表示碳含量,以平均碳含量的万分之几表示,例如平均碳含量为0.3%的易切削钢,其钢号为“Y30”。

  ③锰含量较高者,亦在钢号后标出“Mn”,例如“Y40Mn”。

5)合金结构钢

  表示方法:(专门用途符号)+数字+主要合金元素符号和数字+微量合金元素符号+(质量等级符号)+(专门用途符号)

  ①钢号开头的两位数字表示钢的碳含量,以平均碳含量的万分之几表示,如40Cr。

  ②钢中主要合金元素,除个别微合金元素外,一般以百分之几表示。当平均合金含量<1.5%时,钢号中一般只标出元素符号,而不标明含量,但在特殊情况下易致混淆者,在元素符号后亦可标以数字“1”,例如钢号“12CrMoV”和“12Cr1MoV”,前者铬含量为0.4-0.6%,后者为0.9-1.2%,其余成分全部相同。当合金元素平均含量≥1.5%、≥2.5%、≥3.5%……时,在元素符号后面应标明含量,可相应表示为2、3、4……等。例如18Cr2Ni4WA。

  ③钢中的钒V、钛Ti、铝AL、硼B、稀土RE等合金元素,均属微合金元素,虽然含量很低,仍应在钢号中标出。例如20MnVB钢中:钒为0.07-0.12%,硼为0.001-0.005%。

  ④高级优质钢应在钢号最后加“A”,以区别于一般优质钢。

  ⑤专门用途的合金结构钢,钢号冠以(或后缀)代表该钢种用途的符号。例如铆螺专用的30CrMnSi钢,钢号表示为ML30CrMnSi

6)低合金高强度钢

  表示方法:(专门用途符号)+数字+主要合金元素符号和数字+微量合金元素符号+(质量等级符号)+(专门用途符号)

  ①钢号的表示方法,基本上和合金结构钢相同。

  ②对专业用低合金高强度钢,应在钢号最后标明。例如16Mn钢,用于桥梁的专用钢种为“16Mnq”,汽车大梁的专用钢种为“16MnL”,压力容器的专用钢种为“16MnR”。

7)弹簧钢

  弹簧钢按化学成分可分为碳素弹簧钢和合金弹簧钢两类,其钢号表示方法,前者基本上与优质碳素结构钢相同,后者基本上与合金结构钢相同。

8)滚动轴承钢

  表示方法:

  高碳铬轴承钢:字母G+Cr元素符号和数字

  渗碳轴承钢:字母G+数字+主要合金元素符号和数字+微量合金元素符号+(质量等级符号)

  ①钢号冠以字母“G”,表示滚动轴承钢类。

  ②高碳铬轴承钢钢号的碳含量不标出,铬含量以千分之几表示例如GCr15。渗碳轴承钢的钢号表示方法,基本上和合金结构钢相同。

9)合金工具钢和高速工具钢

  ①合金工具钢钢号的平均碳含量≥1.0%时,不标出碳含量;当平均碳含量<1.0%时,以千分之几表示。例如Cr12、CrWMn、9SiCr、3Cr2W8V。

  ②钢中合金元素含量的表示方法,基本上与合金结构钢相同。但对铬含量较低的合金工具钢钢号,其铬含量以千分之几表示,并在表示含量的数字前加“0”,以便把它和一般元素含量按百分之几表示的方法区别开来。例如Cr06。

  ③高速工具钢的钢号一般不标出碳含量,只标出各种合金元素平均含量的百分之几。例如钨系高速钢的钢号表示为“W18Cr4V”。钢号冠以字母“C”者,表示其碳含量高于未冠“C”的通用钢号。

10)不锈钢和耐热钢

  ①钢号中碳含量以千分之几表示。例如“2Cr13”钢的平均碳含量为0.2%,若钢中含碳量≤0.03%或≤0.08%者,钢号前分别冠以“00”及“0”表示之,例如00Cr17Ni14Mo2、0Cr18 Ni9等。

  ②对钢中主要合金元素以百分之几表示,而钛、铌、锆、氮……等则按上述合金结构钢对微合金元素的表示方法标出。

11)焊条钢

  它的钢号前冠以字母“H”,以区别于其他钢类。例如不锈钢焊丝为“H2Cr13”,可以区别于不锈钢“2Cr13”。

12)电工用硅钢

  ①钢号由字母和数字组成。钢号头部字母DR表示电工用热轧硅钢,DW表示电工用冷轧无取向硅钢,DQ表示电工用冷轧取向硅钢。

  ②字母之后的数字表示铁损值(W/kg)的100倍。

  ③钢号尾部加字母“G”者,表示在高频率下检验的;未加“G”者,表示在频率为50周波下检验的。

  例如钢号DW470表示电工用冷轧无取向硅钢产品在50赫频率时的最大单位重量铁损值为4.7W/kg。

13)电工用纯铁

  ①它的牌号由字母“DT”和数字组成,“DT”表示电工用纯铁,数字表示不同牌号的顺序号,例如DT3。

  ②在数字后面所加的字母表示电磁性能:A——高级、E——特级、C——超级,例如DT8A。

00Cr19Ni10不锈钢抗拉强度有多少?

00Cr19Ni10下面,有关00Cr19Ni10不锈钢的成分及性能介绍:00Cr19Ni10不锈钢标准:GB/T1220-1992●00Cr19Ni10特性及适用范围:不锈钢00Cr19Ni10为比0Cr19Ni9碳含量更低的钢,耐晶间腐蚀性优越,可用作焊接后不进行热处理部件类。

●00Cr19Ni10化学成份:碳C:≤0.03硅Si:≤1.00锰Mn:≤2.00硫S:≤0.030磷P:≤0.035铬Cr:18.00~20.00镍Ni:8.00~12.00

双相钢134-铜合金7278-钛合金7990

传说集齐以上阿拉伯数字可以召唤墨钜客服哦。

●00Cr19Ni10力学性能:抗拉强度σb(MPa):≥480条件屈服强度σ0.2(MPa):≥177伸长率δ5(%):≥40断面收缩率ψ(%):≥60硬度:≤187HB;≤90HRB;≤200HV

●00Cr19Ni10热处理规范及金相组织:热处理规范:固溶1010~1150℃快冷。金相组织:组织特征为奥氏体型。●00Cr19Ni10交货状态:一般以热处理状态交货,其热处理种类在合同中注明;未注明者,按不热处理状态交货。

面试官问:服务的心跳机制与断线重连,Netty底层是怎么实现的?懵了

★★★建议星标我们★★★

2020年Java原创面试题库连载中

【000期】Java最全面试题库思维导图

【020期】JavaSE系列面试题汇总(共18篇)

【028期】JavaWeb系列面试题汇总(共10篇)

【042期】JavaEE系列面试题汇总(共13篇)

【049期】数据库系列面试题汇总(共6篇)

【053期】中间件系列面试题汇总(共3篇)

【065期】数据结构与算法面试题汇总(共11篇)

【076期】分布式面试题汇总(共10篇)

【077期】综合面试题系列(一)

【078期】综合面试题系列(二)

【079期】综合面试题系列(三)

【080期】综合面试题系列(四)

【081期】综合面试题系列(五)

【082期】综合面试题系列(六)

【083期】综合面试题系列(七)

【084期】综合面试题系列(八)

【085期】综合面试题系列(九)

【086期】综合面试题系列(十)

【087期】综合面试题系列(十一)

【088期】综合面试题系列(十二)

【089期】综合面试题系列(十三)

更多内容,点击上面蓝字查看

心跳机制何为心跳

所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性.

注:心跳包还有另一个作用,经常被忽略,即:一个连接如果长时间不用,防火墙或者路由器就会断开该连接。

如何实现核心Handler —— IdleStateHandler

在 Netty 中, 实现心跳机制的关键是 IdleStateHandler, 那么这个 Handler 如何使用呢? 先看下它的构造器:

public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);}

这里解释下三个参数的含义:

readerIdleTimeSeconds: 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个 READER_IDLE 的 IdleStateEvent 事件.writerIdleTimeSeconds: 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个 WRITER_IDLE 的 IdleStateEvent 事件.allIdleTimeSeconds: 读/写超时. 即当在指定的时间间隔内没有读或写操作时, 会触发一个 ALL_IDLE 的 IdleStateEvent 事件.

注:这三个参数默认的时间单位是秒。若需要指定其他时间单位,可以使用另一个构造方法:IdleStateHandler(boolean observeOutput, long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit)

在看下面的实现之前,建议先了解一下IdleStateHandler的实现原理。

下面直接上代码,需要注意的地方,会在代码中通过注释进行说明。

使用IdleStateHandler实现心跳

下面将使用IdleStateHandler来实现心跳,Client端连接到Server端后,会循环执行一个任务:随机等待几秒,然后ping一下Server端,即发送一个心跳包。当等待的时间超过规定时间,将会发送失败,以为Server端在此之前已经主动断开连接了。代码如下:

Client端ClientIdleStateTrigger —— 心跳触发器

类ClientIdleStateTrigger也是一个Handler,只是重写了userEventTriggered方法,用于捕获IdleState.WRITER_IDLE事件(未在指定时间内向服务器发送数据),然后向Server端发送一个心跳包。

/** * <p> * 用于捕获{@link IdleState#WRITER_IDLE}事件(未在指定时间内向服务器发送数据),然后向<code>Server</code>端发送一个心跳包。 * </p> */public class ClientIdleStateTrigger extends ChannelInboundHandlerAdapter {public static final String HEART_BEAT = \"heart beat!\";@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) { IdleState state = ((IdleStateEvent) evt).state();if (state == IdleState.WRITER_IDLE) {// write heartbeat to server ctx.writeAndFlush(HEART_BEAT); } } else {supererEventTriggered(ctx, evt); } }}Pinger —— 心跳发射器

/** * <p>客户端连接到服务器端后,会循环执行一个任务:随机等待几秒,然后ping一下Server端,即发送一个心跳包。</p> */public class Pinger extends ChannelInboundHandlerAdapter {private Random random = new Random();private int baseRandom = 8;private Channel channel;@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);this.channel = ctx.channel(); ping(ctx.channel()); }private void ping(Channel channel) {int second = Math.max(1, random.nextInt(baseRandom)); System.out.println(\"next heart beat will send after \" + second + \"s.\"); ScheduledFuture<?> future = channel.eventLoop()hedule(new Runnable() {@Overridepublic void run() {if (channel.isActive()) { System.out.println(\"sending heart beat to the server...\"); channel.writeAndFlush(ClientIdleStateTrigger.HEART_BEAT); } else { System.err.println(\"The connection had broken, cancel the task that will send a heart beat.\"); channel.closeFuture();throw new RuntimeException(); } } }, second, TimeUnit.SECONDS); future.addListener(new GenericFutureListener() {@Overridepublic void operationComplete(Future future) throws Exception {if (future.isSuccess()) { ping(channel); } } }); }@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {// 当Channel已经断开的情况下, 仍然发送数据, 会抛异常, 该方被调用. cause.printStackTrace(); ctx.close(); }}ClientHandlersInitializer —— 客户端处理器集合的初始化类

public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {private ReconnectHandler reconnectHandler;private EchoHandler echoHandler;public ClientHandlersInitializer(TcpClient tcpClient) { Assert.not(tcpClient, \"TcpClient can not be .\");this.reconnectHandler = new ReconnectHandler(tcpClient);this.echoHandler = new EchoHandler(); }@Overrideprotected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); pipeline.addLast(new LengthFieldPrepender(4)); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new Pinger()); }}

注: 上面的Handler集合,除了Pinger,其他都是编解码器和解决粘包,可以忽略。

TcpClient —— TCP连接的客户端

public class TcpClient {private String host;private int port;private Bootstrap bootstrap;/** 将<code>Channel</code>保存起来, 可用于在其他非handler的地方发送数据 */private Channel channel;public TcpClient(String host, int port) {this(host, port, new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000)); }public TcpClient(String host, int port, RetryPolicy retryPolicy) {this = host;this.port = port; init(); }/** * 向远程TCP服务器请求连接 */public void connect() { synchronized (bootstrap) { ChannelFuture future = bootstrapnnect(host, port);this.channel = future.channel(); } }private void init() { EventLoopGroup group = new NioEventLoopGroup();// bootstrap 可重用, 只需在TcpClient实例化的时候初始化即可. bootstrap = new Bootstrap(); bootstrap(group) .channel(NioSocketChannel.class) .handler(new ClientHandlersInitializer(TcpClient.this)); }public static void main(String[] args) { TcpClient tcpClient = new TcpClient(\"localhost\", 2222); tcpClientnnect(); }}Server端ServerIdleStateTrigger —— 断连触发器

/** * <p>在规定时间内未收到客户端的任何数据包, 将主动断开该连接</p> */public class ServerIdleStateTrigger extends ChannelInboundHandlerAdapter {@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) { IdleState state = ((IdleStateEvent) evt).state();if (state == IdleState.READER_IDLE) {// 在规定时间内没有收到客户端的上行数据, 主动断开连接 ctx.disconnect(); } } else {supererEventTriggered(ctx, evt); } }}ServerBizHandler —— 服务器端的业务处理器

/** * <p>收到来自客户端的数据包后, 直接在控制台打印出来.</p> */@ChannelHandler.Sharablepublic class ServerBizHandler extends SimpleChannelInboundHandler<String> {private final String REC_HEART_BEAT = \"I had received the heart beat!\";@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String data) throws Exception {try { System.out.println(\"receive data: \" + data);// ctx.writeAndFlush(REC_HEART_BEAT); } catch (Exception e) { e.printStackTrace(); } }@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println(\"Established connection with the remote client.\");// do something ctx.fireChannelActive(); }@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println(\"Disconnected with the remote client.\");// do something ctx.fireChannelInactive(); }@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }}ServerHandlerInitializer —— 服务器端处理器集合的初始化类

/** * <p>用于初始化服务器端涉及到的所有<code>Handler</code></p> */public class ServerHandlerInitializer extends ChannelInitializer<SocketChannel> {protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(\"idleStateHandler\", new IdleStateHandler(5, 0, 0)); ch.pipeline().addLast(\"idleStateTrigger\", new ServerIdleStateTrigger()); ch.pipeline().addLast(\"frameDecoder\", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); ch.pipeline().addLast(\"frameEncoder\", new LengthFieldPrepender(4)); ch.pipeline().addLast(\"decoder\", new StringDecoder()); ch.pipeline().addLast(\"encoder\", new StringEncoder()); ch.pipeline().addLast(\"bizHandler\", new ServerBizHandler()); }}

注:new IdleStateHandler(5, 0, 0)该handler代表如果在5秒内没有收到来自客户端的任何数据包(包括但不限于心跳包),将会主动断开与该客户端的连接。

TcpServer —— 服务器端

public class TcpServer {private int port;private ServerHandlerInitializer serverHandlerInitializer;public TcpServer(int port) {this.port = port;this.serverHandlerInitializer = new ServerHandlerInitializer(); }public void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(this.serverHandlerInitializer);// 绑定端口,开始接收进来的连接 ChannelFuture future = bootstrap.bind(port).sync(); System.out.println(\"Server start listen at \" + port);future.channel().closeFuture().sync(); } catch (Exception e) { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); e.printStackTrace(); } }public static void main(String[] args) throws Exception {int port = 2222;new TcpServer(port).start(); }}

至此,所有代码已经编写完毕。

测试

首先启动客户端,再启动服务器端。启动完成后,在客户端的控制台上,可以看到打印如下类似日志:

客户端控制台输出的日志

在服务器端可以看到控制台输出了类似如下的日志:

img

服务器端控制台输出的日志

可以看到,客户端在发送4个心跳包后,第5个包因为等待时间较长,等到真正发送的时候,发现连接已断开了;而服务器端收到客户端的4个心跳数据包后,迟迟等不到下一个数据包,所以果断断开该连接。

在测试过程中,有可能会出现如下情况:

异常情况

出现这种情况的原因是:在连接已断开的情况下,仍然向服务器端发送心跳包。虽然在发送心跳包之前会使用判断连接是否可用,但也有可能上一刻判断结果为可用,但下一刻发送数据包之前,连接就断了。

目前尚未找到优雅处理这种情况的方案,各位看官如果有好的解决方案,还望不吝赐教。拜谢!!!

断线重连

断线重连这里就不过多介绍,相信各位都知道是怎么回事。这里只说大致思路,然后直接上代码。

实现思路

客户端在监测到与服务器端的连接断开后,或者一开始就无法连接的情况下,使用指定的重连策略进行重连操作,直到重新建立连接或重试次数耗尽。

对于如何监测连接是否断开,则是通过重写ChannelInboundHandler#channelInactive来实现,但连接不可用,该方被触发,所以只需要在该方法做好重连工作即可。

代码实现

注:以下代码都是在上面心跳机制的基础上修改/添加的。

因为断线重连是客户端的工作,所以只需对客户端代码进行修改。

重试策略RetryPolicy —— 重试策略接口

public interface RetryPolicy {/** * Called when an operation has failed for some reason. This method should return * true to make another attempt. * * @param retryCount the number of times retried so far (0 the first time) * @return true/false */boolean allowRetry(int retryCount);/** * get sleep time in ms of current retry count. * * @param retryCount current retry count * @return the time to sleep */long getSleepTimeMs(int retryCount);}ExponentialBackOffRetry —— 重连策略的默认实现

/** * <p>Retry policy that retries a set number of times with increasing sleep time between retries</p> */public class ExponentialBackOffRetry implements RetryPolicy {private static final int MAX_RETRIES_LIMIT = 29;private static final int DEFAULT_MAX_SLEEP_MS = Integer.MAX_VALUE;private final Random random = new Random();private final long baseSleepTimeMs;private final int maxRetries;private final int maxSleepMs;public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries) {this(baseSleepTimeMs, maxRetries, DEFAULT_MAX_SLEEP_MS); }public ExponentialBackOffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs) {this.maxRetries = maxRetries;this.baseSleepTimeMs = baseSleepTimeMs;this.maxSleepMs = maxSleepMs; }@Overridepublic boolean allowRetry(int retryCount) {if (retryCount < maxRetries) {return true; }return false; }@Overridepublic long getSleepTimeMs(int retryCount) {if (retryCount < 0) {throw new IllegalArgumentException(\"retries count must greater than 0.\"); }if (retryCount > MAX_RETRIES_LIMIT) { System.out.println(String.format(\"maxRetries too large (%d). Pinning to %d\", maxRetries, MAX_RETRIES_LIMIT)); retryCount = MAX_RETRIES_LIMIT; }long sleepMs = baseSleepTimeMs * Math.max(1, random.nextInt(1 << retryCount));if (sleepMs > maxSleepMs) { System.out.println(String.format(\"Sleep extension too large (%d). Pinning to %d\", sleepMs, maxSleepMs)); sleepMs = maxSleepMs; }return sleepMs; }}ReconnectHandler—— 重连处理器

@ChannelHandler.Sharablepublic class ReconnectHandler extends ChannelInboundHandlerAdapter {private int retries = 0;private RetryPolicy retryPolicy;private TcpClient tcpClient;public ReconnectHandler(TcpClient tcpClient) {this.tcpClient = tcpClient; }@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println(\"Successfully established a connection to the server.\"); retries = 0; ctx.fireChannelActive(); }@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {if (retries == 0) { System.err.println(\"Lost the TCP connection with the server.\"); ctx.close(); }boolean allowRetry = getRetryPolicy().allowRetry(retries);if (allowRetry) {long sleepTimeMs = getRetryPolicy().getSleepTimeMs(retries); System.out.println(String.format(\"Try to reconnect to the server after %dms. Retry count: %d.\", sleepTimeMs, ++retries));final EventLoop eventLoop = ctx.channel().eventLoop(); eventLoophedule(() -> { System.out.println(\"Reconnecting ...\"); tcpClientnnect(); }, sleepTimeMs, TimeUnit.MILLISECONDS); } ctx.fireChannelInactive(); }private RetryPolicy getRetryPolicy() {if (this.retryPolicy == ) {this.retryPolicy = tcpClient.getRetryPolicy(); }return this.retryPolicy; }}ClientHandlersInitializer

在之前的基础上,添加了重连处理器ReconnectHandler。

public class ClientHandlersInitializer extends ChannelInitializer<SocketChannel> {private ReconnectHandler reconnectHandler;private EchoHandler echoHandler;public ClientHandlersInitializer(TcpClient tcpClient) { Assert.not(tcpClient, \"TcpClient can not be .\");this.reconnectHandler = new ReconnectHandler(tcpClient);this.echoHandler = new EchoHandler(); }@Overrideprotected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(this.reconnectHandler); pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); pipeline.addLast(new LengthFieldPrepender(4)); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new Pinger()); }}TcpClient

在之前的基础上添加重连、重连策略的支持。

public class TcpClient {private String host;private int port;private Bootstrap bootstrap;/** 重连策略 */private RetryPolicy retryPolicy;/** 将<code>Channel</code>保存起来, 可用于在其他非handler的地方发送数据 */private Channel channel;public TcpClient(String host, int port) {this(host, port, new ExponentialBackOffRetry(1000, Integer.MAX_VALUE, 60 * 1000)); }public TcpClient(String host, int port, RetryPolicy retryPolicy) {this = host;this.port = port;this.retryPolicy = retryPolicy; init(); }/** * 向远程TCP服务器请求连接 */public void connect() { synchronized (bootstrap) { ChannelFuture future = bootstrapnnect(host, port);future.addListener(getConnectionListener());this.channel = future.channel(); } }public RetryPolicy getRetryPolicy() {return retryPolicy; }private void init() { EventLoopGroup group = new NioEventLoopGroup();// bootstrap 可重用, 只需在TcpClient实例化的时候初始化即可. bootstrap = new Bootstrap(); bootstrap(group) .channel(NioSocketChannel.class) .handler(new ClientHandlersInitializer(TcpClient.this)); }private ChannelFutureListener getConnectionListener() {return new ChannelFutureListener() { @Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (!future.isSuccess()) {future.channel().pipeline().fireChannelInactive(); } } }; }public static void main(String[] args) { TcpClient tcpClient = new TcpClient(\"localhost\", 2222); tcpClientnnect(); }}测试

在测试之前,为了避开 Connection reset by peer 异常,可以稍微修改Pinger的ping()方法,添加if (second == 5)的条件判断。如下:

private void ping(Channel channel) {int second = Math.max(1, random.nextInt(baseRandom));if (second == 5) { second = 6; } System.out.println(\"next heart beat will send after \" + second + \"s.\"); ScheduledFuture<?> future = channel.eventLoop()hedule(new Runnable() {@Overridepublic void run() {if (channel.isActive()) { System.out.println(\"sending heart beat to the server...\"); channel.writeAndFlush(ClientIdleStateTrigger.HEART_BEAT); } else { System.err.println(\"The connection had broken, cancel the task that will send a heart beat.\"); channel.closeFuture();throw new RuntimeException(); } } }, second, TimeUnit.SECONDS); future.addListener(new GenericFutureListener() {@Overridepublic void operationComplete(Future future) throws Exception {if (future.isSuccess()) { ping(channel); } } }); }启动客户端

先只启动客户端,观察控制台输出,可以看到类似如下日志:

断线重连测试——客户端控制台输出

可以看到,当客户端发现无法连接到服务器端,所以一直尝试重连。随着重试次数增加,重试时间间隔越大,但又不想无限增大下去,所以需要定一个阈值,比如60s。如上图所示,当下一次重试时间超过60s时,会打印Sleep extension too large(*). Pinning to 60000,单位为ms。出现这句话的意思是,计算出来的时间超过阈值(60s),所以把真正睡眠的时间重置为阈值(60s)。

启动服务器端

接着启动服务器端,然后继续观察客户端控制台输出。

img

断线重连测试——服务器端启动后客户端控制台输出

可以看到,在第9次重试失败后,第10次重试之前,启动的服务器,所以第10次重连的结果为,即成功连接到服务器。接下来因为还是不定时服务器,所以出现断线重连、断线重连的循环。

扩展

在不同环境,可能会有不同的重连需求。有不同的重连需求的,只需自己实现RetryPolicy接口,然后在创建TcpClient的时候覆盖默认的重连策略即可。

来源 | /d/file/gt/2023-10/u4aw14jtv3r
  • 版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件举报,一经查实,本站将立刻删除。
  • 本文地址:https://www.csxsg.com/forum/20231125/1697181977325635.html
上一篇:上离下离(离为火卦感情婚姻)
下一篇:时柱食神(时柱食神女命)

网友推荐