当前位置: 首页> 娱乐> 明星 > netty编程之基于websocket实现聊天功能

netty编程之基于websocket实现聊天功能

时间:2025/7/14 15:09:13来源:https://blog.csdn.net/wang0907/article/details/141822639 浏览次数:0次

写在前面

源码 。
本文看下netty如何通过websocket实现聊天功能。

类似于实现http server,netty实现websocket也很简单,同样使用对应的编码器和解码器就行了,相关的有HttpServerCodec,HttpObjectAggregator,ChunkedWriteHandler。

1:编码

1.1: netty websocket server

server类:

package com.dahuyou.netty.chat.domain.server;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.net.InetSocketAddress;@Component("nettyServer")
public class NettyServer {private Logger logger = LoggerFactory.getLogger(NettyServer.class);//配置服务端NIO线程组private final EventLoopGroup parentGroup = new NioEventLoopGroup(); //NioEventLoopGroup extends MultithreadEventLoopGroup Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));private final EventLoopGroup childGroup = new NioEventLoopGroup();private Channel channel;public ChannelFuture bing(InetSocketAddress address) {ChannelFuture channelFuture = null;try {ServerBootstrap b = new ServerBootstrap();b.group(parentGroup, childGroup).channel(NioServerSocketChannel.class)    //非阻塞模式.option(ChannelOption.SO_BACKLOG, 128).childHandler(new MyChannelInitializer());channelFuture = b.bind(address).syncUninterruptibly();channel = channelFuture.channel();} catch (Exception e) {logger.error(e.getMessage());} finally {if (null != channelFuture && channelFuture.isSuccess()) {logger.info("netty server start done. {}");} else {logger.error("netty server start error. {}");}}return channelFuture;}public void destroy() {if (null == channel) return;channel.close();parentGroup.shutdownGracefully();childGroup.shutdownGracefully();}public Channel getChannel() {return channel;}}

MyChannelInitializer:

package com.dahuyou.netty.chat.domain.server;import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;import java.nio.charset.Charset;public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel channel) {channel.pipeline().addLast("http-codec", new HttpServerCodec());channel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));channel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());// 在管道中添加我们自己的接收数据实现方法channel.pipeline().addLast(new MyServerHandler());}}

其中设置了支持websocket相关的编解码器是关键。
MyServerHandler:

package com.dahuyou.netty.chat.domain.server;import com.alibaba.fastjson.JSON;
import com.dahuyou.netty.chat.pojo.ClientMsgProtocol;
import com.dahuyou.netty.chat.types.util.ChannelHandler;
import com.dahuyou.netty.chat.types.util.MsgUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyServerHandler extends ChannelInboundHandlerAdapter {private Logger logger = LoggerFactory.getLogger(MyServerHandler.class);private WebSocketServerHandshaker handshaker;/*** 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {SocketChannel channel = (SocketChannel) ctx.channel();logger.info("链接报告开始");logger.info("链接报告信息:有一客户端链接到本服务端");logger.info("链接报告IP:{}", channel.localAddress().getHostString());logger.info("链接报告Port:{}", channel.localAddress().getPort());logger.info("链接报告完毕");ChannelHandler.channelGroup.add(ctx.channel());}/*** 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据*/@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {logger.info("客户端断开链接{}", ctx.channel().localAddress().toString());ChannelHandler.channelGroup.remove(ctx.channel());}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//httpif (msg instanceof FullHttpRequest) {FullHttpRequest httpRequest = (FullHttpRequest) msg;if (!httpRequest.decoderResult().isSuccess()) {DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);// 返回应答给客户端if (httpResponse.status().code() != 200) {ByteBuf buf = Unpooled.copiedBuffer(httpResponse.status().toString(), CharsetUtil.UTF_8);httpResponse.content().writeBytes(buf);buf.release();}// 如果是非Keep-Alive,关闭连接ChannelFuture f = ctx.channel().writeAndFlush(httpResponse);if (httpResponse.status().code() != 200) {f.addListener(ChannelFutureListener.CLOSE);}return;}WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws:/" + ctx.channel() + "/websocket", null, false);handshaker = wsFactory.newHandshaker(httpRequest);if (null == handshaker) {WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());} else {handshaker.handshake(ctx.channel(), httpRequest);}return;}//wsif (msg instanceof WebSocketFrame) {WebSocketFrame webSocketFrame = (WebSocketFrame) msg;//关闭请求if (webSocketFrame instanceof CloseWebSocketFrame) {handshaker.close(ctx.channel(), (CloseWebSocketFrame) webSocketFrame.retain());return;}//ping请求if (webSocketFrame instanceof PingWebSocketFrame) {ctx.channel().write(new PongWebSocketFrame(webSocketFrame.content().retain()));return;}//只支持文本格式,不支持二进制消息if (!(webSocketFrame instanceof TextWebSocketFrame)) {throw new Exception("仅支持文本格式");}String request = ((TextWebSocketFrame) webSocketFrame).text();System.out.println("服务端收到:" + request);ClientMsgProtocol clientMsgProtocol = JSON.parseObject(request, ClientMsgProtocol.class);//1请求个人信息if (1 == clientMsgProtocol.getType()) {ctx.channel().writeAndFlush(MsgUtil.buildMsgOwner(ctx.channel().id().toString()));return;}//群发消息if (2 == clientMsgProtocol.getType()) {TextWebSocketFrame textWebSocketFrame = MsgUtil.buildMsgAll(ctx.channel().id().toString(), clientMsgProtocol.getMsgInfo());ChannelHandler.channelGroup.writeAndFlush(textWebSocketFrame);}}}/*** 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();logger.info("异常信息:\r\n" + cause.getMessage());}}

对解码后的websocket消息按照不同的消息类型做了不同的处理,工作中如果有类似需求的话,这里也会是我们写代码的主要地方咯。

1.2: 通过spring boot启动netty websocket server

package com.dahuyou.netty.chat.trigger;import com.dahuyou.netty.chat.domain.server.NettyServer;
import io.netty.channel.ChannelFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import java.net.InetSocketAddress;@SpringBootApplication
@ComponentScan("com.dahuyou.netty")
public class NettyApplication implements CommandLineRunner {@Value("${netty.host}")private String host;@Value("${netty.port}")private int port;@Autowiredprivate NettyServer nettyServer;public static void main(String[] args) {SpringApplication.run(NettyApplication.class, args);}@Overridepublic void run(String... args) throws Exception {InetSocketAddress address = new InetSocketAddress(host, port);ChannelFuture channelFuture = nettyServer.bing(address);Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.destroy()));channelFuture.channel().closeFuture().syncUninterruptibly();}}

