教你怎么使用Java實(shí)現(xiàn)WebSocket
一、WebSocket簡(jiǎn)介
WebSocket協(xié)議通過(guò)在客戶端和服務(wù)端之間提供全雙工通信來(lái)進(jìn)行Web和服務(wù)器的交互功能。
在WebSocket應(yīng)用程序中,服務(wù)器發(fā)布WebSocket端點(diǎn),客戶端使用url連接到服務(wù)器。建立連接后,服務(wù)器和客戶端就可以互相發(fā)送消息??蛻舳送ǔ_B接到一臺(tái)服務(wù)器,服務(wù)器接受多個(gè)客戶端的連接。
1.1 WebSocket協(xié)議
WebSocket協(xié)議有兩個(gè)部分:握手和傳輸??蛻舳送ㄟ^(guò)向服務(wù)端URL發(fā)送握手請(qǐng)求來(lái)建立連接。握手與現(xiàn)有的基于HTTP的基礎(chǔ)結(jié)構(gòu)相兼容。Web服務(wù)器將其解釋為升級(jí)版的HTTP連接請(qǐng)求。
一個(gè)客戶端建立連接的握手請(qǐng)求:
GET /path/to/websocket/endpoint HTTP/1.1 Host: localhost Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg== Origin: http://localhost Sec-WebSocket-Version: 13
一個(gè)服務(wù)端響應(yīng):
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
從上面的請(qǐng)求和響應(yīng)中可以看出來(lái),一個(gè)WebSocket連接的建立,需要客戶端和服務(wù)端維護(hù)一個(gè)Key來(lái)作為該連接的連接憑證。
客戶端向服務(wù)端發(fā)送WebSocketKey,服務(wù)器根據(jù)WebSocketKey生成WebSocketAccept返回給客戶端,客戶端對(duì)WebSocketKey的值再進(jìn)行相同的操作,如果與服務(wù)器返回的Accept的值相匹配,就表示握手成功。握手之后客戶端與服務(wù)端就互相發(fā)送消息。
1.2 WebSocket支持的消息類型
WebSocket支持文本消息(UTF-8編碼)和二進(jìn)制消息。WebSocket的控制消息由Close、Ping、Pong組成。ping和pong也有可能包含應(yīng)用程序信息。
webSocket端點(diǎn)具有如下的URI形式表示:
ws://host:port/path?query wss://host:port/path?query
ws代表未加密的連接,默認(rèn)端口是80
wss代表加密的連接,默認(rèn)端口是443
path:表示服務(wù)器內(nèi)端點(diǎn)的位置
query:路徑參數(shù)信息
二、創(chuàng)建一個(gè)WebSocket程序
創(chuàng)建和部署WebSocket端點(diǎn)的過(guò)程如下:
1、創(chuàng)建一個(gè)端點(diǎn)類
2、實(shí)現(xiàn)端點(diǎn)的生命周期方法
3、將業(yè)務(wù)邏輯添加到端點(diǎn)
4、將端點(diǎn)部署到Web應(yīng)用程序中
注意:與Servlet相反,WebSocket端點(diǎn)類會(huì)被實(shí)例化多次,容器針對(duì)與其部署的URI的每個(gè)連接都創(chuàng)建一個(gè)實(shí)例。每個(gè)實(shí)例都與一個(gè)連接相關(guān)聯(lián)。因?yàn)樵谌魏螘r(shí)間都只有一個(gè)線程執(zhí)行端點(diǎn)實(shí)例的代碼,所以有助于保持每個(gè)連接的用戶狀態(tài)簡(jiǎn)化開(kāi)發(fā)。
2.1 生命周期方法
EndPoint類中定義了三個(gè)生命周期方法:onOpen、onClose、onError
使用注解方式創(chuàng)建服務(wù)類
@ServerEndpoint("/echo") public class EchoEndpoint { @OnMessage public void onMessage(Session session, String msg) { try { session.getBasicRemote().sendText(msg); } catch (IOException e) { ... } } }
2.2 業(yè)務(wù)邏輯方法
@ServerEndpoint("/receive") public class ReceiveEndpoint { @OnMessage public void textMessage(Session session, String msg) { System.out.println("Text message: " + msg); } @OnMessage public void binaryMessage(Session session, ByteBuffer msg) { System.out.println("Binary message: " + msg.toString()); } @OnMessage public void pongMessage(Session session, PongMessage msg) { System.out.println("Pong message: " + msg.getApplicationData().toString()); } }
注意:一個(gè)服務(wù)類最多可以有三個(gè)OnMessage注解,每個(gè)消息類型分別使用一種方法:Text、Binary、Pong
2.3 維護(hù)客戶狀態(tài)
有時(shí)在程序中我們可能需要在連接中維護(hù)一些用戶參數(shù),WebSocket也提供了這樣的服務(wù)
Session.getUserProperties
獲取用戶參數(shù)信息
如果要存儲(chǔ)所有連接的客戶端共有的信息,可以使用靜態(tài)變量,但是需要用戶保證對(duì)數(shù)據(jù)的線程安全訪問(wèn)。
@ServerEndpoint("/delayedecho") public class DelayedEchoEndpoint { @OnOpen public void open(Session session) { session.getUserProperties().put("previousMsg", " "); } @OnMessage public void message(Session session, String msg) { String prev = (String) session.getUserProperties() .get("previousMsg"); session.getUserProperties().put("previousMsg", msg); try { session.getBasicRemote().sendText(prev); } catch (IOException e) { ... } } }
2.4 數(shù)據(jù)格式的編碼與解碼
由于客戶端和服務(wù)端交互可能涉及數(shù)據(jù)格式的轉(zhuǎn)換,所以提供了Decoder和Encoder的方式解決。
同時(shí)由于WebSocket的@Message注解只能有一個(gè)用來(lái)傳輸Text信息或Binary信息,所以要進(jìn)行最常用的Json->entity轉(zhuǎn)換解析就需要該方法
Encoder
Encoder.Text 用于文本消息Encoder.Binary 用于二進(jìn)制消息
使用方法:
1、創(chuàng)建編解碼類
public class MessageATextEncoder implements Encoder.Text<MessageA> { @Override public void init(EndpointConfig ec) { } @Override public void destroy() { } @Override public String encode(MessageA msgA) throws EncodeException { // Access msgA's properties and convert to JSON text... return msgAJsonString; } }
2、在端點(diǎn)類注解中添加該Encoder
@ServerEndpoint( value = "/myendpoint", encoders = { MessageATextEncoder.class, MessageBTextEncoder.class } ) public class EncEndpoint { ... }
3、這時(shí)候就可以發(fā)送MessageA和MessageB兩種類型的Text數(shù)據(jù)
MessageA msgA = new MessageA(...); MessageB msgB = new MessageB(...); session.getBasicRemote.sendObject(msgA); session.getBasicRemote.sendObject(msgB);
注意:webSocket會(huì)自動(dòng)尋找使用哪種編碼器,所以發(fā)送數(shù)據(jù)統(tǒng)一使用sendObject即可
Decoder
實(shí)現(xiàn)Decoder以將WebSocket消息轉(zhuǎn)換為Java對(duì)象
- Decoder.Text 用于文本消息
- Decoder.Binary 用于二進(jìn)制消息
使用方法
與Encoder類似
注意:與Encoder不同,Decoder最多可以指定一個(gè)Binary和一個(gè)Text類型的Decoder,如果有兩種以上的Java類型作為文本消息進(jìn)行發(fā)送和接收需要進(jìn)行定義處理??梢允苟鄠€(gè)消息繼承一個(gè)公共的消息父類
1、編寫Decoder類,對(duì)收到消息類型的不同進(jìn)行不同的解碼
public class MessageTextDecoder implements Decoder.Text<Message> { @Override public void init(EndpointConfig ec) { } @Override public void destroy() { } @Override public Message decode(String string) throws DecodeException { // Read message... if ( /* message is an A message */ ) return new MessageA(...); else if ( /* message is a B message */ ) return new MessageB(...); } @Override public boolean willDecode(String string) { // Determine if the message can be converted into either a // MessageA object or a MessageB object... return canDecode; } }
2、在端點(diǎn)類中添加decoders={MessageDecoder.class}
@ServerEndpoint( value = "/myendpoint", encoders = { MessageATextEncoder.class, MessageBTextEncoder.class }, decoders = { MessageTextDecoder.class } ) public class EncDecEndpoint { ... }
3、在@OnMessage方法中使用
@OnMessage public void message(Session session, Message msg) { if (msg instanceof MessageA) { // We received a MessageA object... } else if (msg instanceof MessageB) { // We received a MessageB object... } }
到此這篇關(guān)于教你怎么使用Java WebSocket的文章就介紹到這了,更多相關(guān)Java WebSocket詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文看懂springboot實(shí)現(xiàn)短信服務(wù)功能
項(xiàng)目中的短信服務(wù)基本上上都會(huì)用到,簡(jiǎn)單的注冊(cè)驗(yàn)證碼,消息通知等等都會(huì)用到。這篇文章主要介紹了springboot 實(shí)現(xiàn)短信服務(wù)功能,需要的朋友可以參考下2019-10-10Java Date類常用示例_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
在JDK1.0中,Date類是唯一的一個(gè)代表時(shí)間的類,但是由于Date類不便于實(shí)現(xiàn)國(guó)際化,所以從JDK1.1版本開(kāi)始,推薦使用Calendar類進(jìn)行時(shí)間和日期處理。這里簡(jiǎn)單介紹一下Date類的使用,需要的朋友可以參考下2017-05-05mybatis使用mapper代理開(kāi)發(fā)方式
使用MyBatis代理開(kāi)發(fā)模式時(shí),需要注意定義與映射配置文件同名的接口類,確保namespace屬性與接口路徑一致,接口方法名和映射文件中的id名稱相同,返回類型保持一致,在mybatis-config.xml中配置映射文件路徑,保證結(jié)構(gòu)一致,可通過(guò)注解@Param傳遞多個(gè)參數(shù)2024-10-10java 發(fā)送帶Basic Auth認(rèn)證的http post請(qǐng)求實(shí)例代碼
下面小編就為大家?guī)?lái)一篇java 發(fā)送帶Basic Auth認(rèn)證的http post請(qǐng)求實(shí)例代碼。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11解決String字符串轉(zhuǎn)JSONObject順序不對(duì)的問(wèn)題
這篇文章主要介紹了解決String字符串轉(zhuǎn)JSONObject順序不對(duì)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12java 實(shí)現(xiàn)回調(diào)代碼實(shí)例
本文主要介紹Java的回調(diào)機(jī)制,并附實(shí)例代碼以供大家參考學(xué)習(xí),有需要的小伙伴可以看下2016-07-07Springboot整合EasyExcel實(shí)現(xiàn)Excel文件上傳方式
這篇文章主要介紹了Springboot整合EasyExcel實(shí)現(xiàn)Excel文件上傳方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07淺談Java中的atomic包實(shí)現(xiàn)原理及應(yīng)用
這篇文章主要介紹了淺談Java中的atomic包實(shí)現(xiàn)原理及應(yīng)用,涉及Atomic在硬件上的支持,Atomic包簡(jiǎn)介及源碼分析等相關(guān)內(nèi)容,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12