还记得之前有做过一个项目,涉及到实时聊天功能,但是后来听说实际应用貌似不怎么稳定,就一直想着怎么才能优化一下,或者选择更合理的框架技术来实现。
项目背景就是直播中要用到实时聊天,用户在收看直播时可以和主播互动,当然也包括与其他用户之间的交流,规模大概在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/;
全部评论