共计 5146 个字符,预计需要花费 13 分钟才能阅读完成。
上节课完成了 netty 的后端搭建,搞定了简单的 http 请求响应,今天来结合前端 websocket 来完成群聊功能。话不多说先上图:
前端构建
不使用复杂构建工具直接静态页面走起
使用了 zui 样式库 http://zui.sexy/?#/,非常不错,有好多模板。我使用的是聊天模板改造
<link rel=”stylesheet” href=”https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/css/zui.min.css”>
<link rel=”stylesheet” href=”zui-theme.css”>
主体部分
<div class=”container”>
<h1>mike 多人聊天室,等你来聊 </h1>
<div class=”comments”>
<section class=”comments-list” id=”chatlist”>
<div class=”comment”>
<a href=”###” class=”avatar”>
<i class=”icon-user icon-2x”></i>
</a>
<div class=”content”>
<div><strong> 其他人 </strong></div>
<div class=”text”> 其他人的聊天内容 </div>
</div>
</div>
<div class=”comment”>
<a href=”###” class=”avatar pull-right”>
<i class=”icon-user icon-2x”></i>
</a>
<div class=”content pull-right”>
<div><strong> 我 </strong></div>
<div class=”text”> 我说话的内容 </div>
</div>
</div>
</section>
<footer>
<div class=”reply-form” id=”commentReplyForm1″>
<form class=”form”>
<div class=”form-group”>
<div class=”input-control has-label-left”>
<input id=”userName” type=”text” class=”form-control” placeholder=””>
<label for=”inputAccountExample2″ class=”input-control-label-left”> 昵称:</label>
</div>
</div>
</form>
<a href=”###” class=”avatar”><i class=”icon-user icon-2x”></i></a>
<form class=”form”>
<div class=”form-group”>
<textarea id=”inputMsg” class=”form-control new-comment-text” rows=”2″ value=”” placeholder=” 开始聊天 … 输入 enter 发送消息 ”></textarea>
</div>
</form>
</div>
</footer>
</div>
</div>
引入依赖 js
<!– ZUI Javascript 依赖 jQuery –>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/lib/jquery/jquery.js”></script>
<!– ZUI 标准版压缩后的 JavaScript 文件 –>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/zui/1.8.1/js/zui.min.js”></script>
websocket 的 js 代码以及业务代码
<script type=”text/javascript”>
window.CHAT = {
me: “”,
WS:{},
init: function () {
if (window.WebSocket) {
this.WS = new WebSocket(“ws://A156B7L58CCNY4B:8090/ws”);
this.WS.onmessage = function(event) {
var data = event.data;
console.log(“ 收到数据:” + data);
// 显示其他人的聊天信息
console.log(CHAT.me);
console.log(data.split(“:”)[0]);
if(CHAT.me != data.split(“:”)[0]) {
appendOtherchat(data);
}
},
this.WS.onclose = function(event) {
console.log(“ 连接关闭 ”);
},
this.WS.onopen = function(evt) {
console.log(“Connection open …”);
},
this.WS.onerror = function(event) {
console.log(“ 连接失败 ….”);
}
} else {
alert(“ 您的浏览器不支持聊天,请更换浏览器 ”);
}
},
chat:function (msg) {
this.WS.send(msg);
}
}
CHAT.init();
function Trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, “”);
}
function appendMy (msg) {// 拼接自己的聊天内容
document.getElementById(‘chatlist’).innerHTML+=”<div class=’comment’><a class=’avatar pull-right’><i class=’icon-user icon-2x’></i></a><div class=’content pull-right’><div><strong> 我 </strong></div><div class=’text’>”+msg+”</div></div></div>”;
}
function appendOtherchat(msg) {// 拼接别人的聊天信息到聊天室
var msgs = msg.split(“:”);
document.getElementById(‘chatlist’).innerHTML+=”<div class=’comment’><a class=’avatar’><i class=’icon-user icon-2x’></i></a><div class=’content’><div><strong>”+msgs[0]+”</strong></div><div class=’text’>”+msgs[1]+”</div></div></div>”;
}
document.getElementById(‘inputMsg’).addEventListener(‘keyup’, function(event) {
if (event.keyCode == “13”) {
// 回车执行查询
var inputMsg = document.getElementById(‘inputMsg’).value;
if (inputMsg == null || Trim(inputMsg) == “” ) {
alert(“ 请输入聊天消息 ”);
} else {
var userName = document.getElementById(‘userName’).value;
if (userName == null || userName == ”) {
alert(“ 请输入聊天昵称 ”);
} else {
// 发送消息 定义消息格式 用户名:[消息]
CHAT.chat(userName+”:”+inputMsg);
// 记录我的昵称
CHAT.me = userName;
appendMy(inputMsg);
// 发送完清空输入
document.getElementById(‘inputMsg’).focus();
document.getElementById(‘inputMsg’).value=””;
}
}
}
});
</script>
都有注释就不解释了自己看
后端服务改造
ChatHandler 改造,判断 websocket 响应
/**
* 读取客户端发送的消息,并将信息转发给其他客户端的 Channel。
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object request) throws Exception {
if (request instanceof FullHttpRequest) {// 是 http 请求
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,HttpResponseStatus.OK , Unpooled.wrappedBuffer(“Hello netty”
.getBytes()));
response.headers().set(“Content-Type”, “text/plain”);
response.headers().set(“Content-Length”, response.content().readableBytes());
response.headers().set(“connection”, HttpHeaderValues.KEEP_ALIVE);
ctx.channel().writeAndFlush(response);
} else if (request instanceof TextWebSocketFrame) {// websocket 请求
String userId = ctx.channel().id().asLongText();
System.out.println(“ 收到客户端 ”+userId+”:”+((TextWebSocketFrame)request).text());
// 发送消息给所有客户端
channels.writeAndFlush(new TextWebSocketFrame(((TextWebSocketFrame)request).text()));
// 发送给单个客户端
//ctx.channel().writeAndFlush(new TextWebSocketFrame(((TextWebSocketFrame)request).text()));
}
}
* ChatServerInitializer 改造,加入 WebSocket
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//websocket 协议本身是基于 http 协议的,所以这边也要使用 http 解编码器
pipeline.addLast(new HttpServerCodec());
// 以块的方式来写的处理器
pipeline.addLast(new ChunkedWriteHandler());
//netty 是基于分段请求的,HttpObjectAggregator 的作用是将请求分段再聚合, 参数是聚合字节的最大长度
pipeline.addLast(new HttpObjectAggregator(1024*1024*1024));
//ws://server:port/context_path
//ws://localhost:9999/ws
// 参数指的是 contex_path
pipeline.addLast(new WebSocketServerProtocolHandler(“/ws”,null,true,65535));
// 自定义 handler
pipeline.addLast(new ChatHandler());
System.out.println(“ChatClient:”+ch.remoteAddress() +” 连接上 ”);
}
改造完成
启动后端服务,访问你的前端静态页面就可以和小伙伴聊天了。其实后端群聊很简单,就是把一个用户的输入消息,返回给所有在线客户端,前端去负责筛选显示。自己动手照着搞10分钟就能完成。
实现功能
输入聊天昵称开始聊天
聊天消息不为空才能发送
发送完自动清空输入,且聚焦输入框
自己的消息显示在左侧,其他人的消息在右侧
别忘了关注我 mike 啥都想搞
求关注啊。