基于netty实现简单的聊天功能1对1(私信/群发)

netty聊天功能

  • 说明:
  • 我的想法
  • 演示
  • 聊天服务端
  • 聊天初始化配置
  • 客户端处理消息
  • 前端代码
  • 结语

说明:

我的想法

演示




点击开启聊天后聊天






演示地址:
不见得哪天失效了…有哪位大哥服务器可用…
http://112.35.164.210:18089/

聊天服务端

public class JiayangApplication {public static void main(String[] args) {SpringApplication.run(JiayangApplication.class, args);}@Beanpublic void chat() throws Exception{EventLoopGroup mainGroup = new NioEventLoopGroup();EventLoopGroup subGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(mainGroup,subGroup).channel(NioServerSocketChannel.class).childHandler(new WSServerInitialzer());//初始化器ChannelFuture future = serverBootstrap.bind(8080).sync();future.channel().closeFuture().sync();}finally {mainGroup.shutdownGracefully();subGroup.shutdownGracefully();}}
}

聊天初始化配置

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;public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();
//        pipeline.addLast(new HttpServerCodec());
//        pipeline.addLast(new StringDecoder());
//        pipeline.addLast(new StringEncoder());
//        //webcocket基于http协议,所以要有http编解码器
//        pipeline.addLast(new HttpServerCodec());
//        //对写大数据流的支持
//        pipeline.addLast(new ChunkedWriteHandler());//支持大数据流
//        //对httpmessage进行聚合,聚合成fullhttprequest或fullhttpresponse
//        pipeline.addLast(new HttpObjectAggregator(1024*64));
//        //==========================以上是用于支持http协议支持
//        //websocket 服务器处理的协议,用于指定给客户端连接访问的路由:/ws
//        //本handler会帮你处理一些繁重的复杂的事
//        //会帮你处理握手动作 :handshaking(close,ping,pong)ping +pong = 心跳
//        //对于websocket来讲,都是frames进行传输的,不同的数据类型对应的frames是不同的
//        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//        //自定义的handler
//        pipeline.addLast(new ChatHandler());pipeline.addLast("http-decoder", new HttpServerCodec());// 加入ObjectAggregator解码器,作用是他会把多个消息转换为单一的FullHttpRequest或者FullHttpResponsepipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));// 加入chunked 主要作用是支持异步发送的码流(大文件传输),但不专用过多的内存,防止java内存溢出pipeline.addLast(new ChunkedWriteHandler());// 加入自定义handlerpipeline.addLast( new ChatHandler());// 加入webSocket的hanlderpipeline.addLast(new WebSocketServerProtocolHandler("/ws"));}
}

客户端处理消息

