netty 远程主机强迫关闭了一个现有的连接_Netty 从入门到精通(一)

Netty是一个高性能的Java NIO框架,用于快速开发网络应用。它简化了网络编程,支持多种协议,如FTP、SMTP和HTTP等。本文介绍了Netty的基本概念、特点及应用场景,通过一个简单的DiscardServer实例演示了如何使用Netty。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是netty

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。

netty的特点

性能

比核心 Java API 更好的吞吐量,较低的延时

资源消耗更少,这个得益于共享池和重用

减少内存拷贝

健壮性

消除由于慢,快,或重载连接产生的 OutOfMemoryError

消除经常发现在 NIO 在高速网络中的应用中的不公平的读/写比

安全

完整的 SSL / TLS 和 StartTLS 的支持

行在受限的环境例如 Applet 或 OSGI

netty的用途

d53a1135ae036ed07a9df7a60ea46190.png

在微服务的大潮之中, 架构师小明把系统拆分成了多个服务,根据需要部署在多个机器上,这些服务非常灵活,可以随着访问量弹性扩展。

99113a030ba62774a4c5026589e77e7c.png

世界上没有免费的午餐, 拆分成多个“微服务”以后虽然增加了弹性,但也带来了一个巨大的挑战:服务之间互相调用的开销

比如说:原来用户下一个订单需要登录,浏览产品详情,加入购物车,支付,扣库存等一系列操作,在单体应用的时候它们都在一台机器的同一个进程中,说白了就是模块之间的函数调用,效率超级高

现在好了,服务被安置到了不同的服务器上,一个订单流程,几乎每个操作都要越网络,都是远程过程调用(RPC), 那执行时间、执行效率可远远比不上以前了。

远程过程调用的第一版实现使用了HTTP协议,也就是说各个服务对外提供HTTP接口。 小明发现,HTTP协议虽然简单明了,但是废话太多,仅仅是给服务器发个简单的消息都会附带一大堆无用信息:

GET /orders/1 HTTP/1.1

Host: order.myshop.com

User-Agent: Mozilla/5.0 (Windows NT 6.1; )

Accept: text/html;

Accept-Language: en-US,en;

Accept-Encoding: gzip

Connection: keep-alive

......

看看那User-Agent,Accept-Language ,这个协议明显是为浏览器而生的!但是我这里是程序之间的调用,用这个HTTP有点亏。

能不能自定义一个精简的协议? 在这个协议中我只需要把要调用方法名和参数发给服务器即可,根本不用这么多乱七八糟的额外信息。

但是自定义协议客户端和服务器端就得直接使用“低级”的Socket了,尤其是服务器端,得能够处理高并发的访问请求才行。

小明复习了一下服务器端的socket编程,最早的Java是所谓的阻塞IO(Blocking IO), 想处理多个socket的连接的话需要创建多个线程, 一个线程对应一个。

d24e3bb13f7237989a4518e9c0a8c25b.png

这种方式写起来倒是挺简单的,但是连接(socket)多了就受不了了,如果真的有成千上万个线程同时处理成千上万个socket,占用大量的空间不说,光是线程之间的切换就是一个巨大的开销。

更重要的是,虽然有大量的socket,但是真正需要处理的(可以读写数据的socket)却不多,大量的线程处于等待数据状态(这也是为什么叫做阻塞的原因),资源浪费得让人心疼。

后来Java为了解决这个问题,又搞了一个非阻塞IO(NIO:Non-Blocking IO,有人也叫做New IO), 改变了一下思路:通过多路复用的方式让一个线程去处理多个Socket。

67e22adacadcc59e4a7a4fbbfdf93d4d.png

这样一来,只需要使用少量的线程就可以搞定多个socket了,线程只需要通过Selector去查一下它所管理的socket集合,哪个Socket的数据准备好了,就去处理哪个Socket,一点儿都不浪费。

好了,就是Java NIO了!

小明先定义了一套精简的RPC的协议,里边规定了如何去调用一个服务,方法名和参数该如何传递,返回值用什么格式......等等。然后雄心勃勃地要把这个协议用Java NIO给实现了。

可是美好的理想很快被无情的现实给击碎, 小明努力了一周就意识到自己陷入了一个大坑之中,Java NIO虽然看起来简单,但是API还是太“低级”了,有太多的复杂性,没有强悍的、一流的编程能力根本无法驾驭,根本做不到高并发情况下的可靠和高效。

小明不死心,继续向领导要人要资源,一定要把这个坑给填上,挣扎了6个月以后,终于实现了一个自己的NIO框架,可以执行高并发的RPC调用了。

然后又是长达6个月的修修补补,小明经常半夜被叫醒:生产环境的RPC调用无法返回了! 这样的Bug不知道改了多少个。

