Netty结合WebSocket的聊天室
首先是Netty的服务器端
- 由于结合了springboot,所以添加了@Component注解,也可以直接写一个main函数启动
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.stereotype.Component;
/**
* author:Jerry1ee
* time:2020.2.1
*
*/
@Component
public class WebChatServer {
private static class SingletonWebChatServer{
static final WebChatServer instance = new WebChatServer();
}
public static WebChatServer getInstance()
{
return new SingletonWebChatServer().instance;
}
private EventLoopGroup bossGroup ;
private EventLoopGroup workGroup ;
private ServerBootstrap server;
private ChannelFuture channelFuture;
public WebChatServer(){
bossGroup = new NioEventLoopGroup();
workGroup = new NioEventLoopGroup();
server = new ServerBootstrap();
server.group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WebChatServerInitializer())
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true);
}
public void start()
{
this.channelFuture = server.bind(8088);
System.out.println("服务器已经启动...");
}
}
Netty的服务器初始化器
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;
/**
* author:Jerry1ee
* time:2020.2.1
*
*/
public class WebChatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(64*1024));
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpRequestHandler("/ws"));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new TextWebSocketFrameHandler());
}
}
Netty的助手类
-
助手类有自定义的,也有Netty中原本就有的
-
助手类就是为了添加业务过滤逻辑的,可以处理过来的socket请求
-
这边一共写了两个助手类
HttpRequestHandler助手类
- 为了拦截http请求进而转化成为websocket请求的
- 这一部分参考了别人的代码,不再贴出
发送文本的助手类
package com.lzy.chatmovie.netty;
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;
/**
* author:Jerry1ee
* time:2020.2.1
*
*/
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//有客户端连接时,自动调用,将链接的客户端记录下来
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel income = ctx.channel();
//进入聊天室
for (Channel channel : channels) {
channel.writeAndFlush(new TextWebSocketFrame("欢迎 " + income.remoteAddress() + "进入聊天室!"));
}
channels.add(ctx.channel());
System.out.println(income.remoteAddress()+"加入了!");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel outcome = ctx.channel();
//进入聊天室
for (Channel channel : channels) {
channel.writeAndFlush(new TextWebSocketFrame(outcome.remoteAddress() + " 离开了!"));
}
channels.remove(ctx.channel());
System.out.println(outcome.remoteAddress()+"离开了!");
}
//客户端发送消息时,自动执行
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
Channel income = ctx.channel();//获得发送消息的人的链接通道
System.out.println(msg.text());
System.out.println("此时所有的连接:");
for (Channel channel : channels) {
System.out.println(channel);
if (channel != income) {
channel.writeAndFlush(new TextWebSocketFrame("[用户]" + income.remoteAddress() + "说:" + msg.text()));
} else {
channel.writeAndFlush(new TextWebSocketFrame("我说:" + msg.text()));
}
}
}
}
前端(采用了Vue框架)
<template>
<div class="chat_container">
<!--面包屑-->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>聊天室测试</el-breadcrumb-item>
</el-breadcrumb>
<!--卡片视图区域-->
<el-card class="box-card">
<!--聊天表单-->
<el-form ref="chatFormRef" :model="chatForm">
<!--消息框-->
<el-form-item prop="chatArea">
<el-input type="textarea" v-model="chatForm.chatArea" :rows="18"></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="7">
<!--输入框-->
<el-form-item prop="message">
<el-input v-model="chatForm.message" clearable @keyup.enter.native=send> </el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<!--按钮-->
<el-form-item class="buttons">
<el-button type="primary" @click="send">发送消息</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</template>
<script>
export default {
data()
{
return{
chatForm:{
chatArea:'',
message:''
},
path:"ws://localhost:8088/ws",
socket:""
}
},
mounted()
{
this.init()
},
methods:
{
init: function () {
if(typeof(WebSocket) === "undefined"){
this.$message.warning("您的浏览器不支持socket")
}else{
// 实例化socket
this.socket = new WebSocket(this.path)
// 监听socket连接
this.socket.onopen = this.open
// 监听socket消息
this.socket.onmessage = this.onmessage
//监听socket关闭
this.socket.onclose = this.onclose
}
},
open:function(event) {
this.chatForm.chatArea = "连接开启!"
},
onmessage:function(msg)
{
this.chatForm.chatArea = this.chatForm.chatArea+"\n"+msg.data
},
onclose:function(){
this.chatForm.chatArea = this.chatForm.chatArea+"\n连接关闭"
},
send:function () {
if (!window.WebSocket) {
return;
}
if(this.socket.readyState ==WebSocket.OPEN )
{
this.socket.send(this.chatForm.message)
this.chatForm.message=""
}
else {
alert("连接没有开启!")
}
}
}
}
</script>
<style lang="less" scoped>
.login_container{
background-color: #25606b;
height: 100%;
}
/*.chat_form{*/
/*position: absolute;*/
/*bottom: 0;*/
/*width: 100%;*/
/*right: 0;*/
/*padding: 0 100px;*/
/*box-sizing: border-box;*/
/*}*/
/*.textarea_form{*/
/*position: absolute;*/
/*bottom:10px;*/
/*width: 50%;*/
/*right: 0;*/
/*padding: 0 100px;*/
/*box-sizing: border-box;*/
/*}*/
</style>