import com.alibaba.fastjson.JSON;
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.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;import java.net.URLDecoder;
import java.util.concurrent.ConcurrentHashMap;/*** 处理消息的handler* TextWebSocketFrame:在netty中,适用于为websocket专门处理文本的对象,frame是消息的载体*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {//用于记录和管理所有客户端private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);// concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象。(以后用数据库代替)private static ConcurrentHashMap<String, Channel> webSocketMap = new ConcurrentHashMap<>();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {//      //获取客户端消息String content = msg.text();Msgs msgs = JSON.parseObject(content, Msgs.class);if(msgs.getType()==2){clients.writeAndFlush(new TextWebSocketFrame(msgs.getFromUserId()+"(群发):"+ msgs.getMsg()));}else{String toUserId = msgs.getToUserId();Channel channel = webSocketMap.get(toUserId);if (channel == null || !channel.isActive()) {
//                ctx.writeAndFlush(new TextWebSocketFrame("你说: "+msgs.getMsg()));ctx.writeAndFlush(new TextWebSocketFrame(msgs.getToUserId() + " 不在线 "+ "\n"));} else {
//                ctx.writeAndFlush(new TextWebSocketFrame("你说: "+msgs.getMsg()));channel.writeAndFlush(new TextWebSocketFrame(msgs.getFromUserId() + " : " + msgs.getMsg()));}}
//        System.out.println("接收到的数据: " + content);
//        //下面方法和上面一致
//        clients.writeAndFlush(new TextWebSocketFrame("[服务器在: ]"+
//                LocalDateTime.now()+"接收到消息, 消息为: "+content));}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (null != msg && msg instanceof FullHttpRequest) {//转化为http请求FullHttpRequest request = (FullHttpRequest) msg;//拿到请求地址String uri = request.uri();System.out.println(uri);//判断是不是websocket请求,如果是拿出我们传递的参数(我的是token)String origin = request.headers().get("Origin");if (null == origin) {ctx.close();} else {String userId = StringUtils.substringAfter(uri, "/ws/");userId = URLDecoder.decode(userId, "UTF-8");webSocketMap.put(userId, ctx.channel());//重新设置请求地址request.setUri("/ws");clients.writeAndFlush(new TextWebSocketFrame(userId+" 上线了----------"));clients.add(ctx.channel());}}//接着建立请求super.channelRead(ctx, msg);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("channel...活跃");super.channelActive(ctx);}@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {System.out.println("channel...移除");super.channelUnregistered(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println("channel...不活跃");super.channelInactive(ctx);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {System.out.println("channel...读取完毕");super.channelReadComplete(ctx);}@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {System.out.println("channel...用户事件触发");super.userEventTriggered(ctx, evt);}@Overridepublic void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {System.out.println("channel...可更改");super.channelWritabilityChanged(ctx);}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {System.out.println("助手类添加");super.handlerAdded(ctx);}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {System.out.println("助手类移除");super.handlerRemoved(ctx);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("捕获到异常");super.exceptionCaught(ctx, cause);}
}@Data
class Msgs {private String fromUserId;private String toUserId;private String msg;private int type;
}

前端代码

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>我的小黑屋</title><script src="js/jquery-2.1.1.min.js"></script></head><body><div style="background-color: #C9394A;text-align: center;"><div id="tt" style="background-color: #C9394A;text-align: center;font-weight:800;font-size:3.75em;padding-top: 25px;align-items: center;
            vertical-align: middle;
		font-family: Georgia;">小黑屋</div><div style="background-color: #C9394A;text-align: center;"><label>我的昵称:</label><input  id="nm" placeholder="请输入你的名字" onkeyup="event.keyCode==13?sure():''"/><input type="button" value="确定" onclick="sure()" id="an"/></div><div style="background-color: #C9394A;text-align: center;"><label>聊天对象:</label><input  id="nm1" placeholder="请输入聊天对象" onkeyup="event.keyCode==13?sure1():''"/><input type="button" value="确定" onclick="sure1()" id="an1"/></div><div  style="background-color: #C9394A;text-align: center;"><input id="bu" type="button" value="开启聊天" style="width: 19.01rem;" onclick="click1()"/></div><div style="background-color: #C9394A;text-align: center;"><label >发送消息:</label><input type="text" id="msgContent"  placeholder="请输入你想发送的内容"  onkeyup="event.keyCode==13?CHAT.chat():''"/><input id="in" type="button" value="发送" onclick="CHAT.chat()" /><input type="button" value="群发" onclick="CHAT.chat1()" /></div><div style="background-color: #C9394A;"><label>接收消息:</label><div id="reveiveMsg"></div></div></div><script type="application/javascript">function keyupt(e){ console.log(111)var evt = window.event || e; if (evt.keyCode == 13){alert('11')i//回车事件}}var msg = {fromUserId :"",toUserId  : "",msg       : "",type      : 1};function click1(){if(this.msg.fromUserId==""){alert("昵称不能为空")return}if(this.msg.toUserId == ""){alert("聊天对象不能为空")return}$("#bu").hide();var that = thiswindow.CHAT={socket: null,init: function(){if(window.WebSocket){var m = document.getElementById("nm");CHAT.socket = new WebSocket("ws://47.107.235.154:8080/ws/"+m.value);CHAT.socket.onopen = function(){console.log("连接建立成功...")},CHAT.socket.onclose = function(){CHAT.socket.close()console.log("连接关闭...")},CHAT.socket.onerror = function(){console.log("连接发生错误...")},CHAT.socket.onmessage = function(e){console.log("接收到消息..."+e.data);var reveiveMsg = document.getElementById("reveiveMsg");var html = reveiveMsg.innerHTML;reveiveMsg.innerHTML = "<span style='background-color: green;display:block;text-align:block;margin-right:310px;margin-left:310px;float:left;'>"+e.data+"</span>"+"<br/>"+html}}else{alert("浏览器不支持websocket协议...")}},chat: function(){var msg = document.getElementById("msgContent");that.msg.msg = msg.valueif(msg.value==""){alert('请输入内容')return }reveiveMsg.innerHTML = "<span style='background-color: skyblue;display:block;text-align:block;margin-right:310px;margin-left:310px;float:right;'>你说: "+msg.value+"</span>"+ "<br/>"+reveiveMsg.innerHTMLthat.msg.type = 1console.log(JSON.stringify(that.msg))CHAT.socket.send(JSON.stringify(that.msg));document.getElementById("msgContent").value = "";},chat1: function(){var msg = document.getElementById("msgContent");that.msg.msg = msg.valueif(msg.value==""){alert('请输入内容')return }that.msg.type = 2console.log(JSON.stringify(that.msg))CHAT.socket.send(JSON.stringify(that.msg));document.getElementById("msgContent").value = "";},}CHAT.init();}function sure(){var msg = document.getElementById("nm");$('#nm').prop("disabled", msg.disabled?false:true)$('#an').prop("value",msg.disabled?"修改":"确定")if(msg.disabled){this.msg.fromUserId = msg.value }}function sure1(){var msg = document.getElementById("nm1");$('#nm1').prop("disabled", msg.disabled?false:true)$('#an1').prop("value",msg.disabled?"修改":"确定")if(msg.disabled){this.msg.toUserId = msg.value}else{CHAT.socket.close()$("#bu").show();this.msg.toUserId = ""document.getElementById("nm1").value = ""}}</script><style>div{border:solid 2px #C9394A;}</style></body>
</html>

结语

本文链接:https://my.lmcjl.com/post/20075.html

展开阅读全文

4 评论

留下您的评论.