前面的文章主要以分析 Netty 源码为主,后面的文章以 Netty 实践为主。本篇文章会带大家使用 Netty 实现一个 WebSocket 聊天室。
代码已上传至 Gitee:https://gitee.com/panchanghe/netty-project。
1. 前端代码
因为是基于网页的聊天室,所以 HTML 等文件必不可少。笔者是后端开发,前端写的确实不怎么样,大家凑合看吧,主要是学习 Netty 的使用哈~
index.html:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>在线聊天室</title>
<script src="http://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="/static/js/index.js"></script>
</head>
<body>
<div id="app">
<div>
<input type="text" v-model="sendMessage"/>
<input type="button" onclick="send()" value="发送">
</div>
<hr>
<div>
<ul>
<li v-for="m in messages">
<span style="color: #555;">{{m.time}}</span>
<span>{{m.from}}</span>
:
<span style="color: red;">{{m.text}}</span>
</li>
</ul>
</div>
</div>
</body>
</html>
index.js:
var vue;
var ws;
window.onload = () => {
vue = new Vue({
el: '#app',
data: {
sendMessage:'',
messages:[]
}
});
ws = new WebSocket('ws://127.0.0.1:9999/ws');
ws.onopen = function(){
console.log('连接成功.');
}
ws.onmessage = function(e){
//当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据
vue.messages.push(JSON.parse(e.data));
}
ws.onerror = function (eerror) {
//如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
console.log(error);
};
};
function send() {
ws.send(vue.sendMessage);
}
2. 后端代码
后端才是核心,真正的硬货。
2.1 CharServer
聊天室的服务启动类。
public class CharServer {
// 服务绑定的本地端口
private final int port;
public CharServer(int port) {
this.port = port;
}
public static void main(String[] args) {
new CharServer(9999).start();
}
// 启动服务
public void start(){
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
new ServerBootstrap()
.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(ChannelInit.INSTANCE)
.bind(port);
}
}
2.2 ChannelInit
客户端 SocketChannel 的初始化类。
@ChannelHandler.Sharable
public class ChannelInit extends ChannelInitializer<SocketChannel> {
public static ChannelInit INSTANCE = new ChannelInit();
@Override
protected void initChannel(SocketChannel sc) throws Exception {
ChannelPipeline pipeline = sc.pipeline();
// HTTP请求编解码器
pipeline.addFirst(new HttpServerCodec());
// 文件分块传输
pipeline.addFirst(new ChunkedWriteHandler());
// HTTP聚合器
pipeline.addLast(new HttpObjectAggregator(1024 * 1024 * 5));
// 自定义的HTTP请求处理器
pipeline.addLast(HttpRequestHandler.INSTANCE);
// WebSocket服务端协议处理
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
// WebSocket帧处理器
pipeline.addLast(new TextWebSocketFrameHandler());
}
}
2.3 HttpRequestHandler
因为要响应 Html、Js 等文件,所以服务需要能处理 HTTP 请求。
@ChannelHandler.Sharable
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
public static HttpRequestHandler INSTANCE = new HttpRequestHandler();
private static File BASE_FILE;
static {
BASE_FILE = new File(HttpRequestHandler.class.getResource("/static").getFile());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
String uri = request.uri();
if (uri.startsWith("/ws")) {
// 升级为WebSocket协议
ctx.fireChannelRead(request.retain());
} else if (uri.startsWith("/static")) {
// 文件传输
HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
String contentType = "text/html;charset=UTF-8";
if (uri.endsWith(".js")) {
contentType = "application/javascript; charset=utf-8";
}
response.headers().add(HttpHeaderNames.CONTENT_TYPE, contentType);
RandomAccessFile file = new RandomAccessFile(new File(BASE_FILE, uri.replace("/static/", "")), "r");
FileRegion fileRegion = new DefaultFileRegion(file.getChannel(), 0, file.length());
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, file.length());
ctx.write(response);
ctx.write(fileRegion);
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
} else {
// 处理HTTP请求 ...
}
}
}
2.4 TextWebSocketFrameHandler
WebSocket 的文本帧处理器,当有新的客户端加入、或者有客户端发送消息时,广播通知所有客户端。
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static ChannelGroup group = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
group.add(ctx.channel());
// 新的连接
for (Channel channel : group) {
String message = Message.fromSystem(ctx.channel().id() + " 加入聊天室...").toJson();
channel.writeAndFlush(new TextWebSocketFrame(message));
}
} else {
super.userEventTriggered(ctx, evt);
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 客户端发送的文本
String text = msg.text();
// 构建Message
String message = Message.from(ctx.channel().id().asShortText(), text).toJson();
// 广播所有客户端
for (Channel channel : group) {
channel.writeAndFlush(new TextWebSocketFrame(message));
}
}
}
3. 测试
原文始发于微信公众号(程序员小潘):Netty实现WebSocket聊天室
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/29423.html