WebSocket Day02 : 握手连接
前言
握手连接是WebSocket建立通信的第一步,通过客户端和服务器之间的一系列握手操作,确保了双方都支持WebSocket协议,并达成一致的通信参数。握手连接的过程包括客户端发起握手请求、服务器响应握手请求以及双方完成握手连接。完成握手连接后,客户端和服务器之间建立了一个持久性的双向通信链路,可以进行实时的数据传输。
本次案例,使用 servlet 实现一个用户登录进入聊天室聊天。
一、前期准备
1、新建项目,结构如下
2、导入依赖
<!-- websocket 依赖 --><dependency><groupId>javax.websocket</groupId><artifactId>javax.websocket-api</artifactId><version>1.1</version><scope>provided</scope></dependency><!-- 打印日志 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.3.8</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><!-- ch02 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.14.2</version></dependency>
让我逐个解释这些依赖项的作用:
javax.websocket-api: 这是Java WebSocket API的依赖项,用于在Java应用程序中实现WebSocket通信。
logback-classic: 这是Logback日志框架的经典实现,用于打印日志。
lombok: 这是一个Java库,用于通过注解减少Java代码的样板代码量,提高代码的可读性和简洁性。
javax.servlet-api: 这是Java Servlet API的依赖项,用于开发基于Java的Web应用程序。
jackson-databind: 这是Jackson JSON处理库的依赖项,用于在Java应用程序中进行JSON数据的序列化和反序列化操作。
二、使用 servlet 实现登录功能
1、新建一个 loginServlet
/*** @Date 2023-11-01* @Author qiu* 用户登录*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String userName = req.getParameter("userName");// 将用户名保存到 HttpSessionreq.getSession().setAttribute("user",userName);// 重定向到聊天的首页resp.sendRedirect("chat.html");}
}
让我来逐行解释这段代码的功能:
@WebServlet("/login")
:这是一个Servlet注解,指定了该Servlet对应的URL路径为"/login"。当客户端发送带有"/login"路径的请求时,Servlet容器将调用该Servlet来处理请求。
public class LoginServlet extends HttpServlet
:这是一个命名为LoginServlet的Java类,它继承自HttpServlet类,表示它是一个Servlet。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
:这是Servlet的service方法,它负责处理客户端的请求并返回响应。HttpServletRequest对象提供了关于HTTP请求的信息,而HttpServletResponse对象用于设置HTTP响应。
String userName = req.getParameter("userName");
:从HTTP请求中获取名为"userName"的参数值,并将其存储在一个名为userName的字符串变量中。这里假设前端通过HTTP请求将用户名作为参数传递给后端。
req.getSession().setAttribute("user",userName);
:使用HttpServletRequest的getSession()方法获取当前会话的HttpSession对象,并使用setAttribute()方法将用户名存储在名为"user"的属性中。这样,在整个会话期间,可以通过getAttribute()方法来访问和使用该属性。
resp.sendRedirect("chat.html");
:使用HttpServletResponse的sendRedirect()方法将响应重定向到"chat.html"页面。这意味着登录成功后,用户将被重定向到聊天页面。
以上就是这段代码的功能。它通过获取用户输入的用户名,并将其保存到会话中,实现了用户登录的功能。然后,通过重定向将用户导航到聊天页面。
2、新建一个 html 页面实现登录
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>用户登录</h1>
<form name="f1" method="post" action="login">
Name:<input type="text" name="userName"/>
<input type="submit" value="登录">
</form>
</body>
</html>
让我来逐行解释这段代码的功能:
<form name="f1" method="post" action="login">
:这是一个表单(form标签),用于接收用户输入的登录信息,并将其提交到名为"login"的URL路径。
Name:<input type="text" name="userName"/>
:这是一个文本输入框(input标签),用于用户输入用户名。name属性指定了该输入框的名称为"userName",以便后端能够通过该名称获取用户输入的值。
<input type="submit" value="登录">
:这是一个提交按钮(input标签),用于提交表单数据。当用户点击该按钮时,表单中的数据将被发送到服务器进行处理。
三、握手连接
1、新建一个 消息对象 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {/*** 发送人*/private String fromUser;/*** 发送时间*/private String sendTime;/*** 发送内容*/private String content;}
让我来逐行解释这段代码的功能:
@Data
:这是一个Lombok注解,自动生成getter、setter、equals、hashCode和toString方法等常见的代码。
@AllArgsConstructor
:这是一个Lombok注解,自动生成一个包含所有属性的构造函数。
@NoArgsConstructor
:这是一个Lombok注解,自动生成一个无参构造函数。
public class Message
:这是一个命名为Message的Java类。
private String fromUser;
:这是一个私有的字符串类型变量,表示消息的发送人。
private String sendTime;
:这是一个私有的字符串类型变量,表示消息的发送时间。
private String content;
:这是一个私有的字符串类型变量,表示消息的内容。
以上就是这段代码的功能。它定义了一个Message类,用于表示一条消息的数据结构。该类包含了发送人、发送时间和发送内容三个属性,并使用Lombok注解简化了相应的代码编写工作。通过创建Message对象,可以方便地操作和传递消息的相关信息。
2、握手连接处理类
public class WebSocketHandshake extends Configurator {/*** 重写握手处理方法* @param sec* @param request 请求对象* @param response 响应对象*/@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {// 获取 HttpSession 对象HttpSession httpSession = (HttpSession)request.getHttpSession();// 获取用户名String userName = (String) httpSession.getAttribute("user");// 将用户名保存到当前用户连接 websocket 的 session 中sec.getUserProperties().put("user",userName);}}
让我来逐行解释这段代码的功能:
public class WebSocketHandshake extends Configurator
:这是一个命名为WebSocketHandshake的Java类,继承了Configurator类。
@Override
:这是一个注解,表示该方法重写了父类或接口的方法。
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
:这是一个公共的无返回值方法,用于修改WebSocket握手过程中的处理逻辑。它接受三个参数:
sec
:ServerEndpointConfig对象,用于配置WebSocket端点。request
:HandshakeRequest对象,表示握手请求。response
:HandshakeResponse对象,表示握手响应。
HttpSession httpSession = (HttpSession)request.getHttpSession();
:获取握手请求中的HttpSession对象,用于获取用户相关的会话信息。
String userName = (String) httpSession.getAttribute("user");
:从HttpSession对象中获取名为"user"的属性,即用户名。
sec.getUserProperties().put("user",userName);
:将获取到的用户名保存到当前用户连接WebSocket的session中。通过sec.getUserProperties()
可以获取到与当前用户连接相关的会话属性,这里将用户名保存在"user"属性中。
以上就是这段代码的功能。它通过重写WebSocket握手处理方法,在握手过程中获取并保存了用户名,以便后续在WebSocket连接中使用。
1)为什么需要这个类握手处理类
这个类是用于在WebSocket握手过程中获取并保存用户名的。WebSocket是一种基于TCP协议的全双工通信协议,它通过在客户端和服务器之间建立持久连接,实现实时的双向通信。
在实际应用中,往往需要对连接WebSocket的用户进行身份验证和权限控制。而在WebSocket握手过程中,可以通过HTTP协议传递一些额外的信息,比如用户的身份信息。为了在后续的WebSocket连接中能够对用户进行身份验证和权限控制,我们需要将用户相关的信息保存起来。
这个WebSocketHandshake类的作用就是在WebSocket握手过程中,获取用户的身份信息(这里是用户名),并将其保存到当前用户连接的WebSocket会话中。通过在握手过程中获取用户名,并将其保存在WebSocket的session中,我们可以在后续的WebSocket连接中使用这个信息进行身份验证和权限控制。
因此,这个类的存在是为了方便在WebSocket应用中获取和保存用户信息,以便后续进行进一步的处理和控制。
3、服务端
@Slf4j
@ServerEndpoint(value = "/connect",configurator = WebSocketHandshake.class)
public class ChatServer {// 用户列表,key 为用户 id 或者是 name,// value 则是每一个客户端的 Sessionprivate static Map<String,Session> users = new HashMap<>();@OnOpenpublic void onOpen(Session session){// 添加用户到用户列表String userName = (String) session.getUserProperties().get("user");// 添加到用户列表中users.put(userName,session);}@OnMessagepublic void onMessage(String message,Session session) throws Exception {// 获取发送人String formUser = (String) session.getUserProperties().get("user");// 创建发送时间String sendTime = new SimpleDateFormat("hh:mm").format(new Date());// 封装消息对象并序列化为 JSONMessage msg = new Message(formUser,sendTime,message);String jsonMessage = new ObjectMapper().writeValueAsString(msg);log.info(jsonMessage);// 群发给所有人for (String userName : users.keySet()){Session s = users.get(userName);s.getBasicRemote().sendText(jsonMessage);}}@OnClosepublic void onClose(Session session){// 将用户移除在线列表String userName = (String) session.getUserProperties().get("user");users.remove(userName);}}
这是一个基于Java实现的WebSocket聊天室后端代码。
首先,在websocket包下,使用了@ServerEndpoint(value = "/connect")
注解声明了一个WebSocket服务端,对应的WebSocket地址为/connect
。
接下来,代码中定义了一个静态变量users
,用来存储所有连接到该WebSocket服务端的用户Session。在用户连接WebSocket服务器时,通过@OnOpen
注解声明的方法将用户Session添加到用户列表中。
在@OnMessage
注解声明的方法中,当WebSocket服务端接收到客户端发送的消息时,先获取发送人和发送时间,然后封装成一个Message
对象并序列化成JSON格式。然后遍历用户列表,将消息发送给每一个客户端。
最后,在@OnClose
注解声明的方法中,当一个用户关闭连接时,将其从用户列表中移除。
需要注意的是,在上述代码中还使用了@ServerEndpoint
注解的configurator参数,通过自定义WebSocketHandshake
类实现了WebSocket握手过程的一些特殊处理。具体可查看WebSocketHandshake
类的实现。
总的来说,这是一个简单的WebSocket聊天室后端实现,通过Java提供的WebSocket API完成了对WebSocket连接的管理和消息的广播。
4、新建一个客户端聊天页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="js/JQuery文件.txt.js"></script>
</head>
<body>
<h1>聊天室</h1>
<div id="msg"><input type="text" id="message"/><input type="button" value="发送"/><br></div><script>// 创建 WebSocket 对象var ws = new WebSocket("ws://localhost:8080/connect");// 接受服务端的信息ws.onmessage = function (event) {// 将消息填充到 div 中let data = event.data;// 将 json 字符串 转换为 json 对象data = $.parseJSON(data);$('#msg').append(data.fromUser + " : " + data.sendTime + "<br>");$('#msg').append(data.content + "<br>");}$(function () {$(':button').on('click',function () {let msg = $('#message').val();// 发送消息ws.send(msg);// 发送完之后清空消息框$('#message').val('');})})</script></body>
</html>
这是一个简单的前端实现,用于创建一个基本的聊天室界面,并通过WebSocket协议与服务器进行通信。下面逐行详细讲解代码的功能和实现。
<script src="js/JQuery文件.txt.js"></script>
:引入一个JavaScript文件,其中包含了JQuery库的代码。
<h1>聊天室</h1>
:在页面中插入一个标题,显示为"聊天室"。
<div id="msg">
:定义一个div元素,用于显示聊天消息。
<input type="text" id="message"/>
:创建一个文本输入框,用户可以在其中输入消息。
<input type="button" value="发送"/><br>
:创建一个按钮,用于发送消息。
<script>
:JavaScript代码的开始标签。
var ws = new WebSocket("ws://localhost:8080/connect");
:创建一个WebSocket对象,连接到指定的服务器地址(这里是"ws://localhost:8080/connect")。
ws.onmessage
:WebSocket对象的onmessage事件,用于接收从服务器发送的消息。
let data = event.data;
:将接收到的消息内容存储在变量data中。
data = $.parseJSON(data);
:使用JQuery库中的parseJSON函数,将接收到的json格式消息转换为json对象。
$('#msg').append(data.fromUser + " : " + data.sendTime + "<br>");
:将消息发送者和发送时间显示在页面上。
$('#msg').append(data.content + "<br>");
:将消息内容显示在页面上。
$('button').on('click', function () {
:当用户点击按钮时触发一个匿名函数。
let msg = $('#message').val();
:获取文本输入框中的消息内容,并存储在变量msg中。
ws.send(msg);
:通过WebSocket发送消息给服务器。
$('#message').val('');
:清空文本输入框。
</script>
:JavaScript代码的结束标签。
总体来说,这段代码实现了一个简单的聊天室界面,用户可以在文本输入框中输入消息,点击发送按钮后,消息会通过WebSocket协议发送给服务器。同时,页面会接收服务器返回的消息并将其显示在页面上。但需要注意的是,这只是前端部分的实现,后端代码和服务器的支持是实现完整聊天室功能的必要条件。
5、运行效果
本次案例就是通过实现用户的登录,然后把用户名保存到作用域中,然后再从作用域中获取用户名实现的一个多人聊天案例。通过登录去获取到进入聊天室的人是谁。
四、gitee 案例
地址:ch02 · qiuqiu/WebSocket-study - 码云 - 开源中国 (gitee.com)
相关文章:

WebSocket Day02 : 握手连接
前言 握手连接是WebSocket建立通信的第一步,通过客户端和服务器之间的一系列握手操作,确保了双方都支持WebSocket协议,并达成一致的通信参数。握手连接的过程包括客户端发起握手请求、服务器响应握手请求以及双方完成握手连接。完成握手连接后…...

c#的反编译工具ISPY和net reflector 使用比较
我有一份Asp.net程序需要修改,但没有源码,只有dll,需要使用反编译工具回复源码,尝试使用了市面上的两种主流的工具ISPY和net reflector ,最终用ISPY恢复了源码。 比较 ISPY 恢复的代码和实际有差距,但还能…...

基于LDA主题+协同过滤+矩阵分解算法的智能电影推荐系统——机器学习算法应用(含python、JavaScript工程源码)+MovieLens数据集(四)
目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据爬取及处理2. 模型训练及保存3. 接口实现4. 收集数据5. 界面设计 系统测试相关其它博客工程源代码下载其它资料下载 前言 前段时间,博主分享过关于一篇使用协同过滤算法进行智能电影推荐系统的博…...

方阵行列式与转置矩阵
1.转置矩阵:格式规定:如果矩阵A为n阶方阵,那么A的T次方为矩阵A的转置矩阵,即将矩阵A的行与列互换。 2.转置矩阵的运算性质: 1.任何方阵的转置矩阵的转置矩阵为方阵自身。 2.多个矩阵的和的转置矩阵等于多个转置矩阵的…...

【Java 进阶篇】Java Cookie共享:让数据穿越不同应用的时空隧道
在Web开发中,Cookie是一种常见的会话管理技术,用于存储和传递用户相关的信息。通常,每个Web应用都会在用户的浏览器中设置自己的Cookie,以便在用户与应用之间保持状态。然而,有时我们需要在不同的应用之间共享Cookie数…...

甘特图组件DHTMLX Gantt用例 - 如何拆分任务和里程碑项目路线图
创建一致且引人注意的视觉样式是任何项目管理应用程序的重要要求,这就是为什么我们会在这个系列中继续探索DHTMLX Gantt图库的自定义。在本文中我们将考虑一个新的甘特图定制场景,DHTMLX Gantt组件如何创建一个项目路线图。 DHTMLX Gantt正式版下载 用…...

克里金插值matlab代码
% 克里金插值示例 clc; clear; % 生成模拟数据 x linspace(0, 10, 11); y linspace(0, 10, 11); [X, Y] meshgrid(x, y); Z sin(sqrt(X.^2 Y.^2)) 0.1 * randn(size(X)); % 设置克里金参数 nugget 0.1; % 块金值 range 1; % 范围 sill 1; % 基台值 azimuth …...

【LeetCode】23. 合并 K 个升序链表
题目链接:23. 合并 K 个升序链表 题目描述: 数据范围: **思考:**这题实际上就是合并两个有序列表的进阶版,只不过这里变成了合并K个,那么这里我们显然就知道,核心的合并两个有序列表的思路不…...

2023年【熔化焊接与热切割】免费试题及熔化焊接与热切割考试总结
题库来源:安全生产模拟考试一点通公众号小程序 熔化焊接与热切割免费试题参考答案及熔化焊接与热切割考试试题解析是安全生产模拟考试一点通题库老师及熔化焊接与热切割操作证已考过的学员汇总,相对有效帮助熔化焊接与热切割考试总结学员顺利通过考试。…...

为什么要学中文编程?它能有哪些益处?免费版编程工具怎么下载?系统化的编程教程课程怎么学习
一、为什么要学习这个编程工具?能给自己带来什么益处? 1、不论在哪里上班,都不是铁饭碗:现在全球经济低迷,使得很多企业倒闭, 大到知名国企小到私营企业,大量裁员。任何人都无法保证自己现在的…...

数据分析实战 - 2 订单销售数据分析(pandas 进阶)
题目来源:和鲸社区的题目推荐: 刷题源链接(用于直接fork运行 https://www.heywhale.com/mw/project/6527b5560259478972ea87ed 刷题准备 请依次运行这部分的代码(下方4个代码块),完成刷题前的数据准备 …...

测试服务器端口是否开通,计算退休时间
本案例知识点 netstat -tuln | grep 80 nestat 目前主机打开的网络服务端口,-tuln目前主机启动的服务,如图 报错说参数太多,仔细检查发现if后的中括号内,变量少双引号导致,改完之后运行显示22,25端口开放࿰…...

Prometheus接入AlterManager配置企业微信告警(基于K8S环境部署)
文章目录 一、创建企业微信机器人二、配置AlterManager告警发送至企业微信三、Prometheus接入AlterManager配置四、部署PrometheusAlterManager(放到一个Pod中)五、测试告警 注意:请基于 PrometheusGrafana监控K8S集群(基于K8S环境部署)文章之上做本次实验。 一、创…...

11.1 Linux 设备树
一、什么是设备树? 设备树(Device Tree),描述设备树的文件叫做 DTS(DeviceTree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息: 树的主干就是系统总线, IIC 控制器、 GPIO 控制…...

万宾科技管网水位监测助力智慧城市的排水系统
以往如果要了解城市地下排水管网的水位变化,需要依靠人工巡检或者排查的方式,这不仅加大了人员的工作量,而且也为市政府带来了更多的工作难题。比如人员监管监测不到位或无法远程监控等情况,都会降低市政府对排水管网的管理能力&a…...
Glide transform CircleCrop()圆图,Kotlin
Glide transform CircleCrop()圆图,Kotlin import android.os.Bundle import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.load.resource.bitmap.CircleCropclass MainActivity : AppCompatActivity() {o…...

从NetSuite Payment Link杂谈财务自动化、数字化转型
最近在进行信息化的理论学习,让我有机会跳开软件功能,用更加宏大的视野,来审视我们在哪里,我们要到哪去。 在过去20多年,我们的财务软件经历了电算化、网络化、目前处于自动化、智能化阶段。从NetSuite这几年的功能发…...

1.UML面向对象类图和关系
文章目录 4种静态结构图类图类的表示类与类之间的关系依赖关系(Dependency)关联关系(Association)聚合(Aggregation)组合(Composition)实现(Realization)继承/泛化(Inheritance/Generalization)常用的UML工具reference欢迎访问个人网络日志🌹🌹知行空间🌹🌹 4种静态结构…...

JAVA小说小程序系统是怎样开发的
随着移动互联网的普及,小说阅读已经成为人们休闲娱乐的重要方式之一。为了满足广大读者的需求,我们开发了一款基于JAVA编程语言的小说小程序系统。本系统旨在提供一种便捷、高效、有趣的阅读体验,让用户能够随时随地阅读最新、最热门的小说。…...

【深度学习】pytorch——Tensor(张量)详解
笔记为自我总结整理的学习笔记,若有错误欢迎指出哟~ pytorch——Tensor 简介创建Tensortorch.Tensor( )和torch.tensor( )的区别torch.Tensor( )torch.tensor( ) tensor可以是一个数(标量)、一维数组(向量)、二维数组&…...

装修服务预约小程序的内容如何
大小装修不断,市场中大小品牌也比较多,对需求客户来说,可以线下咨询也可以线上寻找品牌,总是可以找到满意的服务公司,而对装修公司来说如今线下流量匮乏,很多东西也难以通过线下方式承载,更需要…...

easypoi 导出Excel 使用总结
easypoi 导出Excel 导出Excel需要设置标题,且标题是多行,标题下面是列表头 设置表格标题 ExportParams headExportParams new ExportParams();StringBuilder buffer new StringBuilder("");buffer.append("1、课程名称:....…...

MySQL性能优化的最佳20条经验
概述 关于数据库的性能,这并不只是DBA才需要担心的事。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。下面讲下MySQL性能优化的一些点。 1. 为查询缓存优化你的查询 大多数的MySQL服务器…...

【Liunx基础】之指令(一)
【Liunx基础】之指令(一) 1.ls指令2.pwd命令3.cd指令4.touch指令5.mkdir指令(重要)6.rmdir指令与rm指令(重要)7.man指令(重要)8.cp指令(重要) 📃博客主页: 小…...

jQuery案例专题
jQuery案例专题 本学期主要担任的课程是js和jQuery,感觉用到的有一些案例挺有意思的,就对其进行了一下整理。 目录: 电影院的幕帘特效 手风琴特效 星光闪烁 网页轮播图 1.电影院的幕帘特效代码如下 html <!DOCTYPE html > <html…...

【Linux】服务器间免登陆访问
准备两台服务器,服务器A,服务器B 在服务器A中实现免登陆服务器B 进入服务器A操作 进入目录/root/.ssh cd /root/.ssh秘钥对使用默认文件名 生成秘钥对,在输入秘钥文件时直接回车则会使用默认文件名:id_rsa ssh-keygen -t rsa…...

【信息安全原理】——IP及路由安全(学习笔记)
目录 🕒 1. IPv4协议及其安全性分析🕒 2. IPsec(IP Security)🕘 2.1 IPsec安全策略🕤 2.1.1 安全关联(Security Association, SA)🕤 2.1.2 安全策略(Security…...

【jvm】虚拟机之本地方法栈
目录 一、说明二、注意 一、说明 1. Java虚拟机栈用于管理Javaj法的调用,而本地方法栈用于管理本地方法的调用。 2. 本地方法栈,也是线程私有的。 3. 允许被实现成固定或者是可动态扩展的内存大小。 (在内存溢出方面是相同) 4. 如果线程请求分…...

『CV学习笔记』图像超分辨率等图像处理任务中的评价指标PSNR(峰值信噪比)
图像超分辨率等图像处理任务中的评价指标PSNR(峰值信噪比) 文章目录 一. PSNR(峰值信噪比)1.1. 定义1.2. 作用1.3. 例子1.4 . PSNR评价标准二. 参考文献一. PSNR(峰值信噪比) 1.1. 定义 峰值信噪比(Peak Signal-to-Noise Ratio, PSNR)是图像超分辨率等图像处理任务中常用的一…...

【51nod 连续区间】 题解(序列分治)
题目描述 区间内的元素元素排序后 任意相邻两个元素值差为 1 1 1 的区间称为“连续区间”。 如 3 , 1 , 2 3,1,2 3,1,2 是连续区间, 3 , 1 , 4 3,1,4 3,1,4 不是连续区间。 给出一个 1 ∼ n 1 \sim n 1∼n 的排列,问有多少连续区间。 …...