Jetty WebSocket API使用
Jetty提供了功能更强的WebSocket API,使用一个公共的核心API供WebSockets的服务端和client使用。
他是一个基于WebSocket消息的事件驱动的API。
WebSocket事件
每一个WebSocket都能接收多种事件:
On Connect Event
表示WebSocket升级成功,WebSocket如今打开。
你将收到一个org.eclipse.jetty.websocket.api.Session对象,相应这个Open事件的session。
为通常的WebSocket,应该紧紧抓住这个Session,并使用它与Remote Endpoint进行交流。
假设为无状态(Stateless)WebSockets,这个Session将被传递到它出现的每个事件,同意你使用一个WebSocket的1个实例为多个Remote Endpoint提供服务。
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 Event
假设一个错误出现,在实现期间,WebSocket将通过这个事件被通知。
On Message Event
表示一个完整的信息被收到,准备被你的WebSocket处理。
这能是一个(UTF8)TEXT信息或者一个原始的BINARY信息。
WebSocket Session
Session对象能被用于:
获取WebSocket的状态
连接状态(打开或者关闭)
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
发送信息到Remote Endpoint
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。
发送Ping/Pong控制帧
你也能使用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凝视
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看到。
用WebSocketListener
一个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取代。
使用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 WebSocket Server API
Jetty通过WebSocketServlet和servlet桥接的使用,提供了将WebSocket端点到Servlet路径的相应。
内在地,Jetty管理HTTP升级到WebSocket,而且从一个HTTP连接移植到一个WebSocket连接。
这仅仅有当执行在Jetty容器内部时才工作。
Jetty WebSocketServlet
为了通过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创建。
使用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 WebSocket Client API
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
为了使用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的响应,并输出他们到控制台。