Springboot+WebSocket+Netty實現(xiàn)在線聊天/群聊系統(tǒng)
一、文章前言
此文主要實現(xiàn)在好友添加、建群、聊天對話、群聊功能,使用Java作為后端語言進(jìn)行支持,界面友好,開發(fā)簡單。
二、開發(fā)流程及工具準(zhǔn)備
2.1、下載安裝IntelliJ IDEA(后端語言開發(fā)工具),Mysql數(shù)據(jù)庫,微信Web開發(fā)者工具。
三、開發(fā)步驟
1.創(chuàng)建maven project
先創(chuàng)建一個名為SpringBootDemo的項目,選擇【New Project】
然后在彈出的下圖窗口中,選擇左側(cè)菜單的【New Project】(注:和2022之前的idea版本不同,這里左側(cè)沒有【Maven】選項,不要選【Maven Archetype】!?。。?,輸入Name(項目名):SpringBootDemo,language選擇【java】,build system選擇【maven】,然后選擇jdk,我這里選的是jdk18.
然后點擊【Create】
2.在project下創(chuàng)建module
點擊右鍵選擇【new】—【Module…】
左側(cè)選擇【Spring initializr】,通過idea中集成的Spring initializr工具進(jìn)行spring boot項目的快速創(chuàng)建。窗口右側(cè):name可根據(jù)自己喜好設(shè)置,group和artifact和上面一樣的規(guī)則,其他選項保持默認(rèn)值即可,【next】
Developer Tools模塊勾選【Spring Boot DevTools】,web模塊勾選【Spring Web】
此時,一個Springboot項目已經(jīng)搭建完成,可開發(fā)后續(xù)功能
3.編寫一個消息實體類、Mapper、service(三層架構(gòu))
@Data public class Chat { @TableId(type = IdType.AUTO) private Long id; private Long userId; private Long targetUserId; private LocalDateTime createTime; private String userName; private String targetUserName; private String content; }
由于我們使用mybatis-plus,所以簡單的增刪改查不用自己寫,框架自帶了,只需要實現(xiàn)或者繼承他的Mapper、Service
4.編寫WebSocket服務(wù)類
@ServerEndpoint("/imserver/{userId}") @Component public class WebSocketService { /** * concurrent包的線程安全Set,用來存放每個客戶端對應(yīng)的MyWebSocket對象。 */ private static ConcurrentHashMap<String, WebSocketService> webSocketMap = new ConcurrentHashMap<>(); /** * 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù) */ private Session session; /** * 接收userId */ private String userId = ""; public static ChatMapper chatMapper = null; /** * 連接建立成功調(diào)用的方法 * <p> * 1.用map存 每個客戶端對應(yīng)的MyWebSocket對象 */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) { this.session = session; this.userId = userId; if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); webSocketMap.put(userId, this); //加入set中 } else { webSocketMap.put(userId, this); //加入set中 } } /** * 報錯 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } /** * 實現(xiàn)服務(wù)器推送到對應(yīng)的客戶端 */ public void sendMessage(String message) { try { this.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } /** * 自定義 指定的userId服務(wù)端向客戶端發(fā)送消息 */ public static void sendInfo(Chat chat) { QueryWrapper<Chat> queryWrapper = new QueryWrapper(); List<Chat> chats=chatMapper.selectList(queryWrapper.lambda() .eq(Chat::getTargetUserId, chat.getTargetUserId()).or().eq(Chat::getUserId, chat.getTargetUserId()).or().eq(Chat::getTargetUserId, chat.getUserId()).or().eq(Chat::getUserId, chat.getUserId())); //log.info("發(fā)送消息到:"+userId+",報文:"+message); if (!StringUtils.isEmpty(chat.getTargetUserId().toString()) && webSocketMap.containsKey(chat.getTargetUserId().toString())) { webSocketMap.get(chat.getUserId().toString()).sendMessage(JSONObject.toJSONString(chats)); webSocketMap.get(chat.getTargetUserId().toString()).sendMessage(JSONObject.toJSONString(chats)); } else { webSocketMap.get(chat.getUserId().toString()).sendMessage(JSONObject.toJSONString(chats)); } } /** * 自定義關(guān)閉 * * @param userId */ public static void close(String userId) { if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); } } /** * 獲取在線用戶信息 * * @return */ public static Map getOnlineUser() { return webSocketMap; }
5.創(chuàng)建控制器Controller
先創(chuàng)建Controller Package
創(chuàng)建一個Controller
輸入類名,選在【Class】
因為要編寫Rest風(fēng)格的Api,要在Controller上標(biāo)注@RestController注解
6.創(chuàng)建具體的Api接口
@RestController public class DemoController { @Autowired private ChatService chatService; @PostMapping("/push") public ResponseEntity<String> pushToWeb(@RequestBody Chat chat) throws IOException { chat.setCreateTime(LocalDateTime.now()); chatService.save(chat); WebSocketService.sendInfo(chat); return ResponseEntity.ok("MSG SEND SUCCESS"); } @GetMapping("/close") public String close(String userId) { WebSocketService.close(userId); return "ok"; } @GetMapping("/getOnlineUser") public Map getOnlineUser() { return WebSocketService.getOnlineUser(); } @GetMapping("/getMessage") public ResponseEntity<List<Chat>> getMessage(String userId) { QueryWrapper<Chat> queryWrapper = new QueryWrapper(); List<Chat> list = chatService. list(queryWrapper.lambda().eq(Chat::getTargetUserId, userId).or().eq(Chat::getUserId, userId)); return ResponseEntity.ok(list); } }
7.編寫netty配置
package com.example.demo.config; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; 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; import org.springframework.stereotype.Component; public class NettyServer { public void start(){ //創(chuàng)建兩個線程組boosGroup和workerGroup,含有的子線程NioEventLoop的個數(shù)默認(rèn)為cpu核數(shù)的兩倍 //boosGroup只是處理鏈接請求,真正的和客戶端業(yè)務(wù)處理,會交給workerGroup完成 EventLoopGroup boosGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //創(chuàng)建服務(wù)器的啟動對象 ServerBootstrap bootstrap = new ServerBootstrap(); //使用鏈?zhǔn)骄幊虂砼渲脜?shù) //設(shè)置兩個線程組 bootstrap.group(boosGroup,workerGroup) //使用NioSctpServerChannel作為服務(wù)器的通道實現(xiàn) .channel(NioServerSocketChannel.class) //初始化服務(wù)器鏈接隊列大小,服務(wù)端處理客戶端鏈接請求是順序處理的,所以同一時間只能處理一個客戶端鏈接 //多個客戶端同時來的時候,服務(wù)端將不能處理的客戶端鏈接請求放在隊列中等待處理 .option(ChannelOption.SO_BACKLOG,1024) //創(chuàng)建通道初始化對象,設(shè)置初始化參數(shù) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { System.out.println("收到到新的鏈接"); //websocket協(xié)議本身是基于http協(xié)議的,所以這邊也要使用http解編碼器 ch.pipeline().addLast(new HttpServerCodec()); //以塊的方式來寫的處理器 ch.pipeline().addLast(new ChunkedWriteHandler()); ch.pipeline().addLast(new HttpObjectAggregator(8192)); ch.pipeline().addLast(new MessageHandler());//添加測試的聊天消息處理類 ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10)); } }); System.out.println("netty server start.."); //綁定一個端口并且同步,生成一個ChannelFuture異步對象,通過isDone()等方法判斷異步事件的執(zhí)行情況 //啟動服務(wù)器(并綁定端口),bind是異步操作,sync方法是等待異步操作執(zhí)行完畢 ChannelFuture cf = bootstrap.bind(1245).sync(); //給cf注冊監(jiān)聽器,監(jiān)聽我們關(guān)心的事件 cf.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (cf.isSuccess()){ System.out.println("監(jiān)聽端口成功"); }else { System.out.println("監(jiān)聽端口失敗"); } } }); //對通道關(guān)閉進(jìn)行監(jiān)聽,closeFuture是異步操作,監(jiān)聽通道關(guān)閉 //通過sync方法同步等待通道關(guān)閉處理完畢,這里會阻塞等待通道關(guān)閉 cf.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); }finally { boosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
8.前端代碼Websocket聊天功能
if (!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if (window.WebSocket) { me.websocket = new WebSocket(me.ws + me.info.id); me.websocket.onmessage = function(event) { var json = JSON.parse(event.data); me.msgListMethod(); console.log(json); }; console.log(me.websocket) me.websocket.onopen = function(event) { console.log("Netty-WebSocket服務(wù)器。。。。。。連接"); }; me.websocket.onerror = function(evt) { console.log('發(fā)生錯誤..., evt'); }; me.websocket.CONNECTING = function(evt) { console.log('正在鏈接中'); }; } else { alert("您的瀏覽器不支持WebSocket協(xié)議!"); } //監(jiān)聽窗口關(guān)閉事件,當(dāng)窗口關(guān)閉時,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。 window.onbeforeunload = function() { if (me.websocket != null) { me.websocket.close(); } };
這里用到了很多消息發(fā)送功能,比如文件、圖片。群聊還可查看群成員功能
以上就是Springboot+WebSocket+Netty實現(xiàn)在線聊天/群聊系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Springboot實現(xiàn)在線聊天的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例
這篇文章主要介紹了SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03SpringCloud服務(wù)的發(fā)現(xiàn)與調(diào)用詳解
在Java微服務(wù)越來越火的今天。幾乎什么公司都在搞微服務(wù)。而使用的比較多的就是Spring?Cloud技術(shù)棧。今天就來研究一下Spring?Cloud中服務(wù)發(fā)現(xiàn)與調(diào)用的基本原理2022-07-07Spring AbstractRoutingDatasource 動態(tài)數(shù)據(jù)源的實例講解
本文介紹如何使用 Spring AbstractRoutingDatasource 基于上下文動態(tài)切換數(shù)據(jù)源,因此我們會讓查找數(shù)據(jù)源邏輯獨立于數(shù)據(jù)訪問之外2021-07-07Java實戰(zhàn)在線選課系統(tǒng)的實現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)一個在線選課系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11