前言

基于SpringBoot,借助Netty控制长链接,使用WebSocket协议做一个实时的聊天室。

项目效果

项目统一登录路径:http://localhost:8080/chat/netty
用户名随机生成,离线调用异步方法,数据写操作,登录显示历史聊天消息


GitHub

项目名:InChat
项目地址:https://github.com/UncleCatMy...
项目介绍:基于Netty4与SpringBoot,聊天室WebSocket(文字图片)加API调用Netty长链接执行发送消息(在线数、用户列表)、Iot物联网-MQTT协议、TCP/IP协议单片机通信,异步存储聊天数据

代码实操讲解

随机命名工具类

public class RandomNameUtil {private static Random ran = new Random();private final static int delta = 0x9fa5 - 0x4e00 + 1;public static char getName(){return (char)(0x4e00 + ran.nextInt(delta));}
}

配置文件yml

spring:datasource:driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://localhost:3306/nettychat?characterEncoding=utf-8&useSSL=falsejpa:show-sql: truenetty:port: 8090    #监听端口bossThread: 2 #线程数workerThread: 2 #线程数keepalive: true #保持连接backlog: 100

数据库准备

SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for user_msg
-- ----------------------------
DROP TABLE IF EXISTS `user_msg`;
CREATE TABLE `user_msg` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`msg` varchar(255) DEFAULT NULL,`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4;-- ----------------------------
-- Records of user_msg
-- ----------------------------
INSERT INTO `user_msg` VALUES ('1', '亪', '今天不开心', '2018-08-14 14:26:02', '2018-08-14 14:26:02');
INSERT INTO `user_msg` VALUES ('2', '祐', '不错呀', '2018-08-14 15:09:40', '2018-08-14 15:09:40');
INSERT INTO `user_msg` VALUES ('3', '搈', '开心 开心', '2018-08-14 15:09:40', '2018-08-14 15:09:40');
INSERT INTO `user_msg` VALUES ('4', '兇', '可以的,后面再做个深入一点的', '2018-08-14 15:18:35', '2018-08-14 15:18:35');
INSERT INTO `user_msg` VALUES ('5', '倎', '开源这个项目', '2018-08-14 15:18:35', '2018-08-14 15:18:35');
INSERT INTO `user_msg` VALUES ('6', '蝡', '1-someting', '2018-08-14 15:24:28', '2018-08-14 15:24:28');
INSERT INTO `user_msg` VALUES ('7', '弔', '不行呀', '2018-08-14 15:24:29', '2018-08-14 15:24:29');
INSERT INTO `user_msg` VALUES ('8', '習', '可以的', '2018-08-14 15:26:03', '2018-08-14 15:26:03');
INSERT INTO `user_msg` VALUES ('9', '蔫', '开源这个项目', '2018-08-14 15:26:03', '2018-08-14 15:26:03');

dataObject与JPA数据DAO

@Data
@Entity
@DynamicUpdate
public class UserMsg implements Serializable {private static final long serialVersionUID = 4133316147283239759L;@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;private String name;private String msg;private Date createTime;private Date updateTime;}
public interface UserMsgRepository extends JpaRepository<UserMsg,Integer> {
//本次未使用到自定义方法,JPA原生即可
}

NoSQL模拟环境

我没有去配置虚拟机环境,就本地模拟了

保存用户名称与链接随机ID

@Component
public class LikeRedisTemplate {private Map<Object,Object> RedisMap = new ConcurrentHashMap<>();public void save(Object id,Object name){RedisMap.put(id,name);}public void delete(Object id){RedisMap.remove(id);}public Object get(Object id){return RedisMap.get(id);}
}

聊天内容临时存储

@Component
public class LikeSomeCacheTemplate {private Set<UserMsg> SomeCache = new LinkedHashSet<>();public void save(Object user,Object msg){UserMsg userMsg = new UserMsg();userMsg.setName(String.valueOf(user));userMsg.setMsg(String.valueOf(msg));SomeCache.add(userMsg);}public Set<UserMsg> cloneCacheMap(){return SomeCache;}public void clearCacheMap(){SomeCache.clear();}
}

异步任务处理

@Component
public class MsgAsyncTesk {@Autowiredprivate LikeSomeCacheTemplate cacheTemplate;@Autowiredprivate UserMsgRepository userMsgRepository;@Asyncpublic Future<Boolean> saveChatMsgTask() throws Exception{
//        System.out.println("启动异步任务");Set<UserMsg> set = cacheTemplate.cloneCacheMap();for (UserMsg item:set){//保存用户消息userMsgRepository.save(item);}//清空临时缓存cacheTemplate.clearCacheMap();return new AsyncResult<>(true);}}

netty核心

配置类

@Data
@Component
@ConfigurationProperties(prefix = "netty")
public class NettyAccountConfig {private int port;private int bossThread;private int workerThread;private boolean keepalive;private int backlog;
}

核心消息处理类

@Component
@Qualifier("textWebSocketFrameHandler")
@ChannelHandler.Sharable
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Autowiredprivate LikeRedisTemplate redisTemplate;@Autowiredprivate LikeSomeCacheTemplate cacheTemplate;@Autowiredprivate MsgAsyncTesk msgAsyncTesk;@Overrideprotected void channelRead0(ChannelHandlerContext ctx,TextWebSocketFrame msg) throws Exception {Channel incoming = ctx.channel();String uName = String.valueOf(redisTemplate.get(incoming.id()));for (Channel channel : channels) {//将当前每个聊天内容进行存储System.out.println("存储数据:"+uName+"-"+msg.text());cacheTemplate.save(uName,msg.text());if (channel != incoming){channel.writeAndFlush(new TextWebSocketFrame("[" + uName + "]" + msg.text()));} else {channel.writeAndFlush(new TextWebSocketFrame("[you]" + msg.text() ));}}}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {System.out.println(ctx.channel().remoteAddress());String uName = String.valueOf(RandomNameUtil.getName());  //用来获取一个随机的用户名,可以用其他方式代替//新用户接入Channel incoming = ctx.channel();for (Channel channel : channels) {channel.writeAndFlush(new TextWebSocketFrame("[新用户] - " + uName + " 加入"));}redisTemplate.save(incoming.id(),uName);   //存储用户channels.add(ctx.channel());}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {Channel incoming = ctx.channel();String uName = String.valueOf(redisTemplate.get(incoming.id()));//用户离开for (Channel channel : channels) {channel.writeAndFlush(new TextWebSocketFrame("[用户] - " + uName + " 离开"));}redisTemplate.delete(incoming.id());   //删除用户channels.remove(ctx.channel());}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {Channel incoming = ctx.channel();System.out.println("用户:"+redisTemplate.get(incoming.id())+"在线");}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {Channel incoming = ctx.channel();System.out.println("用户:"+redisTemplate.get(incoming.id())+"掉线");msgAsyncTesk.saveChatMsgTask();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {Channel incoming = ctx.channel();System.out.println("用户:" + redisTemplate.get(incoming.id()) + "异常");cause.printStackTrace();ctx.close();}
}

定义Initializer

@Component
@Qualifier("somethingChannelInitializer")
public class NettyWebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {@Autowiredprivate TextWebSocketFrameHandler textWebSocketFrameHandler;@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new HttpServerCodec());pipeline.addLast(new HttpObjectAggregator(65536));pipeline.addLast(new ChunkedWriteHandler());pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));pipeline.addLast(textWebSocketFrameHandler);   //这里不能使用new,不然在handler中不能注入依赖}}

启动创建Netty基本组件

@Component
public class NettyConfig {@Autowiredprivate NettyAccountConfig nettyAccountConfig;@Bean(name = "bossGroup", destroyMethod = "shutdownGracefully")public NioEventLoopGroup bossGroup(){return new NioEventLoopGroup(nettyAccountConfig.getBossThread());}@Bean(name = "workerGroup", destroyMethod = "shutdownGracefully")public NioEventLoopGroup workerGroup(){return new NioEventLoopGroup(nettyAccountConfig.getWorkerThread());}@Bean(name = "tcpSocketAddress")public InetSocketAddress tcpPost(){return new InetSocketAddress(nettyAccountConfig.getPort());}@Bean(name = "tcpChannelOptions")public Map<ChannelOption<?>, Object> tcpChannelOptions(){Map<ChannelOption<?>, Object> options = new HashMap<ChannelOption<?>, Object>();options.put(ChannelOption.SO_KEEPALIVE, nettyAccountConfig.isKeepalive());options.put(ChannelOption.SO_BACKLOG, nettyAccountConfig.getBacklog());return options;}@Autowired@Qualifier("somethingChannelInitializer")private NettyWebSocketChannelInitializer nettyWebSocketChannelInitializer;@Bean(name = "serverBootstrap")public ServerBootstrap bootstrap(){ServerBootstrap b = new ServerBootstrap();b.group(bossGroup(), workerGroup()).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.DEBUG)).childHandler(nettyWebSocketChannelInitializer);Map<ChannelOption<?>, Object> tcpChannelOptions = tcpChannelOptions();Set<ChannelOption<?>> keySet = tcpChannelOptions.keySet();for (@SuppressWarnings("rawtypes") ChannelOption option : keySet) {b.option(option, tcpChannelOptions.get(option));}return b;}
}

服务启动协助类

@Data
@Component
public class TCPServer {@Autowired@Qualifier("serverBootstrap")private ServerBootstrap serverBootstrap;@Autowired@Qualifier("tcpSocketAddress")private InetSocketAddress tcpPort;private Channel serverChannel;public void start() throws Exception {serverChannel =  serverBootstrap.bind(tcpPort).sync().channel().closeFuture().sync().channel();}@PreDestroypublic void stop() throws Exception {serverChannel.close();serverChannel.parent().close();}
}

项目启动

@SpringBootApplication
@EnableScheduling //启动异步任务
public class NettychatApplication {public static void main(String[] args) throws Exception{ConfigurableApplicationContext context = SpringApplication.run(NettychatApplication.class, args);//注入NettyConfig 获取对应BeanNettyConfig nettyConfig = context.getBean(NettyConfig.class);//注入TCPServer 获取对应BeanTCPServer tcpServer = context.getBean(TCPServer.class);//启动websocket的服务tcpServer.start();}}

GitHub

项目名:InChat
项目地址:https://github.com/UncleCatMy...
项目介绍:基于Netty4与SpringBoot,聊天室WebSocket(文字图片)加API调用Netty长链接执行发送消息(在线数、用户列表)、Iot物联网-MQTT协议、TCP/IP协议单片机通信,异步存储聊天数据


如果本文对你有所帮助,欢迎关注个人技术公众号

用SpringBoot集成Netty开发一个基于WebSocket的聊天室相关推荐

  1. java websocket netty_用SpringBoot集成Netty开发一个基于WebSocket的聊天室

    前言 基于SpringBoot,借助Netty控制长链接,使用WebSocket协议做一个实时的聊天室. 项目效果 项目统一登录路径:http://localhost:8080/chat/netty ...

  2. SpringBoot + Vue 实现基于 WebSocket 的聊天室(单聊)

    前言 在前一篇文章SpringBoot 集成 STOMP 实现一对一聊天的两种方法中简单介绍了如何利用 STOMP 实现单聊,本文则将以一个比较完整的示例展示实际应用,不过本文并未使用 STOMP,而 ...

  3. java开发websocket聊天室_java实现基于websocket的聊天室

    [实例简介] java实现基于websocket的聊天室 [实例截图] [核心代码] chatMavenWebapp └── chat Maven Webapp ├── pom.xml ├── src ...

  4. 基于WebSocket实现聊天室(Node)

    基于WebSocket实现聊天室(Node) WebSocket是基于TCP的长连接通信协议,服务端可以主动向前端传递数据,相比比AJAX轮询服务器,WebSocket采用监听的方式,减轻了服务器压力 ...

  5. SSM(五)基于webSocket的聊天室

    SSM(五)基于webSocket的聊天室 前言 不知大家在平时的需求中有没有遇到需要实时处理信息的情况,如站内信,订阅,聊天之类的.在这之前我们通常想到的方法一般都是采用轮训的方式每隔一定的时间向服 ...

  6. 基于webSocket的聊天室

    前言 不知大家在平时的需求中有没有遇到需要实时处理信息的情况,如站内信,订阅,聊天之类的.在这之前我们通常想到的方法一般都是采用轮训的方式每隔一定的时间向服务器发送请求从而获得最新的数据,但这样会浪费 ...

  7. 基于 WebSocket 的聊天室项目(下)

    1.创建一个聊天室的数据库 注意修改上一篇准备工作中写的配置文件中的数据库名称:: 创建数据库,如果不存在`websocket_chatroom`默认字符集`utf8`; 使用`websocket_c ...

  8. 基于python的聊天室_Python实现文字聊天室

    你是否想过用所学的Python开发一个图形界面的聊天室程序啊? 像这样的: image 如果你想开发这样一个有点怀旧风格的聊天程序,那么可以接着看: 要开发这个聊天程序,你需要具备以下知识点: asy ...

  9. Springboot集成netty实例

    一.简介 netty是jboss提供的一个java开源框架,netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可用性的网络服务器和客户端程序.也就是说netty是一个基于n ...

  10. workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)...

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

