WebSocket协议:5分钟从入门到精通https://www.cnblogs.com/chyingp/p/websocket-deep-in.html
WebSocket协议入门介绍 https://www.cnblogs.com/nuccch/p/10947256.html
java的websocket客户端和服务端库 https://github.com/TooTallNate/Java-WebSocket
对于Tomcat中的websocket的了解 https://blog.csdn.net/zjgyjd/article/details/99686939
基于Tomcat的webSocket的使用方式和源码分析 https://blog.csdn.net/coder_what/article/details/109032940
websocket之三:Tomcat的WebSocket实现 https://www.cnblogs.com/duanxz/p/5041110.html
WebSocket通信原理和在Tomcat中实现源码详解(万字爆肝) http://dreamphp.cn/blog/detail?blog_id=29757#WebSocketTomcat_253
首先,客户端发起协议升级请求。可以看到,采用的是标准的HTTP报文格式,且只支持GET方法。
GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
重点请求首部意义如下:
Connection: Upgrade:表示要升级协议
Upgrade: websocket:表示要升级到websocket协议。
Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
Sec-WebSocket-Key:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
注意,上面请求省略了部分非重点请求首部。由于是标准的HTTP请求,类似Host、Origin、Cookie等请求首部会照常发送。在握手阶段,可以通过相关请求首部进行 安全限制、权限校验等。
服务端返回内容如下,状态代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
备注:每个header都以\r\n结尾,并且最后一行加上一个额外的空行\r\n。此外,服务端回应的HTTP状态码只能在握手阶段使用。过了握手阶段后,就只能采用特定的错误码。
客户端、服务端数据的交换,离不开数据帧格式的定义。因此,在实际讲解数据交换之前,我们先来看下WebSocket的数据帧格式。
WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。
发送端:将消息切割成多个帧,并发送给服务端;
接收端:接收消息帧,并将关联的帧重新组装成完整的消息;
WebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道保持连接没有断开。然而,对于长时间没有数据往来的连接,如果依旧长时间保持着,可能会浪费包括的连接资源。
但不排除有些场景,客户端、服务端虽然长时间没有数据往来,但仍需要保持连接。这个时候,可以采用心跳来实现。
发送方->接收方:ping
接收方->发送方:pong
ping、pong的操作,对应的是WebSocket的两个控制帧,opcode分别是0x9、0xA。
举例,WebSocket服务端向客户端发送ping,只需要如下代码(采用ws模块)
ws.ping('', false, true);
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.zzhuagroupId>
<artifactId>demo-base-websocketartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.java-websocketgroupId>
<artifactId>Java-WebSocketartifactId>
<version>1.5.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
project>
package com.zzhua;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.server.WebSocketServer;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.locks.LockSupport;
public class Test001 {
@Test
public void test01() {
WebSocketServer webSocketServer = new WebSocketServer(new InetSocketAddress(8080)) {
public void onOpen(WebSocket conn, ClientHandshake handshake) {
System.out.println("onOpen");
}
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("onClose");
}
public void onMessage(WebSocket conn, String message) {
System.out.println("onMessage");
}
public void onError(WebSocket conn, Exception ex) {
System.out.println("onError");
}
public void onStart() {
System.out.println("onStart");
}
};
webSocketServer.run();
}
@Test
public void test02() throws URISyntaxException, InterruptedException {
WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://localhost:8080")) {
public void onOpen(ServerHandshake handshakedata) {
System.out.println("client onOpen");
}
public void onMessage(String message) {
System.out.println("client onMessage");
}
public void onClose(int code, String reason, boolean remote) {
System.out.println("client onClose");
}
public void onError(Exception ex) {
System.out.println("client onError");
}
};
webSocketClient.connect();
Thread.sleep(5000);
LockSupport.park();
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.zzhuagroupId>
<artifactId>demo-tomcat-websocketartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>warpackaging>
<properties>
<jsp.version>2.1jsp.version>
<servlet.version>3.1.0servlet.version>
properties>
<dependencies>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>${jsp.version}version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>${servlet.version}version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.websocketgroupId>
<artifactId>javax.websocket-apiartifactId>
<version>1.1version>
<scope>providedscope>
dependency>
dependencies>
<build>
<finalName>0520carrentfinalName>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
<configuration>
<port>8080port>
<uriEncoding>UTF-8uriEncoding>
<path>/path>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.7.0version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
project>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
web-app>
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
for (MyWebSocketEndpoint myWebSocketEndpoint : MyWebSocketEndpoint.getConnections()) {
myWebSocketEndpoint.getSession().getBasicRemote().sendText("halo");
}
resp.getWriter().write("hello world");
}
}
package com.zzhua.ws;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint(value="/websocket")
public class MyWebSocketEndpoint {
private static final AtomicInteger counter = new AtomicInteger(0); // 客户端计数器
private static final Set<MyWebSocketEndpoint> connections = new CopyOnWriteArraySet<MyWebSocketEndpoint>(); // 客户端websocket连接集合
private Session session = null; // WebSocket会话对象
private Integer number = 0; // 客户端编号
public MyWebSocketEndpoint() {
number = counter.incrementAndGet();
}
/**
* 客户端建立websocket连接
* @param session
*/
@OnOpen
public void start(Session session) {
System.out.println("on open");
this.session = session;
connections.add(this);
try {
session.getBasicRemote().sendText(new StringBuffer().append("Hello: ").append(number).toString());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端断开websocket连接
*/
@OnClose
public void close() {
System.out.println("session close");
try {
this.session.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
connections.remove(this);
}
}
/**
* 接收客户端发送的消息
* @param message
*/
@OnMessage
public void message(String message) {
System.out.println("message: "+ message);
for(MyWebSocketEndpoint client : connections) {
synchronized (client) {
try {
client.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@OnError
public void error(Throwable t) {
System.out.println("client: error"+ number+t.getMessage());
}
public static Set<MyWebSocketEndpoint> getConnections() {
return connections;
}
public Session getSession() {
return session;
}
}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
请输入: <input id="content" type="text"/>
<button onclick="sendMsg()">发送button>
<button onclick="closeWebsocket()">关闭button>
<ul id="msgList">
ul>
body>
<script>
/* 参考: https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket */
// var socket = new WebSocket('ws://localhost:8080/websocket01');
var socket = new WebSocket('ws://localhost:8080/websocket/user001');
// var socket = new WebSocket('ws://[[${ip}]]:8080/websocket01');
// 指定连接成功后的回调
socket.onopen = function (event) {
console.log("建立连接成功")
}
// 发送消息给服务器
function sendMsg() {
var content = document.querySelector('#content');
socket.send(content.value) // 使用websocket发送消息到服务器
}
// 收到服务器的消息时的回调
socket.onmessage = function (ev) {
console.log("收到服务器消息: " + JSON.stringify(ev))
var ul = document.querySelector('#msgList');
var li = document.createElement('li');
li.innerText = ev.data
ul.appendChild(li)
}
// 手动关闭websocket
function closeWebsocket() {
socket.close()
}
// 指定连接关闭后的回调
socket.onclose = function (event) {
console.log("连接关闭")
}
script>
html>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
<relativePath/>
parent>
<groupId>com.zzhuagroupId>
<artifactId>demo-springbootartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>demo-springbootname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
@Configuration
public class WsConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
package com.zzhua.ws;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/*
1. @ServerEndpoint用于标记被修饰的类是一个 websocket服务器 的一个端点,且被标注的类必须有一个public的无参构造方法
2. @ServerEndpoint可以指定 端点发布的路径, 和其它重要的属性,如encoders编码器、decoders解码器
3. 端点发布的路径可以带路径变量,并且该路径变量名可以在@OnOpen和@OnClose标注的方法的方法参数中使用
如:发布路径是@ServerEndpoint(value="/websocket/{userId}"),则在@OnOpen修饰的方法中可以使用@PathParam("userId"),
4. @ServerEndpoint允许指定自定义配置器 ServerEndpointConfig.Configurator
*/
@Component
@ServerEndpoint(value="/websocket/{userId}/{username}")
public class MyWebSocketEndpoint {
private static final AtomicInteger counter = new AtomicInteger(0); // 客户端计数器
private static final Set<MyWebSocketEndpoint> connections = new CopyOnWriteArraySet<MyWebSocketEndpoint>(); // 客户端websocket连接集合
private Session session = null; // WebSocket会话对象
private Integer number = 0; // 客户端编号
public MyWebSocketEndpoint() {
number = counter.incrementAndGet();
}
/**
* 客户端建立websocket连接
* @param session
*/
@OnOpen
public void start(Session session, @PathParam("userId") String userId, @PathParam("username") String username) {
System.out.println("on open");
this.session = session;
connections.add(this);
try {
session.getBasicRemote().sendText(new StringBuffer().append("Hello: ").append(number).toString());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端断开websocket连接
*/
@OnClose
public void close(@PathParam("userId") String userId) {
System.out.println("session close");
try {
this.session.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
connections.remove(this);
}
}
/**
* 接收客户端发送的消息
* @param message
*/
@OnMessage
public void message(String message,@PathParam("userId") String userId,Session session) {
System.out.println("message: "+ message);
for(MyWebSocketEndpoint client : connections) {
synchronized (client) {
try {
client.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@OnError
public void error(Throwable t) {
System.out.println("client: error"+ number+t.getMessage());
}
public static Set<MyWebSocketEndpoint> getConnections() {
return connections;
}
public Session getSession() {
return session;
}
}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
请输入: <input id="content" type="text"/>
<button onclick="sendMsg()">发送button>
<button onclick="closeWebsocket()">关闭button>
<ul id="msgList">
ul>
body>
<script>
/* 参考: https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket */
// var socket = new WebSocket('ws://localhost:8080/websocket01');
var socket = new WebSocket('ws://localhost:8080/websocket/user001/username001');
// var socket = new WebSocket('ws://[[${ip}]]:8080/websocket01');
// 指定连接成功后的回调
socket.onopen = function (event) {
console.log("建立连接成功")
}
// 发送消息给服务器
function sendMsg() {
var content = document.querySelector('#content');
socket.send(content.value) // 使用websocket发送消息到服务器
}
// 收到服务器的消息时的回调
socket.onmessage = function (ev) {
console.log("收到服务器消息: " + JSON.stringify(ev))
var ul = document.querySelector('#msgList');
var li = document.createElement('li');
li.innerText = ev.data
ul.appendChild(li)
}
// 手动关闭websocket
function closeWebsocket() {
socket.close()
}
// 指定连接关闭后的回调
socket.onclose = function (event) {
console.log("连接关闭")
}
script>
html>
这样建立websocket连接是比较容易,但是不能所有客户端都能连接服务端吧,比如只有登录了,才能成功建立websocket连接,这样才比较合理。如果采用这种整合方式,就必须知道它建立ws连接的过程。它是依靠WsFilter过滤器拦截所有请求,判断是不是切换ws协议,如果是,则升级协议,完成握手。所以,
1、要么我们重写WsFilter的doFilter,先进行登录状态验证,再调用父类。
2、要么在WsFilter前,再插入一个过滤器,验证通过了,再交给WsFilter处理,这样WsFilter它就不用操心认证的事了。
3、要么配置一个自定义的ServerEndpointConfig.Configurator,重写里面的modifyHandshake方法,这里面是可以拿到请求相关的信息的。但是不方便的是,这个自定义配置器对象,是通过反射创建的,并不是由spring管理的,虽然我们可以通过拿到各种手段拿到spring容器的方式实现解耦,这是能想到的最简单的方式了。
看这个具体的实现之前,肯定是需要先看懂上面的实现的具体过程的,也就是说,上面相当于是使用原生的tomcat,只不过springboo帮我们注册端点类。而这里,spring抽象出了处理websocket的接口,屏蔽了各种web容器间的实现差异,里面也用到了很多的装饰者模式,不得不佩服spring的抽象能力哇。难怪之前我看的懵逼,会想:为什么有的时候上面这样写,有的时候下面那样写?到底谁优谁劣?它们实现一不一样?这2种写法能不能一起使用?谁优谁劣,一眼就可以看出来了,当然是下面这种更好啦,它又回归到我们熟悉的spring容器咯~
而且这里面实现也是参考了@EnableWebMvc一样的配置原理,并且处理过程也借鉴了springmvc的处理流程,它的握手流程也是通过springmvc的HttpRequestHandler类型的处理器(WebSocketHttpRequestHandler)介入交给握手处理器AbstractHandshakeHandler,握手处理器里面通过协议升级策略方式来屏蔽不同web容器的差异。
在查看源码的时候,上面这种方式和下面这种方式,握手后的协议升级最终都会到UpgradeUtil#doUpgrade这个方法,这个比较关键,在这个doUpgrade方法里,会调用HttpServletRequest#upgrade(WsHttpUpgradeHandler.class)创建一个WsHttpUpgradeHandler对象(在协议升级后,接收到消息时,也是用这个对象处理的),
源码还可以留意到:AbstractProtocol$ConnectionHandler(静态内部类)#process方法和connections属性。在process里面,由“不变的processor”处理请求,如果是协议升级请求,则处理后会返回UPGRADING状态,这其中就包含了协议升级过程,processor中可以得到UpgradeToken,然后创建出一个新的processor放入connections中,再从UpgradeTken中获取httpUpgradeHandler初始化这个新的processor。由于connections中的key就是socket,当socket中有数据来了,就可以找到对应的processor,交给此processor处理了。并且 我们还注意这里面是有个while循环的,跳出的条件是返回的状态不为SocketState.UPGRADING,所以此时新的processor会立马执行一遍processor.process(wrapper, status),相当于前面这一步是在握手,握手之后的这一步是在通知连接建立成功,debug的时候,注意这一点就好了
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
<relativePath/>
parent>
<groupId>com.zzhuagroupId>
<artifactId>demo-springbootartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>demo-springbootname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {
public static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry
.addHandler(new TextWebSocketHandler() { // WebSocketHandler接口
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("建立websocket连接后的回调," + session + "," + this + RequestContextHolder.getRequestAttributes());
// 存入sessionMap,实际应该关联到用户
String name = (String) session.getAttributes().get("name");
System.out.println("与: {}建立链接成功" + name);
sessionMap.put(name, session);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
System.out.println("收到websocket消息," + session + "," + message+ RequestContextHolder.getRequestAttributes());
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("websocket连接关闭后回调" + "," + status + RequestContextHolder.getRequestAttributes());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接发生错误被回调");
}
}, "/websocket01")
.addInterceptors(new HandshakeInterceptor() { // HandshakeInterceptor接口
@Override // 这里回传的ServerHttpRequest(实现类为ServletServerHttpRequest)包裹了HttpServletRequest
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("握手前的回调," + wsHandler + ","+ attributes + "," + RequestContextHolder.getRequestAttributes());
// 也可以从session中获取用户信息(也可以使用请求头放token,只要能获取到用户都ok)
String name = ((ServletServerHttpRequest) request).getServletRequest().getParameter("name");
System.out.println("握手拦截器,获取name: {}"+name);
synchronized (this) {
if (!sessionMap.containsKey(name)) {
attributes.put("name", name);
System.out.println("sessionMap中不包含此name:{},允许建立链接"+name);
return true;
}
System.out.println("sessionMap中已包含此name:{}"+name);
return false;
}
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
System.out.println("握手后回调, " + "," + wsHandler + "," + exception + RequestContextHolder.getRequestAttributes());
}
})
.setHandshakeHandler(new MyHandShakeHandler()) // HandshakeHandler
.setAllowedOrigins("*")
// 开启sockJs,记得把websocketEnabled也给开启了,否则客户端只能使用sockJs,而不能使用websocket
// .withSockJS()
// .setWebSocketEnabled(true)
;
}
static class MyHandShakeHandler implements HandshakeHandler {
// 借助Spring提供的DefaultHandshakeHandler完成握手(仅仅包裹了一下spring封装的DefaultShakeHandler)
private DefaultHandshakeHandler defaultHandshakeHandler = new DefaultHandshakeHandler();
@Override
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws HandshakeFailureException {
System.out.println("开始握手..." + RequestContextHolder.getRequestAttributes());
boolean flag = defaultHandshakeHandler.doHandshake(request, response, wsHandler, attributes);
System.out.println("握手完成..." + flag);
return flag;
}
}
}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
请输入: <input id="content" type="text"/>
<button onclick="sendMsg()">发送button>
<button onclick="closeWebsocket()">关闭button>
<ul id="msgList">
ul>
body>
<script>
/* 参考: https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket */
// var socket = new WebSocket('ws://localhost:8080/websocket01');
// var socket = new WebSocket('ws://localhost:8080/websocket/user001/username001');
var socket = new WebSocket('ws://localhost:8080/websocket01?name=zzhua');
// var socket = new WebSocket('ws://[[${ip}]]:8080/websocket01');
// 指定连接成功后的回调
socket.onopen = function (event) {
console.log("建立连接成功")
}
// 发送消息给服务器
function sendMsg() {
var content = document.querySelector('#content');
socket.send(content.value) // 使用websocket发送消息到服务器
}
// 收到服务器的消息时的回调
socket.onmessage = function (ev) {
console.log("收到服务器消息: " + JSON.stringify(ev))
var ul = document.querySelector('#msgList');
var li = document.createElement('li');
li.innerText = ev.data
ul.appendChild(li)
}
// 手动关闭websocket
function closeWebsocket() {
socket.close()
}
// 指定连接关闭后的回调
socket.onclose = function (event) {
console.log("连接关闭")
}
script>
html>