學(xué)習(xí)教程
跟著下面兩篇教程的任意一篇寫一遍,WebSocket基本上就算掌握了。
- Using WebSocket for Real-Time Communication in Java Platform, Enterpise Edition 7
- Java EE 7: Building Web Applications with WebSocket, JavaScript and HTML5
WebSocket介紹
web應(yīng)用的出現(xiàn)需要客戶端和服務(wù)端更多的交互,http只能在客戶端每次請求服務(wù)端的時候建立短暫的連接,已經(jīng)不能滿足需求。雖然long-poll和Ajax也可以實現(xiàn)服務(wù)端向瀏覽器發(fā)送數(shù)據(jù),但它們這種輪詢的方式存在一些弊端,比如請求延遲和http過度請求。在需要實時連接(real-time)的應(yīng)用中,這種弊端更加明顯。
websocket是HTML5中新加入的內(nèi)容。通過websocket(協(xié)議),每個瀏覽器(客戶端)和服務(wù)端建立一個長連接,雙方都可以隨時主動地向?qū)Ψ桨l(fā)送消息。
Java EE 7 中已經(jīng)集成了websocket,通過使用注解,可以很方便地創(chuàng)建一個websocket應(yīng)用,主要有下面五個注解:
@ServerEndpoint
:定義websocket的地址;
@OnOpen
:服務(wù)端和客戶端建立連接時調(diào)用;
@OnMessage
:發(fā)送數(shù)據(jù)時調(diào)用;
@OnClose
:關(guān)閉連接時調(diào)用;
@OnError
:出錯時調(diào)用。
教程主要內(nèi)容
下面是第一篇教程的主要內(nèi)容,你可以到這里下載源碼。
創(chuàng)建websocket服務(wù)
@ServerEndpoint
定義了websocket的服務(wù)端地址,還定義了編譯發(fā)送出去的信息和解析收到的信息的類。
package com.ws.sticker;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(
value="/story/notifications",
encoders={StickerEncoder.class},
decoders={StickerDecoder.class})
public class StoryWebSocket {
// 保存所有的 sticker
private static final List<Sticker> stickers = Collections.synchronizedList(new LinkedList<Sticker>());
// 保存所有客戶端的 session
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
@OnMessage
public void onMessage(Session session, Sticker sticker){
// 有消息從客戶端發(fā)送過來,保存到列表中,然后通知所有的客戶端
stickers.add(sticker);
for(Session openSession : sessions){
try {
openSession.getBasicRemote().sendObject(sticker);
} catch (IOException | EncodeException e) {
sessions.remove(openSession);
}
}
}
@OnOpen
public void onOpen(Session session) throws IOException, EncodeException{
// 有新的客戶端連接時,保存此客戶端的session,并且把當(dāng)前所有的sticker發(fā)送給它
sessions.add(session);
for(Sticker sticker : stickers){
session.getBasicRemote().sendObject(sticker);
}
}
@OnClose
public void onClose(Session session){
// 有客戶端斷開連接時 ,從session列表中移除此客戶端的session
sessions.remove(session);
}
}
定義編譯類
StickerEncoder.class
編譯將要發(fā)送出去的信息:將要發(fā)送的Sticker
對象轉(zhuǎn)化成JsonObject
對象,然后通過JsonWriter
發(fā)送到客戶端。
package com.ws.sticker;
import java.io.IOException;
import java.io.Writer;
import javax.json.JsonObject;
import javax.json.JsonWriter;
import javax.json.spi.JsonProvider;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
public class StickerEncoder implements Encoder.TextStream<Sticker> {
@Override
public void init(EndpointConfig config) {
// TODO Auto-generated method stub
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void encode(Sticker sticker, Writer writer) throws EncodeException, IOException {
JsonProvider provider = JsonProvider.provider();
JsonObject jsonSticker = provider.createObjectBuilder()
.add("action", "add")
.add("x", sticker.getX())
.add("y", sticker.getY())
.add("sticker", sticker.getImage())
.build();
JsonWriter jsonWriter = provider.createWriter(writer);
jsonWriter.write(jsonSticker);
}
}
定義解析類
StickerDecoder.class
解析服務(wù)端收到的信息:通過Reader
獲得接收到的信息JsonObject
,然后轉(zhuǎn)化成Sticker
對象。
package com.ws.sticker;
import java.io.IOException;
import java.io.Reader;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.spi.JsonProvider;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
public class StickerDecoder implements Decoder.TextStream<Sticker> {
// Do not create a JsonReader object. To create readers and writes, use the
// JsonProvider class.
@Override
public void init(EndpointConfig config) {
// TODO Auto-generated method stub
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public Sticker decode(Reader reader) throws DecodeException, IOException {
JsonProvider provider = JsonProvider.provider();
JsonReader jsonReader = provider.createReader(reader);
JsonObject jsonSticker = jsonReader.readObject();
Sticker sticker = new Sticker();
sticker.setX(jsonSticker.getInt("x"));
sticker.setY(jsonSticker.getInt("y"));
sticker.setImage(jsonSticker.getString("sticker"));
return sticker;
}
}
創(chuàng)建websocket客戶端
var socket = null;
function initialize() {
...
// 連接到websocket服務(wù)端
socket = new WebSocket('ws://localhost:8080/learn/story/notifications');
// 定義接收消息時執(zhí)行的方法
socket.onmessage = onSocketMessage;
}
function onSocketMessage(event) {
if(event.data){
var receivedSticker = JSON.parse(event.data);
log("Received Object: " + JSON.stringify(receivedSticker));
...
}
}
window.onload = initialize;
觀察現(xiàn)象
運行Java web程序,在多個瀏覽器中打開 http://localhost:8080/learn/story/notifications ,當(dāng)其中一個瀏覽器的內(nèi)容改變的時候,其他瀏覽器也會相應(yīng)地改變。
總結(jié)
通過上面的練習(xí),主要學(xué)到了:
- 通過 java API 創(chuàng)建 websocket server;
- 創(chuàng)建JSON 編譯、解析類讀寫socket的數(shù)據(jù);
- 使用JavaScript創(chuàng)建 websocket client。