Jetty提供了功能更強的WebSocket API,使用一個公共的核心API供WebSockets的服務端和client使用。
他是一個基於WebSocket消息的事件驅動的API。html
每個WebSocket都能接收多種事件:java
On Connect Eventweb
表示WebSocket升級成功,WebSocket現在打開。
你將收到一個org.eclipse.jetty.websocket.api.Session對象,相應這個Open事件的session。
爲一般的WebSocket,應該牢牢抓住這個Session,並使用它與Remote Endpoint進行交流。
假設爲無狀態(Stateless)WebSockets,這個Session將被傳遞到它出現的每一個事件,贊成你使用一個WebSocket的1個實例爲多個Remote Endpoint提供服務。api
On Close Event數組
表示WebSocket已經關閉。
每個Close事件將有一個狀態碼(Status Code)(和一個可選的Closure Reason Message)。
一個一般的WebSocket終止將經歷一個關閉握手,Local Endpoint和Remote Endpoint都會發送一個Close幀表示鏈接被關閉。
本地WebSocket可以經過發送一個Close幀到Remote Endpoint表示但願關閉,但是Remote Endpoint能繼續發送信息直到它送一個Close幀爲止。這被稱之爲半開(Half-Open)鏈接,注意一旦Local Endpoint發送了Close幀後,它將不能再發送不論什麼WebSocket信息。
在一個異常的終止中,好比一個鏈接斷開或者超時,底層鏈接將不經歷Close Handshake就被終止,這也將致使一個On Close Event(和可能伴隨一個On Error Event)。安全
On Error Eventwebsocket
假設一個錯誤出現,在實現期間,WebSocket將經過這個事件被通知。session
On Message Eventless
表示一個完整的信息被收到,準備被你的WebSocket處理。
這能是一個(UTF8)TEXT信息或者一個原始的BINARY信息。eclipse
Session對象能被用於:
鏈接狀態(打開或者關閉)
if(session.isOpen()) { // send message }
鏈接是安全的嗎。
if(session.isSecure()) { // connection is using 'wss://' }
在升級請求和響應中的是什麼。
UpgradeRequest req = session.getUpgradeRequest(); String channelName = req.getParameterMap().get("channelName"); UpgradeRespons resp = session.getUpgradeResponse(); String subprotocol = resp.getAcceptedSubProtocol();
本地和遠端地址是什麼。
InetSocketAddress remoteAddr = session.getRemoteAddress();
獲取和設置空暇超時時間。
session.setIdleTimeout(2000); // 2 second timeout
獲取和設置最大信息長度。
session.setMaximumMessageSize(64*1024); // accept messages up to 64k, fail if larger
Session的最重要的特徵是獲取org.eclipse.jetty.websocket.api.RemoteEndpoint。
使用RemoteEndpoint,你能選擇發送TEXT或者BINARY Websocket信息,或者WebSocket PING和PONG控制幀。
其實大部分調用都是堵塞式的,直到發送完畢(或者拋出一個異常)才返回。
實例1 發送二進制信息(堵塞) RemoteEndpoint remote = session.getRemote(); // Blocking Send of a BINARY message to remote endpoint ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 }); try { remote.sendBytes(buf); } catch (IOException e) { e.printStackTrace(System.err); }
怎麼使用RemoteEndpoint送一個簡單的二進制信息。這將堵塞直到信息被髮送完畢,假設不能發送信息可能將拋出一個IOException。
實例2 發送文本信息(堵塞) RemoteEndpoint remote = session.getRemote(); // Blocking Send of a TEXT message to remote endpoint try { remote.sendString("Hello World"); } catch (IOException e) { e.printStackTrace(System.err); }
怎麼使用RemoteEndpoint發送文本信息。這將堵塞直到信息發送,假設不能發送信息可能將拋出一個IOException。
假設你有一個大的信息需要被髮送,並且想分屢次發送,每次一部分,你能使用RemoteEndpoint發送部分信息的方法。僅需要確保你最後發送一個完畢發送的信息(isLast == true)
實例3 發送部分二進制信息(堵塞) RemoteEndpoint remote = session.getRemote(); // Blocking Send of a BINARY message to remote endpoint // Part 1 ByteBuffer buf1 = ByteBuffer.wrap(new byte[] { 0x11, 0x22 }); // Part 2 (last part) ByteBuffer buf2 = ByteBuffer.wrap(new byte[] { 0x33, 0x44 }); try { remote.sendPartialBytes(buf1,false); remote.sendPartialBytes(buf2,true); // isLast is true } catch (IOException e) { e.printStackTrace(System.err); }
怎麼分兩次發送一個二進制信息,使用在RemoteEndpoint中的部分信息支持方法。這將堵塞直到每次信息發送完畢,假設不能發送信息可能拋出一個IOException。
實例4 發送部分文本信息(堵塞) RemoteEndpoint remote = session.getRemote(); // Blocking Send of a TEXT message to remote endpoint String part1 = "Hello"; String part2 = " World"; try { remote.sendPartialString(part1,false); remote.sendPartialString(part2,true); // last part } catch (IOException e) { e.printStackTrace(System.err); }
怎麼經過兩次發送一個文本信息,使用在RemoteEndpoint中的部分信息支持方法。這將堵塞直到每次信息發送完畢,假設不能發送信息可能拋出一個IOException。
你也能使用RemoteEndpoint發送Ping和Pong控制幀。
實例5 發送Ping控制幀(堵塞) RemoteEndpoint remote = session.getRemote(); // Blocking Send of a PING to remote endpoint String data = "You There?"; ByteBuffer payload = ByteBuffer.wrap(data.getBytes()); try { remote.sendPing(payload); } catch (IOException e) { e.printStackTrace(System.err); }
怎麼發送一個Ping控制幀,附帶一個負載「You There?」(做爲一個字節數組負載到達Remote Endpoint)。這將堵塞直到信息發送完畢,假設不能發送Ping幀,可能拋出一個IOException。
實例6 送Pong控制幀(堵塞) RemoteEndpoint remote = session.getRemote(); // Blocking Send of a PONG to remote endpoint String data = "Yup, I'm here"; ByteBuffer payload = ByteBuffer.wrap(data.getBytes()); try { remote.sendPong(payload); } catch (IOException e) { e.printStackTrace(System.err); }
怎麼發送一個Pong控制幀,附帶一個"Yup I'm here"負載(做爲一個字節數組負載到達Remote Endpoint)。這將堵塞直到信息被髮送,假設不能發送Pong幀,可能拋出一個IOException。
爲了正確的使用Pong幀,你應該返回你在Ping幀中收到的相同的字節數組數據。
也存在來年改革異步發送信息的方法可用:
1)RemoteEndpoint.sendBytesByFuture(字節信息)
2)RemoteEndpoint.sendStringByFuture(字符串信息)
兩個方法都返回一個Future<Void>,使用標準java.util.concurrent.Future行爲,能被用於測試信息發送的成功和失敗。
實例7 送二進制信息(異步) RemoteEndpoint remote = session.getRemote(); // Async Send of a BINARY message to remote endpoint ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 }); remote.sendBytesByFuture(buf);
怎麼使用RemoteEndpoint發送一個簡單的二進制信息。這個信息將被放入發送隊列,你將不知道發送成功或者失敗。
實例8 發送二進制信息(異步,等待直到成功) RemoteEndpoint remote = session.getRemote(); // Async Send of a BINARY message to remote endpoint ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 }); try { Future<Void> fut = remote.sendBytesByFuture(buf); // wait for completion (forever) fut.get(); } catch (ExecutionException | InterruptedException e) { // Send failed e.printStackTrace(); }
怎麼使用RemoteEndpoint發送一個簡單的二進制信息,追蹤Future<Void>以肯定發送成功仍是失敗。
實例9 送二進制信息(異步,發送超時) RemoteEndpoint remote = session.getRemote(); // Async Send of a BINARY message to remote endpoint ByteBuffer buf = ByteBuffer.wrap(new byte[] { 0x11, 0x22, 0x33, 0x44 }); Future<Void> fut = null; try { fut = remote.sendBytesByFuture(buf); // wait for completion (timeout) fut.get(2,TimeUnit.SECONDS); } catch (ExecutionException | InterruptedException e) { // Send failed e.printStackTrace(); } catch (TimeoutException e) { // timeout e.printStackTrace(); if (fut != null) { // cancel the message fut.cancel(true); } }
怎麼使用RemoteEndpoint發送一個簡單的二進制信息,追蹤Future<Void>並等待一個有限的時間,假設時間超限則取消該信息。
實例10 發送文本信息(異步) RemoteEndpoint remote = session.getRemote(); // Async Send of a TEXT message to remote endpoint remote.sendStringByFuture("Hello World"); 怎麼使用RemoteEndpoint發送一個簡單的文本信息。這個信息將被放到輸出隊列中,但是你將不知道發送成功仍是失敗。 實例11 發送文本信息(異步,等待直到成功) RemoteEndpoint remote = session.getRemote(); // Async Send of a TEXT message to remote endpoint try { Future<Void> fut = remote.sendStringByFuture("Hello World"); // wait for completion (forever) fut.get(); } catch (ExecutionException | InterruptedException e) { // Send failed e.printStackTrace(); }
怎麼使用RemoteEndpoint發送一個簡單的二進制信息,追蹤Future<Void>以直到發送成功仍是失敗。
實例12 發送文本信息(異步,發送超時) RemoteEndpoint remote = session.getRemote(); // Async Send of a TEXT message to remote endpoint Future<Void> fut = null; try { fut = remote.sendStringByFuture("Hello World"); // wait for completion (timeout) fut.get(2,TimeUnit.SECONDS); } catch (ExecutionException | InterruptedException e) { // Send failed e.printStackTrace(); } catch (TimeoutException e) { // timeout e.printStackTrace(); if (fut != null) { // cancel the message fut.cancel(true); } }
怎麼使用RemoteEndpoint發送一個簡單的二進制信息,追蹤Future<Void>並等待有限的時間,假設超時則取消。
WebSocket的最主要的形式是一個被Jetty WebSocket API提供的用凝視標記的POJO。
實例13 AnnotatedEchoSocket.java package examples.echo; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Example EchoSocket using Annotations. */ @WebSocket(maxTextMessageSize = 64 * 1024) public class AnnotatedEchoSocket { @OnWebSocketMessage public void onText(Session session, String message) { if (session.isOpen()) { System.out.printf("Echoing back message [%s]%n", message); session.getRemote().sendString(message, null); } } }
上面的樣例是一個簡單的WebSocket回送端點,將回送所有它收到的文本信息。
這個實現使用了一個無狀態的方法,所以對每個出現的事件Session都會被傳遞到Message處理方法中。這將贊成你在同多個port交互時可以重用AnnotatedEchoSocket的單實例。
你可用的凝視例如如下:
@WebSocket
一個必須的類級別的凝視。
標記這個POJO做爲一個WebSocket。
類必須不是abstract,且是public。
@OnWebSocketConnect
一個可選的方法級別的凝視。
標記一個在類中的方法做爲On Connect事件的接收者。
方法必須是public,且不是abstract,返回void,並且有且僅有一個Session參數。
@OnWebSocketClose
一個可選的方法級的凝視。
標記一個在類中的方法做爲On Close事件的接收者。
方法標籤必須是public,不是abstract,並且返回void。
方法的參數包含:
1)Session(可選)
2)int closeCode(必須)
3)String closeReason(必須)
@OnWebSocketMessage
一個可選的方法級凝視。
標記在類中的2個方法做爲接收On Message事件的接收者。
方法標籤必須是public,不是abstract,並且返回void。
爲文本信息的方法參數包含:
1)Session(可選)
2)String text(必須)
爲二進制信息的方法參數包含:
1)Session(可選)
2)byte buf[](必須)
3)int offset(必須)
4)int length(必須)
@OnWebSocketError
一個可選的方法級凝視。
標記一個類中的方法做爲Error事件的接收者。
方法標籤必須是public,不是abstract,並且返回void。
方法參數包含:
1)Session(可選)
2)Throwable cause(必須)
@OnWebSocketFrame
一個可選的方法級凝視。
標記一個類中的方法做爲Frame事件的接收者。
方法標籤必須是public,不是abstract,並且返回void。
方法參數包含:
1)Session(可選)
2)Frame(必須)
收到的Frame將在這種方法上被通知,而後被Jetty處理,可能致使還有一個事件,好比On Close,或者On Message。對Frame的改變將不被Jetty看到。
一個WebSocket的基本形式是使用org.eclipse.jetty.websocket.api.WebSocketListener處理收到的事件。
實例14 ListenerEchoSocket.java package examples.echo; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; /** * Example EchoSocket using Listener. */ public class ListenerEchoSocket implements WebSocketListener { private Session outbound; @Override public void onWebSocketBinary(byte[] payload, int offset, int len) { } @Override public void onWebSocketClose(int statusCode, String reason) { this.outbound = null; } @Override public void onWebSocketConnect(Session session) { this.outbound = session; } @Override public void onWebSocketError(Throwable cause) { cause.printStackTrace(System.err); } @Override public void onWebSocketText(String message) { if ((outbound != null) && (outbound.isOpen())) { System.out.printf("Echoing back message [%s]%n", message); outbound.getRemote().sendString(message, null); } } }
假設listener作了太多的工做,你能使用WebSocketAdapter取代。
WebSocketListener的適配器。
實例15 AdapterEchoSocket.java package examples.echo; import java.io.IOException; import org.eclipse.jetty.websocket.api.WebSocketAdapter; /** * Example EchoSocket using Adapter. */ public class AdapterEchoSocket extends WebSocketAdapter { @Override public void onWebSocketText(String message) { if (isConnected()) { try { System.out.printf("Echoing back message [%s]%n", message); getRemote().sendString(message); } catch (IOException e) { e.printStackTrace(System.err); } } } }
這個類比WebSocketListener跟爲便利,並提供了實用的方法檢查Session的狀態。
Jetty經過WebSocketServlet和servlet橋接的使用,提供了將WebSocket端點到Servlet路徑的相應。
內在地,Jetty管理HTTP升級到WebSocket,並且從一個HTTP鏈接移植到一個WebSocket鏈接。
這僅僅有當執行在Jetty容器內部時才工做。
爲了經過WebSocketServlet相應你的WebSocket到一個指定的路徑,你將需要擴展org.eclipse.jetty.websocket.servlet.WebSocketServlet並指定什麼WebSocket對象應該被建立。
實例16 MyEchoServlet.java package examples; import javax.servlet.annotation.WebServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @SuppressWarnings("serial") @WebServlet(name = "MyEcho WebSocket Servlet", urlPatterns = { "/echo" }) public class MyEchoServlet extends WebSocketServlet { @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(10000); factory.register(MyEchoSocket.class); } }
這個樣例將建立一個Sevlet,經過@WebServlet註解匹配到Servlet路徑"/echo"(或者你能在你的web應用的WEB-INF/web.xml中手動的配置),當收到HTTP升級請求時將建立MyEchoSocket實例。
WebSocketServlet.configure(WebSocketServletFactory factory)是爲你的WebSocket指定配置的地方。在這個樣例中,咱們指定一個10s的空暇超時,並註冊MyEchoSocket,即當收到請求時咱們想建立的WebSocket類,使用默認的WebSocketCreator建立。
所有WebSocket都是經過你註冊到WebSocketServletFactory的WebSocketCreator建立的。
默認,WebSocketServletFactory是一個簡單的WebSocketCreator,能建立一個單例的WebSocket對象。 使用WebSocketCreator.register(Class<?> websocket)告訴WebSocketServletFactory應該實例化哪一個類(確保它有一個默認的構造器)。
假設你有更復雜的建立場景,你可以提供你本身的WebSocketCreator,基於在UpgradeRequest對象中出現的信息建立的WebSocket。
實例17 MyAdvancedEchoCreator.java package examples; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; public class MyAdvancedEchoCreator implements WebSocketCreator { private MyBinaryEchoSocket binaryEcho; private MyEchoSocket textEcho; public MyAdvancedEchoCreator() { this.binaryEcho = new MyBinaryEchoSocket(); this.textEcho = new MyEchoSocket(); } @Override public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { for (String subprotocol : req.getSubProtocols()) { if ("binary".equals(subprotocol)) { resp.setAcceptedSubProtocol(subprotocol); return binaryEcho; } if ("text".equals(subprotocol)) { resp.setAcceptedSubProtocol(subprotocol); return textEcho; } } return null; } }
這兒咱們展現了一個WebSocketCreator,將利用來自請求的WebSocket子協議信息決定什麼類型的WebSocket應該被建立。
實例18 MyAdvancedEchoServlet.java package examples; import javax.servlet.annotation.WebServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @SuppressWarnings("serial") @WebServlet(name = "MyAdvanced Echo WebSocket Servlet", urlPatterns = { "/advecho" }) public class MyAdvancedEchoServlet extends WebSocketServlet { @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(10000); factory.setCreator(new MyAdvancedEchoCreator()); } }
當你想要一個定製的WebSocketCreator時,使用WebSocketServletFactory.setCreator(WebSocketCreator creator),而後WebSocketServletFactory將爲所有在這個servlet上收到的Upgrade請求用你的創造器。
一個WebSocketCreator還可以用於:
1)控制WebSocket子協議的選擇;
2)履行不論什麼你以爲重要的WebSocket源;
3)從輸入的請求獲取HTTP頭;
4)獲取Servlet HttpSession對象(假設它存在);
5)指定一個響應狀態碼和緣由;
假設你不想接收這個請求,簡單的從WebSocketCreator.createWebSocket(UpgradeRequest req, UpgradeResponse resp)返回null。
Jetty也提供了一個Jetty WebSocket Client庫,爲了更easy的與WebSocket服務端交互。
爲了在你本身的Java項目上使用Jetty WebSocket Client,你將需要如下的maven配置:
<dependency> <groupId>org.eclipse.jetty.websocket</groupId> <artifactId>websocket-client</artifactId> <version>${project.version}</version> </dependency>
爲了使用WebSocketClient,你將需要鏈接一個WebSocket對象實例到一個指定的目標WebSocket URI。
實例19 SimpleEchoClient.java package examples; import java.net.URI; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; /** * Example of a simple Echo Client. */ public class SimpleEchoClient { public static void main(String[] args) { String destUri = "ws://echo.websocket.org"; if (args.length > 0) { destUri = args[0]; } WebSocketClient client = new WebSocketClient(); SimpleEchoSocket socket = new SimpleEchoSocket(); try { client.start(); URI echoUri = new URI(destUri); ClientUpgradeRequest request = new ClientUpgradeRequest(); client.connect(socket, echoUri, request); System.out.printf("Connecting to : %s%n", echoUri); socket.awaitClose(5, TimeUnit.SECONDS); } catch (Throwable t) { t.printStackTrace(); } finally { try { client.stop(); } catch (Exception e) { e.printStackTrace(); } } } }
上面的樣例鏈接到一個遠端WebSocket服務端,並且鏈接後使用一個SimpleEchoSocket履行在websocket上的處理邏輯,等待socket關閉。
實例20 SimpleEchoSocket.java package examples; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Basic Echo Client Socket */ @WebSocket(maxTextMessageSize = 64 * 1024) public class SimpleEchoSocket { private final CountDownLatch closeLatch; @SuppressWarnings("unused") private Session session; public SimpleEchoSocket() { this.closeLatch = new CountDownLatch(1); } public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { return this.closeLatch.await(duration, unit); } @OnWebSocketClose public void onClose(int statusCode, String reason) { System.out.printf("Connection closed: %d - %s%n", statusCode, reason); this.session = null; this.closeLatch.countDown(); } @OnWebSocketConnect public void onConnect(Session session) { System.out.printf("Got connect: %s%n", session); this.session = session; try { Future<Void> fut; fut = session.getRemote().sendStringByFuture("Hello"); fut.get(2, TimeUnit.SECONDS); fut = session.getRemote().sendStringByFuture("Thanks for the conversation."); fut.get(2, TimeUnit.SECONDS); session.close(StatusCode.NORMAL, "I'm done"); } catch (Throwable t) { t.printStackTrace(); } } @OnWebSocketMessage public void onMessage(String msg) { System.out.printf("Got msg: %s%n", msg); } }
當SimpleEchoSocket鏈接成功後,它發送2個文本信息,而後關閉socket。 onMessage(String msg)收到來自遠端WebSocket的響應,並輸出他們到控制檯。