socket.io是js實(shí)現(xiàn)的隅居,websocket框架苟穆,為了解決瀏覽器不兼容問題而設(shè)計(jì)
socket.io.js下載地址:https://cdnjs.com/libraries/socket.io
常用的方式是削饵,前端使用socket.io.js加叁,后端使用node.js實(shí)現(xiàn)socket.io的接口扮休,可是我們的架構(gòu)后端使用的是java锣枝,所以我使用的是netty-socketio厢拭,基于spring-boot實(shí)現(xiàn);
一.pom.xml中添加依賴
<!-- https://mvnrepository.com/artifact/com.corundumstudio.socketio/netty-socketio -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.11</version>
</dependency>
二.修改SpringBoot啟動(dòng)類
package domain;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import domain.util.YmlConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Spring Boot 應(yīng)用啟動(dòng)類
*
* @author : liangxifeng
* @date : 2018-1-19
*/
// Spring Boot 應(yīng)用的標(biāo)識(shí)
@SpringBootApplication
//如果mybatis中service實(shí)現(xiàn)類中加入事務(wù)注解撇叁,需要此處添加該注解
@EnableTransactionManagement
// mapper 接口類掃描包配置
@MapperScan("domain.dao")
public class AdverCenterApplication extends SpringBootServletInitializer {
//讀取配置文件
@Autowired
private YmlConfig ymlConfig;
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AdverCenterApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(AdverCenterApplication.class, args);
}
/**
* 注冊(cè)netty-socketio服務(wù)端
* @author liangxifeng 2018-07-07
* @return
*/
@Bean
public SocketIOServer socketIOServer() {
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
String os = System.getProperty("os.name");
if(os.toLowerCase().startsWith("win")){ //在本地window環(huán)境測(cè)試時(shí)用localhost
System.out.println("this is windows");
config.setHostname("localhost");
} else {
config.setHostname("192.168.9.209");
}
config.setPort("9092");
/*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;
}
/**
* tomcat啟動(dòng)時(shí)候陨闹,掃碼socket服務(wù)器并注冊(cè)
* @param socketServer
* @return
*/
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
return new SpringAnnotationScanner(socketServer);
}
}
三.在項(xiàng)目服務(wù)啟動(dòng)的時(shí)候啟動(dòng)socket.io服務(wù), 新增ServerRunner.java
package domain.websocketio;
import com.corundumstudio.socketio.SocketIOServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 在項(xiàng)目服務(wù)啟動(dòng)的時(shí)候啟動(dòng)socket.io服務(wù)
* @author liangxifeng 2018-07-07
*/
@Component
@Order(value=1)
@Slf4j
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();
log.info("socket.io啟動(dòng)成功楞捂!");
}
}
四.接收前臺(tái)用戶信息類 MessageInfo.java
package domain.websocketio;
import lombok.ToString;
import org.springframework.stereotype.Component;
/**
* 接收前臺(tái)用戶信息類
* @author liangxifeng 2018-07-07
*/
@Component
@ToString
public class MessageInfo {
String msgContent;
public String getMsgContent() {
return this.msgContent;
}
public void setMsgContent(String msgContent) {
this.msgContent = msgContent;
}
}
消息事件,作為后端與前臺(tái)交互MessageEventHandler.java
package domain.websocketio;
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 domain.websocket.MyWebSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 消息事件正林,作為后端與前臺(tái)交互
* @authoer liangxifeng 2018-07-07
*/
@Component
public class MessageEventHandler {
public static SocketIOServer socketIoServer;
static ArrayList<UUID> listClient = new ArrayList<UUID>();
static final int limitSeconds = 60;
//線程安全的map
public static ConcurrentHashMap<String,SocketIOClient> webSocketMap = new ConcurrentHashMap<String, SocketIOClient>();
@Autowired
public MessageEventHandler(SocketIOServer server) {
this.socketIoServer = server;
}
/**
* 客戶端連接的時(shí)候觸發(fā)泡一,前端js觸發(fā):socket = io.connect("http://192.168.9.209:9092");
* @param client
*/
@OnConnect
public void onConnect(SocketIOClient client) {
String mac = client.getHandshakeData().getSingleUrlParam("mac");
listClient.add(client.getSessionId());
//以mac地址為key,SocketIOClient 為value存入map,后續(xù)可以指定mac地址向客戶端發(fā)送消息
webSocketMap.put(mac,client);
//socketIoServer.getClient(client.getSessionId()).sendEvent("message", "back data");
System.out.println("客戶端:" + client.getSessionId() + "已連接,mac="+mac);
}
/**
* 客戶端關(guān)閉連接時(shí)觸發(fā):前端js觸發(fā):socket.disconnect();
* @param client
*/
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
System.out.println("客戶端:" + client.getSessionId() + "斷開連接");
}
/**
* 自定義消息事件,客戶端js觸發(fā):socket.emit('messageevent', {msgContent: msg}); 時(shí)觸發(fā)
* 前端js的 socket.emit("事件名","參數(shù)數(shù)據(jù)")方法觅廓,是觸發(fā)后端自定義消息事件的時(shí)候使用的,
* 前端js的 socket.on("事件名",匿名函數(shù)(服務(wù)器向客戶端發(fā)送的數(shù)據(jù)))為監(jiān)聽服務(wù)器端的事件
* @param client 客戶端信息
* @param request 請(qǐng)求信息
* @param data 客戶端發(fā)送數(shù)據(jù){msgContent: msg}
*/
@OnEvent(value = "messageevent")
public void onEvent(SocketIOClient client, AckRequest request, MessageInfo data) {
System.out.println("發(fā)來消息:" + data);
//服務(wù)器端向該客戶端發(fā)送消息
//socketIoServer.getClient(client.getSessionId()).sendEvent("messageevent", "你好 data");
client.sendEvent("messageevent","我是服務(wù)器都安發(fā)送的信息");
}
public static void sendBuyLogEvent() { //這里就是向客戶端推消息了
//String dateTime = new DateTime().toString("hh:mm:ss");
for (UUID clientId : listClient) {
if (socketIoServer.getClient(clientId) == null) continue;
socketIoServer.getClient(clientId).sendEvent("enewbuy", "當(dāng)前時(shí)間", 1);
}
}
}
五.目錄結(jié)構(gòu)
image.png
六.html內(nèi)容
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, user-scalable=no">
<title>websocket-java-socketio</title>
<script src="/static/js/socket.io.js"></script></script>
</head>
<body>
<h1>Socket.io Test</h1>
<div><p id="status">Waiting for input</p></div>
<div><p id="message">hello world!</p></div>
<button id="connect" onClick='connect()'/>Connect</button>
<button id="disconnect" onClick='disconnect()'>Disconnect</button>
<button id="send" onClick='send()'/>Send Message</button>
</body>
<script type="text/javascript">
/**
* 前端js的 socket.emit("事件名","參數(shù)數(shù)據(jù)")方法鼻忠,是觸發(fā)后端自定義消息事件的時(shí)候使用的,
* 前端js的 socket.on("事件名",匿名函數(shù)(服務(wù)器向客戶端發(fā)送的數(shù)據(jù)))為監(jiān)聽服務(wù)器端的事件
**/
var socket = io.connect("http://192.168.9.209:9092?mac=2");
var firstconnect = true;
function connect() {
if(firstconnect) {
//socket.on('reconnect', function(){ status_update("Reconnected to Server"); });
//socket.on('reconnecting', function( nextRetry ){ status_update("Reconnecting in "
//+ nextRetry + " seconds"); });
//socket.on('reconnect_failed', function(){ message("Reconnect Failed"); });
//firstconnect = false;
} else {
socket.socket.reconnect();
}
}
//監(jiān)聽服務(wù)器連接事件
socket.on('connect', function(){ status_update("Connected to Server"); });
//監(jiān)聽服務(wù)器關(guān)閉服務(wù)事件
socket.on('disconnect', function(){ status_update("Disconnected from Server"); });
//監(jiān)聽服務(wù)器端發(fā)送消息事件
socket.on('messageevent', function(data) {
message(data)
//console.log("服務(wù)器發(fā)送的消息是:"+data);
});
//斷開連接
function disconnect() {
socket.disconnect();
}
function message(data) {
document.getElementById('message').innerHTML = "Server says: " + data;
}
function status_update(txt){
document.getElementById('status').innerHTML = txt;
}
function esc(msg){
return msg.replace(/</g, '<').replace(/>/g, '>');
}
//點(diǎn)擊發(fā)送消息觸發(fā)
function send() {
//console.log("點(diǎn)擊了發(fā)送消息,開始向服務(wù)器發(fā)送消息")
var msg = "我很好的,是的.";
socket.emit('messageevent', {msgContent: msg});
};
</script>
</html>
七.測(cè)試
-
訪問瀏覽器
socketio-1.jpg -
服務(wù)器端日志內(nèi)容:
socketio-2.jpg
服務(wù)器指定mac地址用戶向客戶端發(fā)送消息
package domain.controller;
import com.corundumstudio.socketio.SocketIOClient;
import domain.domain.DomainResponse;
import domain.domain.Exhibition;
import domain.service.interfaces.exhibition.InsertExhibitionService;
import domain.websocket.PayWebSocket;
import domain.websocketio.MessageEventHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/exhibiton")
public class ExhibitonController {
@Autowired
private InsertExhibitionService insertExhibitionService;
@GetMapping(value = "/sendMsg/{mac}")
/**
* 指定某個(gè)websocket發(fā)送從服務(wù)器發(fā)送到瀏覽器杈绸,服務(wù)器主動(dòng)推送
*/
private DomainResponse testSendMsg(@PathVariable("mac") String mac ) throws Exception{
DomainResponse msg = new DomainResponse(Integer.parseInt(mac),mac,"指定給"+mac+"用戶發(fā)消息");
PayWebSocket payWebSocket = PayWebSocket.payWebSocketMap.get(mac);
if(payWebSocket == null)
{
return new DomainResponse(0,"沒有該用戶",0);
}
payWebSocket.sendMessage(msg);
return msg;
}
/**
* web socketio方式指定用戶發(fā)送消息
* @param mac
* @return
*/
@GetMapping(value = "/socketIo/{mac}")
private DomainResponse socketIoTest(@PathVariable("mac") String mac){
SocketIOClient client = MessageEventHandler.webSocketMap.get(mac);
DomainResponse msg = new DomainResponse(Integer.parseInt(mac),mac,"指定給"+mac+"用戶發(fā)消息");
client.sendEvent("messageevent","haha======++");
return msg;
}
}
測(cè)試
-
指定mac地址為2的用戶發(fā)送消息
image.png -
客戶端會(huì)收到消息
socketio-3.jpg