最新文章

  1. Linux-pstree命令
  2. numpy矩阵计算GPU加速库:cupy
  3. sql 存储过程中top 后面跟参数的问题
  4. mongodb 输出数组字段_三分钟 mongodb 开发快速上手
  5. 《PHP综合开发环境》(NuSphere PhpED v5.6.5615 Win32)[压缩包]
  6. C++(STL):04---智能指针之weak_ptr
  7. LeetCode 1863. 找出所有子集的异或总和再求和(DFS)
  8. 微服务系列:服务注册与发现的实现原理、及实现优劣势比较
  9. Puma单栏博客Typecho主题
  10. word 职称计算机考试大纲,全国职称计算机考试Word2003大纲.doc
  11. 自定义Login注解
  12. Pickle Finance发起两项新提案欲增聘开发者并永久保留0xkoffee的任命
  13. Dropping Balls UVA - 679(二叉树的遍历)
  14. 如何判断一件事重要还是紧急?
  15. IAP之boot实现
  16. 【RAC】RAC相关基础知识
  17. 牛客网SQL刷题四-电商场景(某东商城)
  18. Python发邮件报错 554, b‘HL:ITC 0,163
  19. mms彩信发送 java_Android编程之非调用系统界面实现发送彩信的方法(MMS)
  20. 易我分区大师无损扩容c盘的步骤详解:

热门文章

  1. MFC中如何在CMainFrame类中访问CxxxView视图类中的成员
  2. spring的自动装配(default-autowire=byName)
  3. 动态规划-最大的正方形面积
  4. leetcode刷题日记-供暖器
  5. GIS案例练习-----------第八天
  6. 数组中的奇数排在偶数组前面
  7. pandas 下一行减去上一行
  8. python 删除满足条件的行
  9. 从load加载数据类型是struct说起
  10. endnote x8安装办法