在那些不眠之夜中,小明经常仰天长叹:我用NIO做个高并发的RPC框架怎么这么难呐!

一年之后,自研的框架终于稳定,可是小明也从张大胖那里听到了一个让他崩溃的消息: 小明你知道吗?有个叫Netty的开源框架,可以快速地开发高性能的面向协议的服务器和客户端。 易用、健壮、安全、高效,你可以在Netty上轻松实现各种自定义的协议!咱们也试试?

小明赶紧研究,看完后不由得“泪流满面”:这东西怎么不早点出来啊!

好了,这个故事我快编不下去了,要烂尾了。

e08351f10758861927a04aac3dedbe3b.png

说说Netty到底是何方神圣, 要解决什么问题吧。

像上面小明的例子,想使用Java NIO来实现一个高性能的RPC框架,调用协议,数据的格式和次序都是自己定义的,现有的HTTP根本玩不转,那使用Netty就是绝佳的选择。

其实游戏领域是个更好的例子,长连接,自定义协议,高并发,Netty就是绝配。

因为Netty本身就是一个基于NIO的网络框架, 封装了Java NIO那些复杂的底层细节,给你提供简单好用的抽象概念来编程。

下载netty

1f81a15f999421657aecf39ca387f7aa.png

download

点击这里即可下载netty

创建一个helloworld项目

在开始之前

运行本章示例的最低要求只有两个;Netty和JDK 1.6或以上的最新版本。Netty的最新版本可以在项目下载页面中找到。要下载JDK的正确版本,请参考您首选的JDK供应商的网站。

在阅读时,您可能对本章介绍的类有更多的问题。如果您想了解更多,请参考API参考资料。为了方便起见,本文中的所有类名都链接到在线API引用。另外,请不要犹豫联系Netty项目社区,让我们知道是否有任何不正确的信息、语法错误和打印错误,以及您是否有改进文档的好主意。

编写 Discard Server

世界上最简单的协议不是“你好,世界!”但 Discard Server。它是一个丢弃任何接收到的数据而没有任何响应的协议。

要实现丢弃协议,您只需忽略所有接收到的数据。让我们直接从处理程序实现开始,它处理Netty生成的I/O事件。

package io.netty.example.discard;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;/** * Handles a server-side channel. */public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1) @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2) // Discard the received data silently. ((ByteBuf) msg).release(); // (3) } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4) // Close the connection when an exception is raised. cause.printStackTrace(); ctx.close(); }}

DiscardServerHandler扩展了ChannelInboundHandlerAdapter,它是ChannelInboundHandler的实现。ChannelInboundHandler提供了可以覆盖的各种事件处理程序方法。现在,只需扩展ChannelInboundHandlerAdapter,而不是自己实现处理程序接口。

我们在这里覆盖channelRead()事件处理程序方法。每当从客户端接收到新数据时,就用接收到的消息调用此方法。在本例中,接收到的消息的类型是ByteBuf。

要实现丢弃协议,处理程序必须忽略接收到的消息。ByteBuf是一个引用计数的对象,必须通过release()方法显式地释放它。请记住,释放传递给处理程序的任何引用计数的对象是处理程序的责任。通常,channelRead()处理程序方法是这样实现的:

@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) { try { // Do something with msg } finally { ReferenceCountUtil.release(msg); }}

exceptionCaught()事件处理程序方法在Netty由于I/O错误而引发异常或处理程序实现由于在处理事件时抛出异常而引发异常时使用Throwable调用。在大多数情况下,应该记录捕获的异常,并在这里关闭它的关联通道,尽管此方法的实现可能不同,这取决于您希望如何处理异常情况。例如,您可能希望在关闭连接之前发送带有错误代码的响应消息。

到目前为止一切顺利。我们已经实现了丢弃服务器的前一半。现在剩下的就是编写main()方法,该方法使用DiscardServerHandler启动服务器。package io.netty.example.discard; import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel; /** * Discards any incoming data. */public class DiscardServer {  private int port;  public DiscardServer(int port) { this.port = port; }  public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1) EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); // (2) b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // (3) .childHandler(new ChannelInitializer() { // (4) @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new DiscardServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) // (5) .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)  // Bind and start to accept incoming connections. ChannelFuture f = b.bind(port).sync(); // (7)  // Wait until the server socket is closed. // In this example, this does not happen, but you can do that to gracefully // shut down your server. f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } }  public static void main(String[] args) throws Exception { int port = 8080; if (args.length > 0) { port = Integer.parseInt(args[0]); } new DiscardServer(port).run(); }}

下面我们来进行一个测试

telnet 127.0.0.1 8080 成功连接!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值