1.netty系列之:小白福利!手把手教你做一个简单的代理服务器
2.一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接原因分析
3.Netty åºç° Connection reset by peer å¼å¸¸çå 个åå
4.Netty体验(四)模拟微信小程序移动端开发(上)
netty系列之:小白福利!手把手教你做一个简单的代理服务器
简介
爱因斯坦说过:所有的伟大,都产生于简单的细节中。Netty为我们提供了如此强大的eventloop、channel通过对这些简单东西的麒麟源码编译有效利用,可以得到非常强大的应用程序,比如今天要讲的代理。
代理和反向代理相信只要是程序员应该都听过nginx服务器了,这个超级优秀nginx一个很重要的功能就是做反向代理。那么有小伙伴要问了,有反向代理肯定就有正向代理,那么他们两个有什么区别呢?
先讲一下正向代理,举个例子,最近流量明星备受打击,虽然被打压,但是明星就是明星,一般人是见不到的,如果有人需要跟明星对话的疯狂猜骰子源码话,需要首先经过明星的经纪人,有经纪人将话转达给明星。这个经纪人就是正向代理。我们通过正向代理来访问要访问的对象。
那么什么是反向代理呢?比如现在出现了很多人工智能,假如我们跟智能机器人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的读取工作。
如此一个简单的代理服务器就完成了。
实战如果我们将本地的点评源码 多城市端口,代理到www..com的端口,会发生什么情况呢?运行我们的程序,访问, 所以服务器端不认识我们的请求,从而报错。
总结本文的代理服务器之间简单的转发请求,并不能够处理上述的场景,那么该怎么解决上面的问题呢? 敬请期待我的后续文章!
本文的例子可以参考:learn-netty4
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接原因分析
我们线上存在一个 Dubbo 服务,遇到大量 CLOSE_WAIT 状态的连接,始终无法消失,因此进行了原因分析。 CLOSE_WAIT 状态出现在被动关闭方,前端html框架源码收到对端 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权威指南》。投稿或指正,欢迎参与。2024-11-26 13:37
2024-11-26 13:33
2024-11-26 13:02
2024-11-26 12:37
2024-11-26 11:28
2024-11-26 11:14