最近自己搭建了一个在线的聊天室,利用netty技术开发,实现在线聊天以及群聊功能,包括好友添加等相关功能,目前还在更新中。
1.我是通过springboot+netty实现,通过使用netty4实现,运用这种方式就可以帮助我们实现websocket通信,其中核心功能如下:
public class NettyWebsocketServer implements Runnable{@AutowiredNettyWebsocketChildHandlerInitializer childHandlerInitializer; private static final int PORT=8088;private static final int RCV_ALLOCTOR_SIZE=592048;EventLoopGroup bossGroup=new NioEventLoopGroup();EventLoopGroup workGroup=new NioEventLoopGroup();ServerBootstrap serverBootstrap=new ServerBootstrap();private ChannelFuture channelFuture;public NettyWebsocketServer() {}@Overridepublic void run() {build(); }/*** 启动服务*/public void build() {long startTm=System.currentTimeMillis();try {serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024) //Tcp参数,握手字符长度.childOption(ChannelOption.TCP_NODELAY, true) //设置TCP NO_DELAY 算法 尽量减少发送大文件包.childOption(ChannelOption.SO_KEEPALIVE, true) // 针对子线程的配置 保持长连接.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(RCV_ALLOCTOR_SIZE)) // 配置固定长度接收缓存内存分配.childHandler(childHandlerInitializer);channelFuture = serverBootstrap.bind(PORT).sync();// 监听关闭channelFuture.channel().closeFuture().sync();long endTm=System.currentTimeMillis();log.info("服务器启动完成,耗时:[{}]毫秒,已在端口[{}]阻塞",endTm-startTm,PORT);}catch (Exception e) {bossGroup.shutdownGracefully();workGroup.shutdownGracefully();}}public void close() {channelFuture.channel().close();Future<?> bossFuture=bossGroup.shutdownGracefully();Future<?> workFuture=workGroup.shutdownGracefully();try {bossFuture.await();workFuture.await();}catch (Exception e) {// TODO: handle exception}}public ChannelHandler getChannelHandler() {return childHandlerInitializer;}
}
这个类是websocket的服务端。
2.接下来还需要websocket处理器。
@Component
@Sharable
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame>{private final ChatService chatService;public NettyWebSocketServerHandler(ChatService chatService) {this.chatService=chatService;}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {doHandler(ctx, frame);}private void doHandler(ChannelHandlerContext ctx, WebSocketFrame frame) {if(frame instanceof CloseWebSocketFrame) {try { WebSocketServerHandshaker handshaker=Constant.webSocketHandshakerMap.get(ctx.channel().id().asLongText());//WebSocketServerHandshaker handshaker=JSONObject.parseObject(jedis.get(ctx.channel().id().asLongText()), WebSocketServerHandshaker.class);if(handshaker==null) {sendErrorMessage(ctx, "该用户已离线或不存在该连接");}else {handshaker.close(ctx.channel(), ((CloseWebSocketFrame)frame).retain()); //增加消息的引用计数}}finally {} return;}//ping请求if(frame instanceof PingWebSocketFrame) {ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));return ;}if(!(frame instanceof TextWebSocketFrame)) {sendErrorMessage(ctx, "不支持二进制文件");return ;}String request=((TextWebSocketFrame) frame).text();JSONObject params=null;try {params=JSONObject.parseObject(request);log.info("收到服务器消息:[{}]",params);}catch (Exception e) {sendErrorMessage(ctx, "转换出错");log.info("参数转换错误");}if(null==params) {sendErrorMessage(ctx, "参数为空");log.warn("参数为空");return ;}String msgType=(String)params.get("type");switch (msgType) {case "REGISTER":chatService.register(params, ctx);break;case "SINGLE_SENDING": //发消息个单个人chatService.sendOne(params, ctx);break;case "GROUP_SENDING": //群发chatService.sendGroup(params, ctx);break;case "FILE_MSG_SINGLE_SENDING": //发文件给单个人break;case "FILE_MSG_GROUP_SENDING": //群发文件default:break;}}/*** 发送错误消息给客户端* @param ctx* @param msg*/public void sendErrorMessage(ChannelHandlerContext ctx,String msg) {String result=new ApiResult().failed(msg).toString();ctx.channel().writeAndFlush(new TextWebSocketFrame(result)); //通知所有已经连接的WebSocket客户端新的客户端}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {if(ctx!=null) {//Constant.webSocketHandshakerMap.remove(Constant.onlineUserMap.get(ctx.channel().id().asLongText()));//Constant.onlineUserMap.remove(String.valueOf(user.getUserID())); //jedis.del(context.channel().id().asLongText());//jedis.del(String.valueOf(user.getUserID())+"_online");}chatService.remove(ctx);//String userChannel=jedis.get(String.valueOf(user.getUserID())+"_online");//ChannelHandlerContext context=JSONObject.parseObject(userChannel, ChannelHandlerContext.class);log.info("已离线");}/*** 出现异常触发*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {cause.printStackTrace();ctx.close();}
}
当然还有其他一些类,http请求处理器,需要将http协议升级为websocket协议,这里就不一一写了。
3.下面是我实现后的界面展示:
1)登录页
2)登录后的聊天界面:
大家也可以自己登录看看,不过需要先注册后才可以登录。
测试账号:116 密码:123456
网站地址: 炫彩聊天室