Jetty 中的 WebSocket
最后修改于 2024 年 1 月 27 日
WebSocket 是一种互联网协议,可在客户端和服务器之间提供双向通信。WebSocket 被设计用于 Web 浏览器和 Web 服务器,但也可以被任何客户端或服务器应用程序使用。
消息可以采用 UTF-8 文本或二进制格式进行传递。
WebSocketServlet
Jetty 的 WebSocketServlet
是一个将 Servlet 技术与 WebSocket API 连接起来的 Servlet。在 WebSocketServlet
的 configure 方法中,我们使用 WebSocketServletFactory
注册我们的 WebSocket。WebSocket 是处理传入的 WebSocket 升级请求的 Java 类。
在下面的示例中,一个 Servlet 处理来自 Web 浏览器客户端的 WebSocket 请求。
$ tree . ├── build.xml └── src ├── com │ └── zetcode │ ├── MyServlet.java │ └── MySocket.java └── web ├── index.html ├── index.js └── WEB-INF 5 directories, 5 files
这是项目目录的内容。
<!DOCTYPE html> <html> <body> <script src="index.js"></script> </body> </html>
index.html
文件包含一个 <script>
标签,该标签加载一个外部脚本。
var ws = new WebSocket("ws://:8080/ws/wsexample"); ws.onopen = function() { document.write("WebSocket opened <br>"); ws.send("Hello Server"); }; ws.onmessage = function(evt) { document.write("Message: " + evt.data); }; ws.onclose = function() { document.write("<br>WebSocket closed"); }; ws.onerror = function(err) { document.write("Error: " + err); };
此 JavaScript 代码创建与 WebSocket 的连接。它为 WebSocket 事件定义了四个回调函数。
var ws = new WebSocket("ws://:8080/ws/wsexample");
创建了一个 WebSocket
对象。ws
是 WebSocket 连接的 URI 方案。构造函数接受一个标识 WebSocket 服务器和资源名称的 URI。
ws.onopen = function() { document.write("WebSocket opened <br>"); ws.send("Hello Server"); };
当创建与 WebSocket 的连接时,会触发一个 Open 事件。send
方法将数据传输到 WebSocket。
ws.onmessage = function(evt) { document.write("Message: " + evt.data); };
使用 document.write
方法将从 WebSocket 服务器接收到的消息写入 HTML 页面。
package com.zetcode; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.annotation.WebServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @WebServlet(name = "WebSocket Servlet", urlPatterns = { "/wsexample" }) public class MyServlet extends WebSocketServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().println("HTTP GET method not implemented."); } @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(10000); factory.register(MySocket.class); } }
在 MyServlet.java
类中,我们在 configure
方法中将一个 WebSocket 注册到 WebSocketServletFactory
。
@Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().println("HTTP GET method not implemented."); }
WebSocket 请求不同于 HTTP GET 请求。我们的 Servlet 返回一条消息,说明 GET 方法未实现,用于尝试通过 GET 方法连接到此 Servlet。如果我们没有实现此方法,我们将收到 Jetty 返回的 HTTP 错误 405
— HTTP 方法 GET 不被此 URL 支持 — 错误消息。
factory.getPolicy().setIdleTimeout(10000);
连接将在十秒后超时。届时将触发 Close 事件。
package com.zetcode; import java.io.IOException; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; @WebSocket public class MySocket { @OnWebSocketClose public void onClose(int statusCode, String reason) { System.out.println("Close: " + reason); } @OnWebSocketError public void onError(Throwable t) { System.out.println("Error: " + t.getMessage()); } @OnWebSocketConnect public void onConnect(Session session) { System.out.println("Connect: " + session.getRemoteAddress().getAddress()); try { session.getRemote().sendString("Hello Webbrowser"); } catch (IOException e) { System.out.println("IO Exception"); } } @OnWebSocketMessage public void onMessage(String message) { System.out.println("Message: " + message); } }
这是一个处理 WebSocket 请求的 Java 类。
@WebSocket public class MySocket { ... }
@WebSocket
是一个标识 WebSocket 类的注解。
@OnWebSocketConnect public void onConnect(Session session) { System.out.println("Connect: " + session.getRemoteAddress().getAddress()); try { session.getRemote().sendString("Hello Webbrowser"); } catch (IOException e) { System.out.println("IO Exception"); } }
@OnWebSocketConnect
注解标记了一个接收连接 Open 事件的方法。sendString
方法将消息发送回客户端。
@OnWebSocketMessage public void onMessage(String message) { System.out.println("Message: " + message); }
@OnWebSocketMessage
注解标记了一个接收来自客户端的消息事件的方法。消息将被写入我们启动 Jetty 的控制台。
<?xml version="1.0" encoding="UTF-8"?> <project name="WebSocket" default="compile"> <property name="name" value="ws"/> <property environment="env"/> <property name="src.dir" value="src"/> <property name="web.dir" value="${src.dir}/web" /> <property name="build.dir" location="${web.dir}/WEB-INF/classes"/> <property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/> <property name="dist.dir" location="dist"/> <property name="deploy.path" location="${env.JETTY_BASE}/webapps"/> <path id="compile.classpath"> <fileset dir="${jetty.lib.dir}"/> </path> <target name="init"> <mkdir dir="${build.dir}"/> <mkdir dir="${dist.dir}"/> </target> <target name="compile" depends="init"> <javac srcdir="${src.dir}" destdir="${build.dir}" includeantruntime="false"> <classpath refid="compile.classpath"/> </javac> <echo>Compilation completed</echo> </target> <target name="archive" depends="compile"> <war destfile="${dist.dir}/${name}.war" needxmlfile="false"> <fileset dir="${web.dir}"/> </war> <echo>Archive created</echo> </target> <target name="clean" depends="init"> <delete dir="${build.dir}"/> <delete dir="${dist.dir}"/> <echo>Cleaning completed</echo> </target> <target name="deploy" depends="archive"> <copy file="${dist.dir}/${name}.war" overwrite="true" todir="${deploy.path}"/> <echo>Archive deployed</echo> </target> </project>
这是项目的 Ant build.xml
文件。
$ java -jar $JETTY_HOME/start.jar --add-to-start=websocket
我们需要将 WebSocket 模块添加到 Jetty 的基础目录(base directory)中,如果它尚未启用的话。
$ curl localhost:8080/ws/wsexample HTTP GET method not implemented.
发送 HTTP GET 请求会产生此输出。