1.3: websocket client

<!--
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-->
<title>大忽悠</title><script type="text/javascript" src="res/js/jquery.min.js"></script>
<script type="text/javascript" src="res/js/jquery.serialize-object.min.js"></script>
<script type="text/javascript" src="res/js/index.js"></script><style>
body{background-image:url(res/img/1121416_071010Daniel12.jpg);background-repeat:no-repeat;background-size:cover;font-family:”微软雅黑”;
}#chatDiv{position: relative;margin:0 auto;margin-top:150px;width:839px;height:667px;background-color:#CCCCCC;border-radius:3px;-moz-border-radius:3px;
}</style></head><body><div id="chatDiv"><!-- left --><div style="width:60px; height:667px; background-color:#2D2B2A; float:left;"><!-- 头像 --><div style="width:35px; height:35px; margin:0 auto; margin-top:19px; margin-left:12px; float:left; border:1px solid #666666;border-radius:3px;-moz-border-radius:3px;"><img src="res/img/Snipaste_2024-09-02_14-29-46.png" width="35px" height="35px"/></div><!-- 聊天 --><div style="width:28px; height:28px; margin:0 auto; margin-top:25px; margin-left:16px; float:left;"><img src="res/img/chat.png" width="28px" height="28px"/></div><!-- 好友 --><div style="width:28px; height:28px; margin:0 auto; margin-top:20px; margin-left:16px; float:left;"><img src="res/img/friend.png" width="28px" height="28px"/></div><!-- 收藏 --><div style="width:28px; height:28px; margin:0 auto; margin-top:20px; margin-left:16px; float:left;"><img src="res/img/collection.png" width="28px" height="28px"/></div><!-- 设置 --><div style="width:20px; height:20px; margin:0 auto; margin-left:20px; float:left; position:absolute;bottom:0; margin-bottom:12px;"><img src="res/img/set.png" width="20px" height="20px"/></div></div><!-- center --><div style="width:250px; height:667px; background-color:#EBE9E8; float:left;"><div style=" background-image:url(res/img/search.png); background-repeat:no-repeat;margin:0 auto; margin-top:25px; padding-top:5px; padding-bottom:5px; width:210px; background-color:#DBD9D8;border-radius:3px;-moz-border-radius:3px; float:left; margin-left:20px; font-size:12px; color:#333333;text-indent:27px;">找呀找呀找朋友!!!</div><!-- friendList --><div id="friendList" style="float:left; margin-top:5px;"><div style="width:250px; height:65px;"><table style="width:240px; height:60px; margin:0 auto; margin-top:2px; background-color:#E5E5E5;"><tr><td rowspan="2" width="50"><img src="res/img/2487997a9f274d86a61a8dffff3e0070_wang0907.jpg" width="50px" height="50px" style="border-radius:3px;-moz-border-radius:3px;"/></td><td style="color:#333333; text-indent:5px; font-size:12px; vertical-align:bottom; font-weight:bolder;">大忽悠</td></tr><tr><td style="color:#999999; text-indent:5px; font-size:10px;">你好,这里是大忽悠有限责任公司</td></tr></table></div></div></div><!-- chat --><div id="chat" style="width:529px; height:667px; background-color:#F5F5F5; float:right;"><div style="width:16px; height:16px; background-image:url(res/img/exit.png); background-repeat:no-repeat; float:right; margin-top:10px; margin-right:30px;"></div><div style="width:16px; height:16px; background-image:url(res/img/min.png); background-repeat:no-repeat; float:right; margin-top:10px; margin-right:12px;"></div><div style="border-bottom:1px #E7E7E7 solid;width:509px; padding-top:0px; padding-left:20px; padding-bottom:10px; font-size:18px; font-weight:bolder;float:left;">大忽悠(6666666)</div><!-- 会话区域 begin --><div id="show" style="width:529px; height:450px; float:left;overflow-y:scroll;"><!-- 消息块;系统 --><div class="msgBlockSystem" style="margin-left:30px; margin-top:15px; width:340px; height:auto; margin-bottom:15px; float:left;"><div class="msgBlock_userHeadImg" style="float:left; width:35px; height:35px;border-radius:3px;-moz-border-radius:3px; background-color:#FFFFFF;"><img class="point" src="res/img/Snipaste_2024-09-02_14-29-46.png" width="35px" height="35px" style="border-radius:3px;-moz-border-radius:3px;"/></div><div class="msgBlock_channelId" style="float:left; width:100px; margin-top:-5px; margin-left:10px; padding-bottom:2px; font-size:10px;">大忽悠</div><div class="msgBlock_msgInfo" style="height:auto;width:280px;float:left;margin-left:12px; margin-top:4px;border-radius:3px;-moz-border-radius:3px; "><div style="width:4px; height:20px; background-color:#CC0000; float:left;border-radius:3px;-moz-border-radius:3px;"></div><div class="point" style="float:left;width:260px; padding:7px; background-color:#FFFFFF; border-radius:3px;-moz-border-radius:3px; height:auto; font-size:12px;display:block;word-break: break-all;word-wrap: break-word;">这里是大忽悠有限责任公司,你可别被我忽悠了<hr/><img width="260" height="260" src="res/img/2487997a9f274d86a61a8dffff3e0070_wang0907.jpg" /></div></div></div><!-- 消息块;好友 --><div class="msgBlockFriendClone" style=" display:none; margin-left:30px; margin-top:15px; width:340px; height:auto; margin-bottom:15px; float:left;"><div class="msgBlock_userHeadImg" style="float:left; width:35px; height:35px;border-radius:3px;-moz-border-radius:3px; background-color:#FFFFFF;"><img class="headPoint" src="res/img/head5.jpg" width="35px" height="35px" style="border-radius:3px;-moz-border-radius:3px;"/></div><div class="msgBlock_channelId" style="float:left; width:100px; margin-top:-5px; margin-left:10px; padding-bottom:2px; font-size:10px;"><!-- 名称 --></div><div class="msgBlock_msgInfo" style="height:auto;width:280px;float:left;margin-left:12px; margin-top:4px;border-radius:3px;-moz-border-radius:3px; "><div style="width:4px; height:20px; background-color:#CC0000; float:left;border-radius:3px;-moz-border-radius:3px;"></div><div class="msgPoint" style="float:left;width:260px; padding:7px; background-color:#FFFFFF; border-radius:3px;-moz-border-radius:3px; height:auto; font-size:12px;display:block;word-break: break-all;word-wrap: break-word;"><!-- 信息 --></div></div></div><!-- 消息块;自己 --><div class="msgBlockOwnerClone" style=" display:none; margin-right:30px; margin-top:15px; width:340px; height:auto; margin-bottom:15px; float:right;"><div style="float:right; width:35px; height:35px;border-radius:3px;-moz-border-radius:3px; background-color:#FFFFFF;"><img class="headPoint" src="res/img/head3.jpg" width="35px" height="35px" style="border-radius:3px;-moz-border-radius:3px;"/></div><div class="msgBlock_msgInfo" style="height:auto;width:280px;float:left;margin-left:12px; margin-top:4px;border-radius:3px;-moz-border-radius:3px; "><div class="msgPoint" style="float:left;width:260px; padding:7px; background-color:#FFFFFF; border-radius:3px;-moz-border-radius:3px; height:auto; font-size:12px;display:block;word-break: break-all;word-wrap: break-word;"><!-- 信息 --></div><div style="width:4px; height:20px; background-color:#CC0000; float:right;border-radius:3px;-moz-border-radius:3px;"></div></div></div><span id="msgPoint"></span></div><!-- 会话区域 end --><div style="width:100%; height:2px; float:left; background-color:#CCCCCC;"></div><div style="margin:0 auto; width:100%; height:149px; margin-top:5px;  background-color:#FFFFFF; float:left;"><textarea id="sendBox" style="font-size:14px; border:0; width:499px; height:80px; outline:none; padding:15px;font-family:”微软雅黑”;resize: none;"></textarea><div style="margin-top:20px; float:right; margin-right:35px; padding:5px; padding-left:15px; padding-right:15px; font-size:12px; background-color:#F5F5F5;border-radius:3px;-moz-border-radius:3px; cursor:pointer;" onclick="javascript:util.send();">发送(S)</div></div></div></div></body>
</html>

2:测试

运行:
在这里插入图片描述
在这里插入图片描述
发送一个消息:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
酱!!!

写在后面

参考文章列表

idea中的html到浏览器中的乱码问题解决方案 。

关键字:netty编程之基于websocket实现聊天功能

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: