HTTP 協(xié)議有一個缺陷:通信只能由客戶端發(fā)起伤靠。只能是客戶端向服務(wù)器發(fā)出請求捣域,服務(wù)器返回查詢結(jié)果。HTTP 協(xié)議做不到服務(wù)器主動向客戶端推送信息宴合。如果服務(wù)器有連續(xù)的狀態(tài)變化竟宋,客戶端要獲知就非常麻煩。我們只能使用"輪詢"(js-定時器形纺,定時發(fā)送請求驗證),每隔一段時候丘侠,就發(fā)出一個詢問,了解服務(wù)器有沒有新的信息逐样。最典型的場景就是聊天室蜗字。
WebSocket:
它的最大特點就是,服務(wù)器可以主動向客戶端推送信息脂新,客戶端也可以主動向服務(wù)器發(fā)送信息挪捕,是真正的雙向平等對話,屬于[服務(wù)器推送技術(shù)]的一種争便。(前提是保持連接的狀態(tài)<读恪)
(1)建立在 TCP 協(xié)議之上,服務(wù)器端的實現(xiàn)比較容易。
(2)與 HTTP 協(xié)議有著良好的兼容性奏纪。默認端口也是80和443鉴嗤,并且握手階段采用 HTTP 協(xié)議,因此握手時不容易屏蔽序调,能通過各種 HTTP 代理服務(wù)器醉锅。
(3)數(shù)據(jù)格式比較輕量,性能開銷小发绢,通信高效硬耍。
(4)可以發(fā)送文本,也可以發(fā)送二進制數(shù)據(jù)边酒。
(5)沒有同源限制经柴,客戶端可以與任意服務(wù)器通信。
(6)協(xié)議標(biāo)識符是ws(如果加密墩朦,則為wss)坯认,服務(wù)器網(wǎng)址就是 URL。
實現(xiàn)單一客戶端連接:頁面?zhèn)鬟f過來用戶的標(biāo)識介杆,在onMessage()中判定用戶實現(xiàn)單一通訊。
ws://example.com:80/some/path
js部分
<!DOCTYPE html>
<html>
<head>
<title>Java后端WebSocket的Tomcat實現(xiàn)</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">發(fā)送消息</button>
<hr/>
<button onclick="closeWebSocket()">關(guān)閉WebSocket連接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">
var uid = "admin";
var websocket = null;
//判斷當(dāng)前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/WebSocketTest/websocket/"+uid);
}
else {
alert('當(dāng)前瀏覽器 Not support websocket')
}
//連接發(fā)生錯誤的回調(diào)方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發(fā)生錯誤");
};
//連接成功建立的回調(diào)方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
}
//接收到消息的回調(diào)方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關(guān)閉");
}
//監(jiān)聽窗口關(guān)閉事件韭寸,當(dāng)窗口關(guān)閉時春哨,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口恩伺,server端會拋異常赴背。
window.onbeforeunload = function () {
closeWebSocket();
}
//將消息顯示在網(wǎng)頁上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關(guān)閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發(fā)送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
java部分
package test;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
/**
* @ServerEndpoint 注解是一個類層次的注解,它的功能主要是將目前的類定義成一個websocket服務(wù)器端,
* 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務(wù)器端
*/
@ServerEndpoint(value = "/websocket/{param}")//{}中的數(shù)據(jù)代表一個參數(shù)晶渠,多個參數(shù)用/分隔
public class WebSocketTest {
private String uname;
//
// 靜態(tài)變量凰荚,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計成線程安全的褒脯。
private static int onlineCount = 0;
// concurrent包的線程安全Set便瑟,用來存放每個客戶端對應(yīng)的MyWebSocket對象。若要實現(xiàn)服務(wù)端與單一客戶端通信的話番川,可以使用Map來存放到涂,其中Key可以為用戶標(biāo)識
private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
// 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
private Session session;
/**
* 連接建立成功調(diào)用的方法
*
* @param session
* 可選的參數(shù)颁督。session為與某個客戶端的連接會話践啄,需要通過它來給客戶端發(fā)送數(shù)據(jù)
*/
@OnOpen
public void onOpen(@PathParam(value = "param") String uid, Session session) {
this.session = session;
this.uname = uid;
System.out.println(uid);
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在線數(shù)加1
System.out.println("有新連接加入!當(dāng)前在線人數(shù)為" + getOnlineCount());
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); // 從set中刪除
subOnlineCount(); // 在線數(shù)減1
System.out.println("有一連接關(guān)閉沉御!當(dāng)前在線人數(shù)為" + getOnlineCount());
}
/**
* 收到客戶端消息后調(diào)用的方法
*
* @param message
* 客戶端發(fā)送過來的消息
* @param session
* 可選的參數(shù)
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的消息:" + message);
// 群發(fā)消息
for (WebSocketTest item : webSocketSet) {
try {
if (item.uname.equals(this.uname)) {
item.sendMessage(item.uname + ":" + message);
}
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 發(fā)生錯誤時調(diào)用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("發(fā)生錯誤");
error.printStackTrace();
}
/**
* 這個方法與上面幾個方法不一樣屿讽。沒有用注解,是根據(jù)自己需要添加的方法吠裆。
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
// this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketTest.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketTest.onlineCount--;
}
}
以上代碼來自網(wǎng)絡(luò)伐谈±猛辏可以實現(xiàn)前段和后端的相互通訊。
--------------------------------------------------------------分割線------------------------------------------------------------------------
而我在項目中衩婚,需要后臺與另一個服務(wù)端(非本服務(wù)端)建立連接窜护,并相互通訊。本人初學(xué)非春,總結(jié)和參考了網(wǎng)上各位前輩的方法柱徙,實現(xiàn)如下:
后臺代碼:
public String getAccess(String access) {
System.out.println("dao:"+access);
Session session = null;
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://localhost:8095//testWebsocket/websocket/userSever/8080";
try {
session = container.connectToServer(MyClient.class, URI.create(uri));
} catch (DeploymentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
/**發(fā)送頁面?zhèn)鬟^來的數(shù)據(jù)*/
try {
session.getBasicRemote().sendText(access);
} catch (IOException e) {
e.printStackTrace();
}
String acc = "";
while(true) {
acc = MyCache.catche2.get("a1");
if(acc!=null) {
return acc;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@ClientEndpoint
public class MyClient {
@OnOpen
public void onOpen(Session session) {
System.out.println("Connected to endpoint: " + session.getBasicRemote());
try {
session.getBasicRemote().sendText("Hello");
} catch (IOException ex) {
}
}
@OnMessage
public void onMessage(String message) {
System.out.println(message);
MyCache.catche2.put("a1", message);
}
@OnError
public void onError(Throwable t) {
t.printStackTrace();
}
}
遠端服務(wù)端:
/**在遠端服務(wù)器上建立websocket服務(wù)器*/
@ServerEndpoint(value = "/websocket/{relationId}/{userCode}")
public class UserWebSocketMain {
/**
* 打開連接時觸發(fā)
* @param relationId
* @param userCode
* @param session
*/
@OnOpen
public void onOpen(@PathParam("relationId") String relationId,
@PathParam("userCode") int userCode,
Session session){
System.out.println("連接成功"+relationId+":"+userCode);
SessionUtils.put(relationId, userCode, session);
}
/**
* 收到客戶端消息時觸發(fā)
* @param relationId
* @param userCode
* @param message
* @return
*/
@OnMessage
public String onMessage(@PathParam("relationId") String relationId,
@PathParam("userCode") int userCode,
String message) {
try {
sendMessage("userSever", 8080, message);
} catch (IOException e) {
e.printStackTrace();
}
return "Got your message (" + message + ").Thanks !";
}
/**
* 異常時觸發(fā)
* @param relationId
* @param userCode
* @param session
*/
@OnError
public void onError(@PathParam("relationId") String relationId,
@PathParam("userCode") int userCode,
Throwable throwable,
Session session) {
//SessionUtils.remove(relationId, userCode);
throwable.printStackTrace();
}
/**
* 關(guān)閉連接時觸發(fā)
* @param relationId
* @param userCode
* @param session
*/
@OnClose
public void onClose(@PathParam("relationId") String relationId,
@PathParam("userCode") int userCode,
Session session) {
SessionUtils.remove(relationId, userCode);
}
public void sendMessage( String relationId,int userCode,String message) throws IOException {
Session session = SessionUtils.get(relationId, userCode);
System.out.println(session);
if(session!=null) {
session.getBasicRemote().sendText(message);
}else {
}
// this.session.getAsyncRemote().sendText(message);
}
}
/**管理連接*/
public class SessionUtils {
public static Map<String, Session> clients = new ConcurrentHashMap<>();
private static final AtomicInteger connectionIds = new AtomicInteger();
public static void put(String relationId, int userCode, Session session){
System.out.println("保存會話"+relationId+":"+userCode);
clients.put(getKey(relationId, userCode), session);
}
public static Session get(String relationId, int userCode){
System.out.println("取出會話"+relationId+":"+userCode);
return clients.get(getKey(relationId, userCode));
}
public static void remove(String relationId, int userCode){
System.out.println("關(guān)閉連接"+relationId+":"+userCode);
clients.remove(getKey(relationId, userCode));
}
/**
* 判斷是否有連接
* @param relationId
* @param userCode
* @return
*/
public static boolean hasConnection(String relationId, int userCode) {
System.out.println("判斷是否有會話:"+relationId+":"+userCode);
return clients.containsKey(getKey(relationId, userCode));
}
/**
* 組裝唯一識別的key
* @param relationId
* @param userCode
* @return
*/
public static String getKey(String relationId, int userCode) {
System.out.println("組裝會話唯一標(biāo)識"+relationId+":"+userCode);
return relationId + "_" + userCode;
}
}
將兩邊服務(wù)端啟動,調(diào)用后臺的getAccess()方法就可以建立連接奇昙,因為想在getAccess()中直接拿到響應(yīng)的數(shù)據(jù)护侮,所以在MyClient 類中做了一個全局Map(MyCache.catche2),當(dāng)消息傳回調(diào)用MyClient 的onMessage()方法時將數(shù)據(jù)存入map储耐,再在getAccess()中用一個輪詢得到map的數(shù)據(jù)(方法很笨羊初,只是不知道還有什么其他方法,見諒J蚕妗3ぴ蕖!)
以上可實現(xiàn)遠端服務(wù)端連接闽撤,本人java小白剛參加工作得哆,寫此心得希望各位大神指正,對于獲取即時數(shù)據(jù)這一塊哟旗,我用了map贩据,不知還有其他方法可以實現(xiàn),跪求U⒉汀1チ痢!I嵘场=稀!