天天看點

基于netty-socket.io給指定用戶端發送消息

前言:

1、使用netty-socketio搭建socketIo服務;

2、client連接配接socketIo服務時,儲存下該client對象;

3、使用TestController,向指定client推送消息;

一、netty-socket.io版本;

<dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>1.7.16</version>
</dependency>
           

二、建立socketIoServer執行個體;

/**
	 * 建立socketIOServer執行個體
	 * @return
	 */
	@Bean
	public SocketIOServer socketIOServer() {
		com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        config.setHostname(host);
        config.setPort(port);
        setAuthorizationListener(config);
        return new SocketIOServer(config);
	}
           

三、socketIo client連接配接校驗說明;

設想是通過服務端頒發token給client頁面,當client頁面進行連接配接時,将服務端頒發的token傳遞回來。服務端通過client傳遞的一系列參數,取出服務端儲存的token,與傳遞的token做驗證。

本文未做上述邏輯校驗,僅是将client傳遞的參數列印在控制台。

/**
	 * 設定socketio client連接配接時的安全校驗
	 * @param config
	 */
	private void setAuthorizationListener(com.corundumstudio.socketio.Configuration config) {
		config.setAuthorizationListener(new AuthorizationListener() {
			@Override
			public boolean isAuthorized(HandshakeData data) {
				
				String userId = data.getSingleUrlParam("userId");
				String pageSign = data.getSingleUrlParam("pageSign");
				String token = data.getSingleUrlParam("token");
				System.out.println("userId:" + userId + ",pageSign:" + pageSign + ",token: " + token);
				
				return true;
			}
		});
	}
           

四、啟用netty-socket.io注解功能;

隻有打開了SpringAnnotationScanner,netty-socket.io的注解才會生效。

/**
	 * 開啟netty socketio的注解功能
	 * @param socketServer
	 * @return
	 */
	@Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }
           

五、通過校驗的client連接配接儲存下來;

儲存client的時候,辨別是哪個使用者在哪個頁面發起的連接配接,以便後續給指定使用者的指定頁面發送消息。注意:未處理同一個使用者同時在多個浏覽器登入的情況(可将token作為連接配接辨別的一部分)。

@OnConnect
	public void onConnect(SocketIOClient client) {
		this.socketClientComponent.storeClientId(client);
		System.out.println("用戶端連接配接:" + getParamsFromClient(client));
	}
           

六、client斷開連接配接時,從已連接配接池中移除掉;

@OnDisconnect
	public void onDisconnect(SocketIOClient client) {
		this.socketClientComponent.delClientId(client);
		System.out.println("用戶端斷開:" + getParamsFromClient(client));
	}
           

七、client儲存說明;

有效client目前是根據userId+pageSign作為連接配接唯一辨別,将client對象儲存在本地緩存中。本地緩存有撐爆的可能,正确的做法應該是同一個使用者在同一個浏覽器登入中,不管在多少個頁面下,使用的是同一個socket。根據不同eventName事件名稱來區分不同頁面。服務端通過socketIoClient的sessionId來擷取到真實的socketIoClient對象(socketIOServer.getClient(client.getSessionId()))。

/**
 * socketio client 操作元件
 * @author haishui211
 */
@Component
public class SocketClientComponent {
	
	private Map<String, SocketIOClient> clients = new HashMap<String, SocketIOClient>();
	
	/**
	 * 儲存socketio client 用戶端
	 * @param userId
	 * @param client
	 */
	public void storeClientId(SocketIOClient client) {
		clients.put(getKeyFromClient(client), client);
	}
	
	/**
	 * 移除socketio client 用戶端
	 */
	public void delClientId(SocketIOClient client) {
		clients.remove(getKeyFromClient(client));
	}
	
	/**
	 * 給指定client發送指定事件的資料
	 * @param businessName
	 * @param data
	 */
	public void send(String userId, String pageSign, String businessName, Map<String, Object> data) {
		SocketIOClient client = clients.get(getKey(userId, pageSign));
		if(client != null) {
			client.sendEvent(businessName, data);
		}
	}
	
	private String getKeyFromClient(SocketIOClient client) {
		HandshakeData data = client.getHandshakeData();
		String userId = data.getSingleUrlParam("userId");
		String pageSign = data.getSingleUrlParam("pageSign");
		return getKey(userId, pageSign);
	}
	
	private String getKey(String userId, String pageSign) {
		return "userId:" + userId + ":pageSign:" + pageSign;
	} 
}
           

八、testController模拟消息發送;

@RestController
@RequestMapping("/test")
public class TestController {
	
	@Autowired
	private SocketClientComponent socketClientComponent;
	
	@PostMapping("/push")
	public void push(@RequestBody Map<String, Object> data) {
		String eventName = (String) data.get("eventName");
		String userId = (String) data.get("userId");
		String pageSign = (String) data.get("pageSign");
		socketClientComponent.send(userId, pageSign, eventName, data);
	}
}
           

九、頁面用戶端;

1、引用的Js;
<script
  src="http://code.jquery.com/jquery-3.4.1.js"
  integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
  crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>

2、連接配接發起;
var socket =  io.connect('http://localhost:9999?userId=12311&pageSign=firstPage&token=1234');
 
        socket.on('connect', function() {
            output('<span class="connect-msg">成功連接配接到服務端!</span>');
        });
         
        socket.on('testEvent', function(data) {
            output(JSON.stringify(data));
        });
         
        socket.on('disconnect', function() {
            output('<span class="disconnect-msg">The client has disconnected!</span>');
        });
           

十、測試資料;

{
	"hhhh":"1234",
	"eventName":"testEvent",
	"pageSign":"firstPage",
	"userId":"12311"
}
           

結果:

基于netty-socket.io給指定用戶端發送消息

十一、demo位址;

https://github.com/haishui211/springRep.git

項目名:nettySocketIoDemo001,html頁面放在了static目錄