浏览器是一个连接到 WebSocket 服务器并发送消息的客户端。它还从服务器接收“Hello Webbrowser”消息。
... 2014-09-06 17:08:42.193:INFO:oejs.Server:main: Started @4094ms Connect: /127.0.0.1 Message: Hello Server Error: Timeout on Read Close: Idle Timeout
这些是出现在 Jetty 控制台上的消息。
WebSocket 客户端
在本节中,我们将展示如何从两个不同的客户端创建 WebSocket 请求。第一个客户端是一个使用 Python websocket 库的 Python 脚本。
$ sudo apt-get install python-websocket
我们需要安装 python-websocket
库。
#!/usr/bin/python import websocket websocket.enableTrace(True) ws = websocket.create_connection("ws://:8080/ws/wsexample") print "Sending message" ws.send("Hello server") result = ws.recv() print "Received '%s'" % result
该脚本将消息发送到我们的 WebSocket 服务器并接收响应。
$ ./pyclient.py Sending message Received 'Hello client'
运行程序,我们得到此输出。
在第二个客户端中,我们使用 Jetty 的 WebSocketClient
和 ClientUpgradeRequest
类来建立 WebSocket 连接并发送和接收消息。
package com.zetcode; import org.eclipse.jetty.websocket.api.Session; 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; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import java.io.IOException; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class JettyWebSocketClient { public static void main(String[] args) throws Exception { JettyWebSocketClient app = new JettyWebSocketClient(); app.start(); } public void start() throws Exception { WebSocketClient client = new WebSocketClient(); MyWebSocket socket = new MyWebSocket(); client.start(); URI destUri = new URI("ws://:8080/ws/wsexample"); ClientUpgradeRequest request = new ClientUpgradeRequest(); System.out.println("Connecting to: " + destUri); client.connect(socket, destUri, request); socket.awaitClose(5, TimeUnit.SECONDS); client.stop(); } @WebSocket public class MyWebSocket { private final CountDownLatch closeLatch = new CountDownLatch(1); @OnWebSocketConnect public void onConnect(Session session) throws IOException { System.out.println("Sending message: Hello server"); session.getRemote().sendString("Hello server"); } @OnWebSocketMessage public void onMessage(String message) { System.out.println("Message from Server: " + message); } @OnWebSocketClose public void onClose(int statusCode, String reason) { System.out.println("WebSocket Closed. Code:" + statusCode); } public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { return this.closeLatch.await(duration, unit); } } }
这个例子是一个 Java WebSocket 客户端。
URI destUri = new URI("ws://:8080/ws/wsexample");
创建了一个指向我们 websocket 端点的 URI。
ClientUpgradeRequest request = new ClientUpgradeRequest();
打开 WebSocket 连接的初始对话是通过 HTTP 协议完成的。客户端向服务器发送一个Upgrade请求。如果服务器支持 WebSocket 协议,它会同意协议切换。Jetty 使用 ClientUpgradeRequest
类来创建 Upgrade 请求。
client.connect(socket, destUri, request);
我们通过已建立的 WebSocket 连接到指定的 URI 并发送 Upgrade 请求。
session.getRemote().sendString("Hello server");
消息使用 sendString
方法发送。
<?xml version="1.0" encoding="UTF-8"?> <project name="JettyWebSocketClient" default="compile"> <property name="name" value="wsclient"/> <property environment="env"/> <property name="src.dir" value="src"/> <property name="build.dir" location="build"/> <property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/> <path id="compile.classpath"> <fileset dir="${jetty.lib.dir}"/> </path> <path id="run.classpath"> <pathelement path="${build.dir}"/> <fileset dir="${jetty.lib.dir}"> <include name="**/*.jar"/> </fileset> </path> <target name="init"> <mkdir dir="${build.dir}"/> </target> <target name="compile" depends="init"> <javac srcdir="${src.dir}" destdir="${build.dir}" includeantruntime="false"> <classpath refid="compile.classpath"/> </javac> <echo>Compilation completed</echo> </target> <target name="clean" depends="init"> <delete dir="${build.dir}"/> <echo>Cleaning completed</echo> </target> <target name="run" depends="compile"> <echo>Running the program</echo> <java classname="com.zetcode.JettyWebSocketClient" classpathref="run.classpath"/> </target> </project>
这是编译和运行 WebSocket 客户端的 Ant 构建文件。
$ ant run Buildfile: /home/janbodnar/prog/jetty/websocket3/build.xml init: compile: [echo] Compilation completed run: [echo] Running the program [java] 2014-09-07 23:13:39.796:INFO::main: Logging initialized @1230ms [java] Connecting to: ws://:8080/ws/wsexample [java] Sending message: Hello server [java] Message from server: Hello client [java] WebSocket closed. Code:1001 BUILD SUCCESSFUL Total time: 6 seconds
运行代码时,我们得到此输出。
在本章的 Jetty 教程中,我们已经在 Jetty 中建立了 WebSocket 连接。
作者
列出所有Java教程。