文章目录
- Netty入门级应用实例_02_WebSocket发送聊天信息
- 0. 准备工作
- 1. 实时通信
- 2. 后台代码
- 2.1 WebSocketServer
- 2.2 WebSocket初始化器
- 2.3 自定义处理消息的Handler
- 3. 页面代码
- 4. 测试结果
- 4.1 页面
- 4.2 控制台
Netty入门级应用实例_02_WebSocket发送聊天信息
0. 准备工作
引入pom依赖
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.31.Final</version>
</dependency>
1. 实时通信
-
Ajax轮询
使用ajax异步定时向服务端请求数据,服务端有数据或者状态结果返回的话,前端进行结果渲染。
主要特点:需要不停的发出ajax请求,页面不需要刷新,耗资源 -
Long pull
类似于Ajax轮询策略,只是在前台发出请求之后,如果后台没有响应,则会处于一直等待状态,直到后台返回response为止,收到响应后,才能够再次发出请求。
主要特点:需要不停的建立HTTP连接,耗资源,效率差 -
websocket
基于HTTP协议,能够建立长连接,服务端能够主动推送消息给客户端
主要特点:持久化协议,一旦连接建立成功,服务端可以不停的主动向客户端推送消息,即只需要建立一次HTTP请求,就能获得源源不断的数据信息传输
2. 后台代码
2.1 WebSocketServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @program: study_netty
* @description: WebSocketServer
* @author: Mr.superbeyone
* @create: 2018-11-20 13:37
**/
public class WSServer {
public static void main(String[] args) throws Exception{
// 定义主线程池
EventLoopGroup boss = new NioEventLoopGroup();
// 定义从线程池
EventLoopGroup work = new NioEventLoopGroup();
try {
// 定义Server服务器
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, work)
.channel(NioServerSocketChannel.class)
.childHandler(new WSServerInitializer());
ChannelFuture channelFuture = bootstrap.bind(8888).sync();
channelFuture.channel().closeFuture().sync();
} finally {
//优雅关闭
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
2.2 WebSocket初始化器
import com.superbeyone.netty.websocket.handler.ChatHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* @program: study_netty
* @description: WebSocket初始化器
* @author: Mr.superbeyone
* @create: 2018-11-20 13:44
**/
public class WSServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// WebSocket是基于HTTP协议,所以要有HTTP编解码器
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
// 提供对写大数据流的支持
pipeline.addLast("ChunkedWriteHandler", new ChunkedWriteHandler());
// 对HttpMessage进行聚合,聚合成FullHttpRequest或者FullHttpResponse 该handler使用率极高
pipeline.addLast(new HttpObjectAggregator(1024 * 64)); //1024 *64 为最大消息长度
//========================================以上用于支持HTTP协议===================================================
//======================================以下用于支持WebSocket协议================================================
/**
* WebSocket 服务器处理的协议,用于指定给客户端连接访问的路由:/ws
* 本handler会帮你处理一些繁重的复杂工作,比如:握手动作 handshaking (close,ping,pong) ping + pong = 心跳检测
* 对于WebSocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// 添加自定义handler
pipeline.addLast("ChatHandler", new ChatHandler());
}
}
2.3 自定义处理消息的Handler
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.time.LocalDateTime;
/**
* @program: study_netty
* @description: 自定义处理消息的Handler
* @author: Mr.superbeyone
* @create: 2018-11-20 14:03
**/
/**
* TextWebSocketFrame: 在Netty中,是用于为WebSocket专门处理文本的对象,frame是消息的载体
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用于管理和记录所有客户端的Channel
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取从客户端传输过来的消息
String content = msg.text();
System.out.println("接收到的消息:\t" + content);
for (Channel channel : clients) {
channel.writeAndFlush(new TextWebSocketFrame("服务器在[" + LocalDateTime.now() + "]接收到消息,内容为:\t" + content));
}
// 等价于for循环
// clients.writeAndFlush(new TextWebSocketFrame("[服务器在]" + LocalDateTime.now()+ "接收到消息,内容为" + content));
}
/**
* 当客户端连接服务端之后(打开连接)
* 获取客户端的channel,并且放到ChannelGroup中进行管理
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 当触发handlerRemoved,ChannelGroup会自动移除对应客户端的Channel
// clients.remove(ctx.channel());
System.out.println("客户端断开,channel对应的长id为:\t" +
ctx.channel().id().asLongText());
System.out.println("客户端断开,channel对应的短id为:\t" +
ctx.channel().id().asShortText());
super.handlerRemoved(ctx);
}
}
3. 页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
发送消息:
<input type="text" id="msgContent"> <input type="button" onclick="CHAT.chat()" value="点我发送">
<br>
<hr>
接收消息:
<div id="receiveMsg" style="background-color: lightgray"></div>
<script>
window.CHAT = {
socket: null,
init: function () {
//判断浏览器是否支持WebSocket
if (window.WebSocket) {
CHAT.socket = new WebSocket("ws://localhost:8888/ws");
CHAT.socket.onopen = function () {
console.log("连接建立成功");
},
CHAT.socket.onclose = function () {
console.log("连接关闭");
},
CHAT.socket.onerror = function () {
console.log("发生错误");
},
CHAT.socket.onmessage = function (e) {
console.log("接收到消息: " + e.data);
var receiveMsg = document.getElementById("receiveMsg");
var html = receiveMsg.innerHTML;
receiveMsg.innerHTML = html + "<br/>" + e.data;
}
} else {
alert("浏览器不支持WebSocket协议");
}
},
chat: function () {
var msg = document.getElementById("msgContent");
CHAT.socket.send(msg.value);
}
}
CHAT.init();
</script>
</body>
</html>
4. 测试结果
4.1 页面
4.2 控制台