Spring Boot實(shí)戰(zhàn)之netty-socketio實(shí)現(xiàn)簡單聊天室(給指定用戶推送消息)
網(wǎng)上好多例子都是群發(fā)的,本文實(shí)現(xiàn)一對一的發(fā)送,給指定客戶端進(jìn)行消息推送
1、本文使用到netty-socketio開源庫,以及MySQL,所以首先在pom.xml中添加相應(yīng)的依賴庫
<dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>1.7.11</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
2、修改application.properties, 添加端口及主機(jī)數(shù)據(jù)庫連接等相關(guān)配置,
wss.server.port=8081 wss.server.host=localhost spring.datasource.url = jdbc:mysql://127.0.0.1:3306/springlearn spring.datasource.username = root spring.datasource.password = root spring.datasource.driverClassName = com.mysql.jdbc.Driver # Specify the DBMS spring.jpa.database = MYSQL # Show or not log for each sql query spring.jpa.show-sql = true # Hibernate ddl auto (create, create-drop, update) spring.jpa.hibernate.ddl-auto = update # Naming strategy spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy # stripped before adding them to the entity manager) spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
3、修改Application文件,添加nettysocket的相關(guān)配置信息
package com.xiaofangtech.sunt; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import com.corundumstudio.socketio.AuthorizationListener; import com.corundumstudio.socketio.Configuration; import com.corundumstudio.socketio.HandshakeData; import com.corundumstudio.socketio.SocketIOServer; import com.corundumstudio.socketio.annotation.SpringAnnotationScanner; @SpringBootApplication public class NettySocketSpringApplication { @Value("${wss.server.host}") private String host; @Value("${wss.server.port}") private Integer port; @Bean public SocketIOServer socketIOServer() { Configuration config = new Configuration(); config.setHostname(host); config.setPort(port); //該處可以用來進(jìn)行身份驗(yàn)證 config.setAuthorizationListener(new AuthorizationListener() { @Override public boolean isAuthorized(HandshakeData data) { //http://localhost:8081?username=test&password=test //例如果使用上面的鏈接進(jìn)行connect,可以使用如下代碼獲取用戶密碼信息,本文不做身份驗(yàn)證 // String username = data.getSingleUrlParam("username"); // String password = data.getSingleUrlParam("password"); return true; } }); final SocketIOServer server = new SocketIOServer(config); return server; } @Bean public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) { return new SpringAnnotationScanner(socketServer); } public static void main(String[] args) { SpringApplication.run(NettySocketSpringApplication.class, args); } }
4、添加消息結(jié)構(gòu)類MessageInfo.java
package com.xiaofangtech.sunt.message; public class MessageInfo { //源客戶端id private String sourceClientId; //目標(biāo)客戶端id private String targetClientId; //消息類型 private String msgType; //消息內(nèi)容 private String msgContent; public String getSourceClientId() { return sourceClientId; } public void setSourceClientId(String sourceClientId) { this.sourceClientId = sourceClientId; } public String getTargetClientId() { return targetClientId; } public void setTargetClientId(String targetClientId) { this.targetClientId = targetClientId; } public String getMsgType() { return msgType; } public void setMsgType(String msgType) { this.msgType = msgType; } public String getMsgContent() { return msgContent; } public void setMsgContent(String msgContent) { this.msgContent = msgContent; } }
5、添加客戶端信息,用來存放客戶端的sessionid
package com.xiaofangtech.sunt.bean; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.NotNull; @Entity @Table(name="t_clientinfo") public class ClientInfo { @Id @NotNull private String clientid; private Short connected; private Long mostsignbits; private Long leastsignbits; private Date lastconnecteddate; public String getClientid() { return clientid; } public void setClientid(String clientid) { this.clientid = clientid; } public Short getConnected() { return connected; } public void setConnected(Short connected) { this.connected = connected; } public Long getMostsignbits() { return mostsignbits; } public void setMostsignbits(Long mostsignbits) { this.mostsignbits = mostsignbits; } public Long getLeastsignbits() { return leastsignbits; } public void setLeastsignbits(Long leastsignbits) { this.leastsignbits = leastsignbits; } public Date getLastconnecteddate() { return lastconnecteddate; } public void setLastconnecteddate(Date lastconnecteddate) { this.lastconnecteddate = lastconnecteddate; } }
6、添加查詢數(shù)據(jù)庫接口ClientInfoRepository.java
package com.xiaofangtech.sunt.repository; import org.springframework.data.repository.CrudRepository; import com.xiaofangtech.sunt.bean.ClientInfo; public interface ClientInfoRepository extends CrudRepository<ClientInfo, String>{ ClientInfo findClientByclientid(String clientId); }
7、添加消息處理類MessageEventHandler.Java
package com.xiaofangtech.sunt.message; import java.util.Date; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.corundumstudio.socketio.AckRequest; import com.corundumstudio.socketio.SocketIOClient; import com.corundumstudio.socketio.SocketIOServer; import com.corundumstudio.socketio.annotation.OnConnect; import com.corundumstudio.socketio.annotation.OnDisconnect; import com.corundumstudio.socketio.annotation.OnEvent; import com.xiaofangtech.sunt.bean.ClientInfo; import com.xiaofangtech.sunt.repository.ClientInfoRepository; @Component public class MessageEventHandler { private final SocketIOServer server; @Autowired private ClientInfoRepository clientInfoRepository; @Autowired public MessageEventHandler(SocketIOServer server) { this.server = server; } //添加connect事件,當(dāng)客戶端發(fā)起連接時(shí)調(diào)用,本文中將clientid與sessionid存入數(shù)據(jù)庫 //方便后面發(fā)送消息時(shí)查找到對應(yīng)的目標(biāo)client, @OnConnect public void onConnect(SocketIOClient client) { String clientId = client.getHandshakeData().getSingleUrlParam("clientid"); ClientInfo clientInfo = clientInfoRepository.findClientByclientid(clientId); if (clientInfo != null) { Date nowTime = new Date(System.currentTimeMillis()); clientInfo.setConnected((short)1); clientInfo.setMostsignbits(client.getSessionId().getMostSignificantBits()); clientInfo.setLeastsignbits(client.getSessionId().getLeastSignificantBits()); clientInfo.setLastconnecteddate(nowTime); clientInfoRepository.save(clientInfo); } } //添加@OnDisconnect事件,客戶端斷開連接時(shí)調(diào)用,刷新客戶端信息 @OnDisconnect public void onDisconnect(SocketIOClient client) { String clientId = client.getHandshakeData().getSingleUrlParam("clientid"); ClientInfo clientInfo = clientInfoRepository.findClientByclientid(clientId); if (clientInfo != null) { clientInfo.setConnected((short)0); clientInfo.setMostsignbits(null); clientInfo.setLeastsignbits(null); clientInfoRepository.save(clientInfo); } } //消息接收入口,當(dāng)接收到消息后,查找發(fā)送目標(biāo)客戶端,并且向該客戶端發(fā)送消息,且給自己發(fā)送消息 @OnEvent(value = "messageevent") public void onEvent(SocketIOClient client, AckRequest request, MessageInfo data) { String targetClientId = data.getTargetClientId(); ClientInfo clientInfo = clientInfoRepository.findClientByclientid(targetClientId); if (clientInfo != null && clientInfo.getConnected() != 0) { UUID uuid = new UUID(clientInfo.getMostsignbits(), clientInfo.getLeastsignbits()); System.out.println(uuid.toString()); MessageInfo sendData = new MessageInfo(); sendData.setSourceClientId(data.getSourceClientId()); sendData.setTargetClientId(data.getTargetClientId()); sendData.setMsgType("chat"); sendData.setMsgContent(data.getMsgContent()); client.sendEvent("messageevent", sendData); server.getClient(uuid).sendEvent("messageevent", sendData); } } }
8、添加ServerRunner.java
package com.xiaofangtech.sunt.message; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import com.corundumstudio.socketio.SocketIOServer; @Component public class ServerRunner implements CommandLineRunner { private final SocketIOServer server; @Autowired public ServerRunner(SocketIOServer server) { this.server = server; } @Override public void run(String... args) throws Exception { server.start(); } }
9、工程結(jié)構(gòu)
10、運(yùn)行測試
1) 添加基礎(chǔ)數(shù)據(jù),數(shù)據(jù)庫中預(yù)置3個(gè)客戶端testclient1,testclient2,testclient3
2) 創(chuàng)建客戶端文件index.html,index2.html,index3.html分別代表testclient1 testclient2 testclient3三個(gè)用戶
本文直接修改的https://github.com/mrniko/netty-socketio-demo/tree/master/client 中的index.html文件
其中clientid為發(fā)送者id, targetclientid為目標(biāo)方id,本文簡單的將發(fā)送方和接收方寫死在html文件中
使用 以下代碼進(jìn)行連接
io.connect('http://localhost:8081?clientid='+clientid);
index.html 文件內(nèi)容如下
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Demo Chat</title> <link href="bootstrap.css" rel="external nofollow" rel="stylesheet"> <style> body { padding:20px; } #console { height: 400px; overflow: auto; } .username-msg {color:orange;} .connect-msg {color:green;} .disconnect-msg {color:red;} .send-msg {color:#888} </style> <script src="js/socket.io/socket.io.js"></script> <script src="js/moment.min.js"></script> <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> <script> var clientid = 'testclient1'; var targetClientId= 'testclient2'; var socket = io.connect('http://localhost:8081?clientid='+clientid); socket.on('connect', function() { output('<span class="connect-msg">Client has connected to the server!</span>'); }); socket.on('messageevent', function(data) { output('<span class="username-msg">' + data.sourceClientId + ':</span> ' + data.msgContent); }); socket.on('disconnect', function() { output('<span class="disconnect-msg">The client has disconnected!</span>'); }); function sendDisconnect() { socket.disconnect(); } function sendMessage() { var message = $('#msg').val(); $('#msg').val(''); var jsonObject = {sourceClientId: clientid, targetClientId: targetClientId, msgType: 'chat', msgContent: message}; socket.emit('messageevent', jsonObject); } function output(message) { var currentTime = "<span class='time'>" + moment().format('HH:mm:ss.SSS') + "</span>"; var element = $("<div>" + currentTime + " " + message + "</div>"); $('#console').prepend(element); } $(document).keydown(function(e){ if(e.keyCode == 13) { $('#send').click(); } }); </script> </head> <body> <h1>Netty-socketio Demo Chat</h1> <br/> <div id="console" class="well"> </div> <form class="well form-inline" onsubmit="return false;"> <input id="msg" class="input-xlarge" type="text" placeholder="Type something..."/> <button type="button" onClick="sendMessage()" class="btn" id="send">Send</button> <button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button> </form> </body> </html>
3、本例測試時(shí)
testclient1 發(fā)送消息給 testclient2
testclient2 發(fā)送消息給 testclient1
testclient3發(fā)送消息給testclient1
運(yùn)行結(jié)果如下
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Cloud如何切換Ribbon負(fù)載均衡模式
這篇文章主要介紹了Spring Cloud如何切換Ribbon負(fù)載均衡模式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12MyBatis超詳細(xì)講解如何實(shí)現(xiàn)分頁功能
MyBatis-Plus?是一個(gè)?Mybatis?增強(qiáng)版工具,在?MyBatis?上擴(kuò)充了其他功能沒有改變其基本功能,為了簡化開發(fā)提交效率而存在,本篇文章帶用它實(shí)現(xiàn)分頁功能2022-03-03SpringBoot應(yīng)用監(jiān)控Actuator使用隱患及解決方案
SpringBoot的Actuator 模塊提供了生產(chǎn)級別的功能,比如健康檢查,審計(jì),指標(biāo)收集,HTTP 跟蹤等,幫助我們監(jiān)控和管理Spring Boot 應(yīng)用,本文將給大家介紹SpringBoot應(yīng)用監(jiān)控Actuator使用隱患及解決方案,需要的朋友可以參考下2024-07-07Java編程倒計(jì)時(shí)實(shí)現(xiàn)方法示例
這篇文章主要介紹了Java編程倒計(jì)時(shí)實(shí)現(xiàn)的三個(gè)示例,三種實(shí)現(xiàn)方法,具有一定參考價(jià)值,需要的朋友可以了解下。2017-09-09Java?ASM使用logback日志級別動態(tài)切換方案展示
這篇文章主要介紹了Java?ASM使用logback日志級別動態(tài)切換方案展示,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04hibernate通過session實(shí)現(xiàn)增刪改查操作實(shí)例解析
這篇文章主要介紹了hibernate通過session實(shí)現(xiàn)增刪改查操作實(shí)例解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12Spring實(shí)戰(zhàn)之協(xié)調(diào)作用域不同步的Bean操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之協(xié)調(diào)作用域不同步的Bean操作,結(jié)合實(shí)例形式分析了Spring協(xié)調(diào)作用域不同步的Bean相關(guān)配置及使用技巧,需要的朋友可以參考下2019-11-11sentinel?整合spring?cloud限流的過程解析
這篇文章主要介紹了sentinel?整合spring?cloud限流,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03