spring Boot实战之netty-socketio实现简单聊天室(给指定用户推送消息)
网上好多例子都是群发的,本文实现一对一的发送,给指定客户端进行消息推送
1、本文使用到netty-socketio开源库,以及MySQL,所以首先在pom.xml中添加相应的依赖库
- <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, 添加端口及主机数据库连接等相关配置,
- 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的相关配置信息
- 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);
- //该处可以用来进行身份验证
- config.setAuthorizationListener(new AuthorizationListener() {
- @Override
- public boolean isAuthorized(HandshakeData data) {
- //http://localhost:8081?username=test&password=test
- //例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息,本文不做身份验证
- // 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、添加消息结构类MessageInfo.java
- package com.xiaofangtech.sunt.message;
- public class MessageInfo {
- //源客户端id
- private String sourceClientId;
- //目标客户端id
- private String targetClientId;
- //消息类型
- private String msgType;
- //消息内容
- 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、添加查询数据库接口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事件,当客户端发起连接时调用,本文中将clientid与sessionid存入数据库
- //方便后面发送消息时查找到对应的目标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事件,客户端断开连接时调用,刷新客户端信息
- @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);
- }
- }
- //消息接收入口,当接收到消息后,查找发送目标客户端,并且向该客户端发送消息,且给自己发送消息
- @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、工程结构
10、运行测试
1) 添加基础数据,数据库中预置3个客户端testclient1,testclient2,testclient3
2) 创建客户端文件index.html,index2.html,index3.html分别代表testclient1 testclient2 testclient3三个用户
本文直接修改的https://github.com/mrniko/netty-socketio-demo/tree/master/client 中的index.html文件
其中clientid为发送者id, targetclientid为目标方id,本文简单的将发送方和接收方写死在html文件中
使用 以下代码进行连接
- io.connect('http://localhost:8081?clientid='+clientid);
index.html 文件内容如下
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <title>Demo Chat</title>
- <link href="bootstrap.css" 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、本例测试时
testclient1 发送消息给 testclient2
testclient2 发送消息给 testclient1
testclient3发送消息给testclient1
运行结果如下