皮皮网

【dnn源码分析】【书城php源码】【彩票官方源码】netty源码 close

2024-12-29 05:35:15 来源:c 像素鸟源码

1.netty系列之:小白福利!手把手教你做一个简单的代理服务器
2.一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接原因分析
3.Netty 出现 Connection reset by peer 异常的几个原因
4.Netty体验(四)模拟微信小程序移动端开发(上)

netty源码 close

netty系列之:小白福利!手把手教你做一个简单的代理服务器

       简介

       爱因斯坦说过:所有的伟大,都产生于简单的细节中。Netty为我们提供了如此强大的eventloop、channel通过对这些简单东西的dnn源码分析有效利用,可以得到非常强大的应用程序,比如今天要讲的代理。

代理和反向代理

       相信只要是程序员应该都听过nginx服务器了,这个超级优秀nginx一个很重要的功能就是做反向代理。那么有小伙伴要问了,有反向代理肯定就有正向代理,那么他们两个有什么区别呢?

       先讲一下正向代理,举个例子,最近流量明星备受打击,虽然被打压,但是明星就是明星,一般人是见不到的,如果有人需要跟明星对话的书城php源码话,需要首先经过明星的经纪人,有经纪人将话转达给明星。这个经纪人就是正向代理。我们通过正向代理来访问要访问的对象。

       那么什么是反向代理呢?比如现在出现了很多人工智能,假如我们跟智能机器人A对话,然后A把我们之间的对话转给了后面的藏着的人,这个人用他的智慧,回答了我们的对话,交由智能机器人A输出,最终实现了人工智能。这个过程就叫做反向代理。

netty实现代理的原理

       那么在netty中怎么实现这个代理服务器呢?

       首选我们首先代理服务器是一个服务器,所以我们需要在netty中使用ServerBootstrap创建一个服务器:

EventLoopGroupbossGroup=newNioEventLoopGroup(1);EventLoopGroupworkerGroup=newNioEventLoopGroup();try{ ServerBootstrapb=newServerBootstrap();b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).handler(newLoggingHandler(LogLevel.INFO)).childHandler(newSimpleDumpProxyInitializer(REMOTE_HOST,REMOTE_PORT)).childOption(ChannelOption.AUTO_READ,false).bind(LOCAL_PORT).sync().channel().closeFuture().sync();

       在这个local服务器中,我们传入ProxyInitializer。在这个handler初始化器中,我们传入自定义的handler:

publicvoidinitChannel(SocketChannelch){ ch.pipeline().addLast(newLoggingHandler(LogLevel.INFO),newSimpleDumpProxyInboundHandler(remoteHost,remotePort));}

       在自定义的handler中,我们使用Bootstrap创建一个client,彩票官方源码用来连接远程要代理的服务器,我们将这个client端的创建放在channelActive方法中:

//开启outbound连接Bootstrapb=newBootstrap();b.group(inboundChannel.eventLoop()).channel(ctx.channel().getClass()).handler(newSimpleDumpProxyOutboundHandler(inboundChannel)).option(ChannelOption.AUTO_READ,false);ChannelFuturef=b.connect(remoteHost,remotePort);

       然后在client建立好连接之后,就可以从inboundChannel中读取数据了:

outboundChannel=f.channel();f.addListener(future->{ if(future.isSuccess()){ //连接建立完毕,读取inbound数据inboundChannel.read();}else{ //关闭inboundchannelinboundChannel.close();}});

       因为是代理服务,所以需要将inboundChannel读取的数据,转发给outboundChannel,所以在channelRead中我们需要这样写:

publicvoidchannelRead(finalChannelHandlerContextctx,Objectmsg){ //将inboundChannel中的消息读取,并写入到outboundChannelif(outboundChannel.isActive()){ outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener)future->{ if(future.isSuccess()){ //flush成功,读取下一个消息ctx.channel().read();}else{ future.channel().close();}});}}

       当outboundChannel写成功之后,再继续inboundChannel的读取工作。

       同样对于client的outboundChannel来说,也有一个handler,在这个handler中,我们需要将outboundChannel读取到的数据反写会inboundChannel中:

publicvoidchannelRead(finalChannelHandlerContextctx,Objectmsg){ //将outboundChannel中的消息读取,并写入到inboundChannel中inboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener)future->{ if(future.isSuccess()){ ctx.channel().read();}else{ future.channel().close();}});}

       当inboundChannel写成功之后,再继续outboundChannel的读取工作。

       如此一个简单的代理服务器就完成了。

实战

       如果我们将本地的Gmail源码被盗端口,代理到www..com的端口,会发生什么情况呢?运行我们的程序,访问, 所以服务器端不认识我们的请求,从而报错。

总结

       本文的代理服务器之间简单的转发请求,并不能够处理上述的场景,那么该怎么解决上面的问题呢? 敬请期待我的后续文章!

       本文的例子可以参考:learn-netty4

       最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

       欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接原因分析

       我们线上存在一个 Dubbo 服务,遇到大量 CLOSE_WAIT 状态的连接,始终无法消失,因此进行了原因分析。

       CLOSE_WAIT 状态出现在被动关闭方,松鼠直播源码收到对端 FIN 包后回复 ACK,但未发送 FIN 包之前。问题在于服务没有回复 FIN,原因可能是收到了 FIN 包却未发送响应,通过抓包验证了这一情况。

       问题核心在于为什么没有回复 FIN。Dubbo 服务底层使用 Netty,作为普通的 TCP 服务端,关键在于 FIN 包的回复。

       分析显示,如果服务没有发送 FIN 包,可能原因有:

       1. 半连接队列或全连接队列积压,通过 ss 命令查看全连接队列大小和等待 accept 的连接个数。

       2. LISTEN 状态的 socket,Recv-Q 表示等待用户进程 accept 的连接个数,Send-Q 表示全连接队列最大容纳的连接数。

       非 LISTEN 状态的 socket,Recv-Q 表示 receive queue 字节大小,Send-Q 表示 send queue 字节大小。

       通过 ss 命令确认 Recv-Q 为 0,全连接队列无积压。

       嫌疑指向 Netty 没有注册事件,导致收到 FIN 包后无动于衷。

       进一步发现,凌晨 1 点业务实例加载大量数据导致堆内存占满,持续进行 fullgc。Netty 线程出现 OOM 异常。在 org.jboss.netty.channel.socket.nio.NioServerBoss#process 方法中,Netty 调用 accept 取走连接,第 行尝试注册事件时抛出 java.lang.OutOfMemoryError 异常。

       因此,Netty 处理不健壮,try-catch 包裹了 accept 连接和注册事件逻辑,在 OOM 异常处理时,未能成功注册事件或关闭连接,导致连接存在但不被监听处理。

       推荐相关视频学习:

       LinuxC++零拷贝的实现 用户态协议栈 ntytcp

       支撑互联网的基石 TCP/IP,5个方面全面解析

       TCP/IP协议栈深度解析丨实现单机百万连接丨优化三次握手、四次挥手

       LinuxC++后台服务器开发架构师免费学习地址

       为模拟问题复现,可使用字节码注入或直接重构 Netty 源码。本地拥有 Netty 源码,采用重构方法更快。重新构建项目后,使用 nc 模拟健康检查握手并断开连接,CLOSE_WAIT 状态连接持续存在直至 Netty 进程退出。再次 nc 断开连接,新增 CLOSE_WAIT 状态。由于服务持续进行健康检查,导致 OOM 期间 CLOSE_WAIT 状态不断增加。

       问题核心:Netty 代码不够健壮,尝试捕获异常时,未能正确处理连接注册事件或关闭连接,导致连接存在且未被监听。

       修改方式:在 catch 处理 throwable 时关闭连接即可,最新版本的 Netty 代码这部分逻辑已优化,将 accept 和注册事件拆分。有兴趣的读者可以尝试。

       学习 TCP、网络编程是解决类似问题的关键。

Netty 出现 Connection reset by peer 异常的几个原因

       æœ€è¿‘使用 netty 过程中发现了几个比较细节的 Connection reset by peer 异常,做个笔记。

        这个场景出现在用 Jedis ping 检测的场景,用完直接 close,服务端稳定出现 Connection reset by peer。

        tcpdump 一下就很容易定位到问题所在,客户端收到 PONG 响应后直接发了一个 RST 包给服务端:

        查看 Jedis 的源码发现 socket 有个比较特殊的配置 socket.setSoLinger(true, 0) 。

        先看一下 man7/socket.7 的解释:

        坦白说不是很明白啥意思。。。

        最终在 stackoverflow 上找到一个比较容易理解的解释:

        简而言之,设置 SO_LINGER(0) 可以不进行四次挥手直接关闭 TCP 连接,在协议交互上就是直接发 RST 包,这样的好处是可以避免长时间处于 TIME_WAIT 状态,当然 TIME_WAIT 存在也是有原因的,大部分评论都不建议这样配置。

        这个场景有点儿微妙,首先得理解一下 tcp 的两个队列。

        这篇文章讲得比较清楚: SYN packet handling in the wild

        accept 队列满通常是由于 netty boss 线程处理慢,特别是在容器化之后,服务刚启动的时候很容易出现 CPU 受限。

        为了模拟这个现象,我写了个示例程序 shichaoyuan/netty-backlog-test ,设置 SO_BACKLOG 为 1,并且在 accept 第一个连接后设置 autoRead 为 false,也就是让 boss 线程不再继续 accept 连接。

        启动第一个 Client,可以正常连接,发送 PING,接收 PONG。

        启动第二个 Client,也可以正常连接,但是没有收到 PONG:

        可见这个连接创建成功了,已经在 Accept Queue 里了,但是进程没有 accept,所以没有与进程绑定。

        启动第三个 Client,也可以正常连接,也没有收到 PONG:

        与第二个连接一样。

        启动第四个 Client,也可以正常连接,但是在发送 PING 后出现 Connection reset by peer:

        这个连接在服务端并没有进入 accept queue,处于 SYN_RECV 状态,并且很快就消失了(因为 accept queue 已经满了,无法转入 ESTABLISHED 状态)。

        抓包看一下:

        从客户端视角来看连接确实是建成功了,有一个比较特殊的地方在三次握手之后,服务端又向客户端发送了一个 [S.],客户端回复了一个 [.],这个交互看起来不影响连接。

        服务端后来销毁了连接,而客户端还认为连接是 ESTABLISHED 的,发送 PING 消息,服务端自然得回复一个 RST。

        PS:我在 Windows 的 WSL2 中实验这种场景是建连接超时,可能不同的操作系统或 linux 版本对这个交互的过程处理不同,在此不进行进一步测试了。

        以上,这个故事告诉我们判断连接是否可用,建成功之后应该发个心跳包测试一下。

Netty体验(四)模拟微信小程序移动端开发(上)

       Netty模拟微信小程序移动端开发(上)体验

       在上篇实现了网页版的实时通讯后,本篇转向移动端,模拟微信App的通信,采用WebSocket实现长连接。

       在移动端,实时双向通讯有三种常见方式:ajax轮询持续请求,Long pull的循环阻塞等待,以及WebSocket的HTTP长连接,后者主动且保持连接。WebSocket API的基本步骤包括连接到后端,如通过`var socket = new WebSocket("ws://[ip]:[port]")`建立连接,其生命周期包括`onopen()`, `onmessage()`, `onerror()`, 和 `onclose()` 函数,以及主动方法`Socket.send()`和`Socket.close()`。

       首先,通过HBuilder工具创建移动端项目,HBuilderX相比HBuilder更稳定。在首页`index.html`中,我们添加必要的调试快捷键,并设置页面样式。为了移动端联调Netty,需要在Android手机上启用USB调试并运行调试基座。模拟微信页面时,需设置底部状态栏的tab样式,通过`mui.plusReady()`初始化并设置背景和字体颜色。

       接着,创建聊天记录、通讯录等页面,内容仅需标题,通过数组管理页面切换。通过`mui.webview`实现页面的动态加载和显示,并处理底部tab的点击事件。最后,为解决字体问题,对底部top样式进行设置,完成联机测试。

       需要注意,本内容是基于网上资料编写的,仅供学习参考,非商业用途。关注公众号获取更多资源,如《Springcloud与Docker微服务架构实战》和《Netty权威指南》。投稿或指正,欢迎参与。