socket.io client + socketio-netty server簡析
一、 背景:
現在實時web訊息推送一般會用到websocket,但是由於此技術並沒有推廣開來,所以各瀏覽器對其支援也不同,例如下圖顯示了各類瀏覽器的支援情況。
粉紅色區域表示不支援Websocket。
至於IE瀏覽器,以及部分陳舊的桌面瀏覽器,可以選擇Flashsocket作為替代品。
客戶端如何把Websocket和Flashsocket結合在一起使用,可借鑑開源專案:web-socket-js (客戶端的Websocket實現方案)
其思路和socket.io大致一致,僅僅提供對websocket的客戶端的簡單包裝,若是Android 上原生瀏覽器,沒有安裝Flash Lite情況下,就無能為力了。
因此,僅僅憑藉Websocket + Flashsocket,是不能夠完成跨瀏覽器、統一客戶端API的重任。
在這種情況下,socket.io就應運而生了。
二、socket.io的介紹以及優點
socket.io 支援以下通訊通道傳輸協議:
- WebSocket
- Adobe® Flash® Socket
- AJAX long polling
- AJAX multipart streaming
- Forever Iframe
- JSONP Polling
socket.io並不是簡單的封裝了websocket,可以說websocket只是socket.io的其中一部分。使用socket.io客戶端和伺服器端雙方約定適合當前瀏覽器的最佳通訊通道,然後正常通訊。並且還可以手動指定使用某種方式進行通訊。只要我們指定socket.io transport的引數,就可以做到心裡有數。
在socketio-netty伺服器端配置:
transports = websocket,flashsocket,htmlfile,xhr-polling,jsonp-polling
在客戶端,簡單定義地址:
var socket = io.connect('http://localhost:9000');
在不遠的將來,桌面版瀏覽器可能升級了最新版本的websocket草案,導致客戶端原生的websocket協議無法被識別時,可使用Flashsocket作為替代品。但總會有一種通訊協議墊底,可以保證正常的運轉。
socket.io即提供了node.js伺服器端又提供了客戶端的整體解決方案,而socketio-netty則是基於JAVA伺服器端,支援最新socket.io client最新版規範。對JAVA程式設計人員來講,可以不用學習node.js,從而多了一個選擇。
注:附1中簡單的介紹了集中不同通訊協議的優缺點。有興趣的可以檢視。
三、socket.io事件和方法簡單介紹
Socket.IO內建了一些預設事件,我們在設計事件的時候應該避開預設的事件名稱,並靈活運用這些預設事件。
伺服器端事件:
server.addEventListener("con", Object.class, new DataListener<Object>() ;
新增監聽的事件以及該時間中傳輸訊息的實體物件,服務端可以通過此方法來捕捉到client傳輸過來的Message請求;
server.addConnectListener(new ConnectListener();
新增連線監聽時間,當client連線時會首先觸發此事件,server可以在這裡進行一些初始化操作。
server.addDisconnectListener( new DisconnectListener();
新增斷開連線監聽時間,當client斷開連線時會首先觸發此事件,server可以在這裡進行一些斷線操作。(包括關閉瀏覽器,主動斷開,掉線等任何斷開連線的情況)。客戶端事件:
connect:連線成功disconnect:斷開連線message:同伺服器端message事件 在這裡要提下客戶端socket發起連線時的順序。當第一次連線時,事件觸發順序為:connecting->connect;當失去連線時,事件觸發順序為:disconnect->reconnecting(可能進行多次)->connecting->reconnect->connect。
注意:重新整理瀏覽器時,相當與客戶端首先disconnect然後重新建立一次connect。並且此時socket.io會預設重連之前斷開的連線。
客戶端常用方法:
socket.emit()和socket.on();這兩種都可以用來傳送訊息,只是在寫法上有稍微不同。
socket.emit('action');表示傳送了一個action命令,命令是字串的,也可以這麼寫: socket.on('action',function(){...});
socket.emit('action',data);表示傳送了一個action命令,還有data資料,也可以這麼寫: socket.on('action',function(data){...});
socket.emit(action,arg1,arg2); 表示傳送了一個action命令,還有兩個資料,也可以這麼寫: socket.on('action',function(arg1,arg2){...});
在emit方法中包含回撥函式,例如:
socket.emit('action',data, function(arg1,arg2){...} );那麼這裡面有一個回撥函式可以在另一端呼叫,也可以這麼寫:socket.on('action',function(data,fn){ fn('a','b') ; });
上面的data資料可以有0個或者多個,相應的在另一端改變function中引數的個數即可,function中的引數個數和順序應該和傳送時一致
上面的fn表示另一個端傳遞過來的引數,是個函式,寫fn('a','b') ;會回撥函式執行。一次傳送不應該寫多個回撥,否則只有最後一個起效,回撥應作為最後一個引數。
四、簡單的程式碼應用(聊天室)
服務端:
package com.corundumstudio.socketio.demo;
import java.util.HashMap;
import java.util.Map;
import com.corundumstudio.socketio.listener.*;
import com.corundumstudio.socketio.*;
public class ChatLauncher {
private static Map<String,String> clients;
public static void main(String[] args) throws InterruptedException {
Configuration config = new Configuration();
//設定主機名稱UR了
config.setHostname("localhost");
//設定埠,此處必須設定,不設定啟動時會報錯
config.setPort(80);
final SocketIOServer server = new SocketIOServer(config);
server.addEventListener("con", ChatObject.class, new DataListener<ChatObject>() {
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
data.setMessage("使用者處於線上狀態");
System.out.println("uuid"+client.getSessionId());
server.getBroadcastOperations().sendEvent("chatevent", data);
//回撥當前clicent的函式
//client.sendEvent("chatevent", data);
}
});
server.addEventListener("discon", ChatObject.class, new DataListener<ChatObject>() {
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
data.setMessage("使用者離線了");
server.getBroadcastOperations().sendEvent("chatevent", data);
System.out.println("離線了!!!");
client.disconnect();
//回撥當前clicent的函式
//client.sendEvent("chatevent", data);
}
});
//當client連線時觸發此事件
server.addConnectListener(new ConnectListener(){
@Override
public void onConnect(SocketIOClient client) {
System.out.println(client.getSessionId()+"線上了!!!");
}
});
//當client離線時觸發此事件
server.addDisconnectListener( new DisconnectListener(){
@Override
public void onDisconnect(SocketIOClient client) {
System.out.println(client.getSessionId()+"離線了!!!");
}
});
//監聽埠上的chatevent事件
server.addEventListener("chatevent", ChatObject.class, new DataListener<ChatObject>() {
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
server.getBroadcastOperations().sendEvent("chatevent", data);
//回撥當前clicent的函式
//client.sendEvent("chatevent", data);
}
});
server.start();
Thread.sleep(Integer.MAX_VALUE);
server.stop();
}
}
客戶端:
<!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 userName = 'user' + Math.floor((Math.random()*1000)+1);
//頁面開啟時,進行連線,url後為埠資訊,若不設定預設為連線80埠
var socket = io.connect('http://localhost:80');
//監聽連線事件
socket.on('connect', function() {
//var jsonObject = {userName: userName,
// message: '1'};
//socket.emit("con",jsonObject);
output('<span class="connect-msg">Client has connected to the server!</span>');
});
//監聽chatevent事件
socket.on('chatevent', function(data) {
output('<span class="username-msg">' + data.userName + ':</span> ' + data.message);
});
//監聽離線事件
socket.on('disconnect', function() {
output('<span class="disconnect-msg">The client has disconnected!</span>');
});
function sendDisconnect() {
socket.disconnect();
// var jsonObject = {userName: userName,
// message: '2'};
//socket.emit("discon",jsonObject);
}
function sendReconnect(){
socket.connect();
}
function sendMessage() {
var message = $('#msg').val();
$('#msg').val('');
var jsonObject = {userName: userName,
message: message};
//1、通過send方法傳送資料
//chatSocket.json.send(jsonObject);
//2、原生emit方法
socket.emit('chatevent', 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" οnsubmit="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>
<button type="button" onClick="sendReconnect()" class="btn">REconnect</button>
</form>
</body>
</html>
執行服務端.java檔案後,開啟客戶端頁面即可進行聊天。如下圖:
圖 1
圖 2
注意:完整程式碼在方的參考區域,有興趣的可以下載下來一起學習。
五、學習心得
在html5之前,因為http協議是無狀態的,要實現 瀏覽器與伺服器的實時通訊,如果不使用 flash、applet 等瀏覽器外掛的話,就需要定期輪詢伺服器來獲取資訊。這造成了一定的延遲和大量的網路通訊。隨著HTML5 的出現,這一情況有望徹底改觀,這與需要實現與伺服器實時通訊的應用來說,是一種極大的進步,而且隨和使用者對網路實時通訊的要求越來越高,學習sockey.io這門技術也是很有發揮空間的。
附1:
不斷地輪詢(俗稱“拉”,polling)是獲取實時訊息的一個手段:Ajax 隔一段時間(通常使用 JavaScript 的 setTimeout 函式)就去伺服器查詢是否有改變,從而進行增量式的更新。但是間隔多長時間去查詢成了問題,因為效能和即時性造成了嚴重的反比關係。間隔太短,連續不斷的請求會沖垮伺服器,間隔太長,務器上的新資料就需要越多的時間才能到達客戶機。
-
- 優點:服務端邏輯簡單;
- 缺點:其中大多數請求可能是無效請求,在大量使用者輪詢很頻繁的情況下對伺服器的壓力很大;
- 應用:併發使用者量少,而且要求訊息的實時性不高,一般很少採用;
- 長輪詢技術(long-polling):客戶端向伺服器傳送Ajax請求,伺服器接到請求後hold住連線,直到有新訊息或超時(設定)才返回響應資訊並關閉連線,客戶端處理完響應資訊後再向伺服器傳送新的請求。
-
- 優點:實時性高,無訊息的情況下不會進行頻繁的請求;
- 缺點:伺服器維持著連線期間會消耗資源;
- 基於Iframe及htmlfile的流(streaming)方式:iframe流方式是在頁面中插入一個隱藏的iframe,利用其src屬性在伺服器和客戶端之間建立一條長連結,伺服器向iframe傳輸資料(通常是HTML,內有負責插入資訊的javascript),來實時更新頁面。
-
- 優點:訊息能夠實時到達;
- 缺點:伺服器維持著長連線期會消耗資源;
- 外掛提供socket方式:比如利用Flash XMLSocket,Java Applet套介面,Activex包裝的socket。
-
- 優點:原生socket的支援,和PC端和移動端的實現方式相似;
- 缺點:瀏覽器端需要裝相應的外掛;
- WebSocket:是HTML5開始提供的一種瀏覽器與伺服器間進行全雙工通訊的網路技術。
-
- 優點:更好的節省伺服器資源和頻寬並達到實時通訊;
- 缺點:目前還未普及,瀏覽器支援不好;
綜上,考慮到瀏覽器相容性和效能問題,採用長輪詢(long-polling)是一種比較好的方式。
參考程式碼:
https://github.com/mrniko/netty-socketio
https://github.com/mrniko/netty-socketio-demo
參考文章:
http://tech.qq.com/a/20120521/000296.htm
http://www.cnblogs.com/luxiaoxun/p/4279997.html
http://blog.csdn.net/mengxianhua/article/details/44778733
http://blog.csdn.net/kelong_xhu/article/details/50846483 (socketio分散式)
相關文章
- MySQL client server 協議MySqlclientServer協議
- Client does not support authentication protocol requested by server; consider upgrading MySQL clientclientProtocolServerIDEMySql
- 淺析kubernetes中client-go InformerclientGoORM
- client: c#+protobuf, server: golang+protobufclientC#ServerGolang
- 淺析kubernetes中client-go structure01clientGoStruct
- Client Side Cache 和 Server Side Cache 的區別clientIDEServer
- Connect SQL Server from Linux Client using Windows Authentication and troubleshoot stepsSQLServerLinuxclientWindows
- python3:利用socket建立的一個簡單的聊天client端和server端例項PythonclientServer
- BootAnimation簡析boot
- CGLib 簡析CGLib
- RectTransform簡析ORM
- MongoDB 簡析MongoDB
- OpenTelemetry 簡析
- KafkaProducer 簡析Kafka
- KafkaBroker 簡析Kafka
- 《初識TCP》iOS、macOS實現socket client與socket serverTCPiOSMacclientServer
- NXNSAttack漏洞簡析
- ObjC Runtime簡析OBJ
- ObjC RunLoop簡析OBJOOP
- Flux模式簡析UX模式
- 在 go websocket server 與 javascript websocket client 互動中使用 flatbuffersGoWebServerJavaScriptclient
- ERROR3948: Loading local data is disabled - this must be enabled on both the client and server sidesErrorclientServerIDE
- socket.io websocketWeb
- Spring註解簡析Spring
- vuex 原始碼簡析Vue原始碼
- Entitas實現簡析
- minipack 原始碼簡析原始碼
- ObjC block簡析(一)OBJBloC
- Flutter渲染流程簡析Flutter
- 高版本mysql訪問出現Client does not support authentication protocol requested by server;MySqlclientProtocolServer
- adb server version (31) doesn’t match this client (36); killing… 的解決方法Serverclient
- 元素 offset client scroll 相關屬性簡介client
- socket.IO通訊
- Socket.IO 入門
- 使用威聯通做UPS server(NUT) 配置 linux和windows的 UPS clientServerLinuxWindowsclient
- [MDP.AspNetCore] 實作OAuth協定SSO Server/Client專案範例NetCoreOAuthServerclient
- Unknown initial character set index ‘255‘ received from server. Initial client character set can beIndexServerclient
- Spring系列.AOP原理簡析Spring