第九期分享:初识 Netty -- 高性能网络编程框架

114人浏览 / 1人评论

还记得之前有做过一个项目,涉及到实时聊天功能,但是后来听说实际应用貌似不怎么稳定,就一直想着怎么才能优化一下,或者选择更合理的框架技术来实现。

项目背景就是直播中要用到实时聊天,用户在收看直播时可以和主播互动,当然也包括与其他用户之间的交流,规模大概在1000人左右(貌似也不多),当时使用了J-IM(官网: http://www.j-im.cn)。不稳定的原因可能是自己能力不够,项目集成不合理,当然也不是说J-IM不好用,各有各的长处,合理应用才能发挥到极致。

后来无意间了解到Netty,貌似口碑不错,就怀着试一试的态度,问了度娘一波,果然还是没有让我失望,着实强大,可以提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。不仅如此,Netty简单易学,不必编写复杂的逻辑代码去实现通信,也不需要去考虑性能问题和考虑编码问题,这对我来说简直可以说是nice!

咱们回到正题,回到需求上来,用户互动是基于Socket通信的,关于Socket这里不再多做介绍,本文重点唠唠Netty。在这之前,不妨先自行了解一下NIO编程( Netty是基于Java NIO client-server的网络应用框架),有基础的可以试着用传统的IO编程和NIO编程分别来实现客户端与服务端的通信,看看两者有何区别。再回到Netty,使用Netty实现又会是什么情形,这里简单看下IO和Netty(Netty封装了JDK的NIO)的具体实现。

传统IO:

服务端 IOServer:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 

public class IOServer { public static void main(String[] args) throws Exception { // 监听8000端口 ServerSocket serverSocket = new ServerSocket(8000); new Thread(() -> { while (true) { try { Socket socket = serverSocket.accept(); new Thread(() -> { try { byte[] data = new byte[1024]; InputStream inputStream = socket.getInputStream(); while (true) { int len; while ((len = inputStream.read(data)) != -1) { System.out.println(new String(data, 0, len)); } } } catch (IOException e) { } }).start(); } catch (IOException e) { System.out.println("异常"); } } }).start(); } }

客户端IOClient:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 

public class IOClient { public static void main(String[] args) { new Thread(() -> { try { Socket socket = new Socket("127.0.0.1", 8000); while (true) { try { socket.getOutputStream().write(("hello world").getBytes()); socket.getOutputStream().flush(); Thread.sleep(1000); } catch (Exception e) { } } } catch (IOException e) { e.printStackTrace(); } }).start(); } }

直接Start,正常情况下控制台会每隔一秒打印“ hello world”,如图:

不难发现,传统IO实现每个连接创建成功之后都需要一个线程来维护,每个线程包含一个while死循环,那么1w个连接对应1w个线程,继而1w个while死循环,用户少的情况貌似也没什么大问题,但是对于客户端比较多的业务来说,单机服务端可能需要支撑成千上万的连接数,这时IO模型可能就不太合适了。为了适应市场需求,JDK在1.4之后提出了NIO(网上有很多介绍,可以自己了解下)。

接下来看看Netty,首先我们需要引入Maven依赖:在项目的pom.xml文件中新增:

  •  
  •  
  •  
  •  
  •  
 

<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.25.Final</version> </dependency>

服务端部分代码:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 

public static void main(String[] args) { EventLoopGroup mainGroup = new NioEventLoopGroup(); EventLoopGroup subGroup = new NioEventLoopGroup(); try { // 开启心跳检测 HeartBl.start(); ServerBootstrap server = new ServerBootstrap(); server.group(mainGroup, subGroup).channel(NioServerSocketChannel.class) .childHandler(new WSServerInitialzer()); ChannelFuture future = server.bind(8088).sync(); future.channel().closeFuture().sync(); } catch (Exception e) { } finally { mainGroup.shutdownGracefully(); subGroup.shutdownGracefully(); } }

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 

public class WSServerInitialzer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // websocket 基于http协议,所以要有http编解码器 pipeline.addLast(new HttpServerCodec()); // 对写大数据流的支持 pipeline.addLast(new ChunkedWriteHandler()); // 对httpMessage进行聚合,聚合成FullHttpRequest或FullHttpResponse // 几乎在netty中的编程,都会使用到此hanler pipeline.addLast(new HttpObjectAggregator(1024 * 1024 * 1024)); /** * websocket 服务器处理的协议,用于指定给客户端连接访问的路由 : /ws * 本handler会帮你处理一些繁重的复杂的事 * 会帮你处理握手动作:handshaking(close, ping, pong) ping + pong = 心跳 * 对于websocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同 */ pipeline.addLast(new WebSocketServerProtocolHandler("/test", true)); // 自定义的handler pipeline.addLast(new ChatHandler()); }
}

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 

@Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) { // 获取客户端传输过来的消息 try { String content = msg.text(); String result = ""; UserProcess.X.sendMessage(content, ctx.channel()); } catch (Exception e) { e.printStackTrace(); ctx.channel().writeAndFlush(new TextWebSocketFrame("系統錯誤")); } finally {
} }

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 

/** * 客户端发消息 * @param message * @param channel */ public void sendMessage(String message, Channel channel) { System.out.println("收到消息=="+message); UserProcess.X.sendMessageToUser(message,channel); }
/** * 返回消息内容到客户端 * @param msg * @param channel */ public void sendMessageToUser(String msg,Channel channel) { try { // 向客户端发送消息 channel.writeAndFlush(new TextWebSocketFrame(msg)); users.add(channel); } catch (Exception e) { e.printStackTrace(); } }

运行StartServer.main(),启动服务端,打开socket在线测试( http://coolaf.com/tool/chattest),输入:ws://127.0.0.1:8088/test,

显示连接成功则表示服务端启动正常,可以自定义消息并发送,看下浏览器是否可以正常收到服务端返回的消息:

PS:(可以不用在意服务端回应的那个1)

因为这个分享是基于实际项目改造过来的,剔除实际需求的话,简单使用看起来代码还是有点多,不过不影响Netty的性能,压力测试下,单机连接数数十万是没有任何问题的。

本文从实例出发,相关理论知识还需要自行了解消化,我也在学习的路上,有什么问题可以留言或私信,一起学习!!!

 

Netty官网: https://netty.io/;

Netty官方翻译文档: http://ifeve.com/netty5-user-guide/;

 

全部评论

2020-06-01 18:06
渣渣