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教程。