WebScoket 规范 + WebSocket 协议

WebSocket握手协议

1、客户端握手请求(注意:键值之间有一个空格,行间有换行符号0x13x10或者说\r\n)
GET /WebSocket/LiveVideo HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: localhost:8080             (客户端请求主机)
Origin: http://127.0.0.1         (来源网页地址)
Sec-WebSocket-Key1: 23 asdfJKj,asdjk
Sec_WebSocket-Key2: wewerw234 jij998
0x13x10 + 8个字节Sec_WebSocket-Key3值,没有键名(注意,这里的0x13x10的额外的,也就说有两个连续的0x13x10)

2、服务端握手回复
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://127.0.0.1     (来源网页地址)
Sec-WebSocket-Location: ws://localhost:8080/WebSocket/LiveVideo
16个字节的加密KEY

加密KEY算法:

Sec_WebSocket-Key1的产生方式:
(1)提取客户端请求的Sec_WebSocket-Key1中的数字符组成字符串k1
(2)转换字符串为8个字节的长整型intKey1
(3)统计客户端请求的Sec_WebSocket-Key1中的空格数k1Spaces
(4)intK1/k1Spaces取整k1FinalNum
(5)将k1FinalNum转换成字节数组再反转最终形成4个字节的Sec_WebSocket-Key1

Sec_WebSocket-Key2的产生方式:
(1)提取客户端请求的Sec_WebSocket-Key2中的数字符组成字符串k2
(2)转换字符串为8个字节的长整型intKey2
(3)统计客户端请求的Sec_WebSocket-Key2中的空格数k2Spaces
(4)intK2/k2Spaces取整k2FinalNum
(5)将k2FinalNum转换成字节数组再反转最终形成4个字节的Sec_WebSocket-Key2

Sec_WebSocket-Key3的产生方式:
客户端握手请求的最后8个字节

将Sec_WebSocket-Key1、Sec_WebSocket-Key2、Sec_WebSocket-Key3合并成一个16字节数组
再进行MD5加密形成最终的16个字节的加密KEY

3、消息发送接收
客户端和服务端发送非握手文本消息时,消息以utf-8编码,并以0x00开头,0xFF结尾。

 

 WebScoket 规范
4.1 握手协议
websocket 是 独立的基于TCP的协议, 其跟http协议的关系仅仅是 WebSocket 的握手被http 服务器当做 Upgrade request http包处理。 websocket 有自己的握手处理。 TCP连接建立后,client 发送websocket 握手请求. 请求包需求如下:
必须是有效的http request 格式
HTTP request method 必须是GET,协议应不小于1.1 如: Get /chat HTTP/1.1
必须包括Upgrade 头域,并且其值为“websocket”
必须包括"Connection" 头域,并且其值为 "Upgrade"
必须包括"Sec-WebSocket-Key"头域,其值采用base64编码的随机16字节长的字符序列, 服务器端根据该域来判断client 确实是websocket请求而不是冒充的,如http。响应方式是,首先要获取到请求头中的Sec-WebSocket-Key的值,再把这一段 GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"加到获取到的Sec-WebSocket-Key的值的后面,然后拿这个 字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,就得到了返回给客户端的Sec-WebSocket-Accept的http响应头的值。
如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接。
必须包括"Sec-webSocket-Version" 头域,当前值必须是13.
可能包括"Sec-WebSocket-Protocol",表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之。
可能包括"Sec-WebSocket-Extensions", 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强
可能包括任意其他域,如cookie
示例如下:
        GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Server 接手到握手请求后应处理该请求包括:
处理请求包括处理GET 方法
验证Upgrader头域
验证Connection 头域
处理Sec-WebSocket-Key头域,方法见上;
处理Sec-WebSocket-Version
处理Origin头域,可选, 浏览器必须发送该头域
处理Sec-WebSocket-Protocol头域,可选
处理Sec-WebSocket-Extensions 头域,可选
处理其他头域,可选
Server 发送握手响应,这里只介绍服务器接受该连接情况下,包括:
http Status-Line
Upgrade 头域 ,值必须是"websocket"
Conntion头域,值必须是:“Upgrade”
Sec-WebSocket-Accept” 头域,该头域的值即处理Sec-WebSocket-Key" 域后的结果。
可选的"Sec-WebSocket-Protocol"头域
可选的"Sec-WebSocket-Extensions"头域
响应可能如下:
        HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
4.2 数据传输
   在WebSocket 协议中,使用序列frames方式来传输数据。一个frame的标准格式如下:
      0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
FIN:1位,是否是消息的结束帧(分片)
RSV1, RSV2, RSV3: 分别都是1位, 预留,用于约定自定义协议。 如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接;
Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:
%x0 表示连续消息分片
%x1 表示文本消息分片
%x2 表未二进制消息分片
%x3-7 为将来的非控制消息片断保留的操作码
%x8 表示连接关闭  %x9 表示心跳检查的ping
%xA 表示心跳检查的pong
%xB-F 为将来的控制消息片断的保留操作码
Mask: 定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;
Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果 这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符 合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而 此时负载数据的长度就为应用数据的长度。注意Payload length不包括Masking-key在内。
Masking-key: 0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。 数据Mask方法是,第 i byte 数据 = orig-data ^ (i % 4) .
Payload data: (x+y)位,负载数据为扩展数据及应用数据长度之和。
Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内。
Application data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。
把消息分片处理主要是处于以下两个原因:
消息接收方事先并不知道消息大小, 而且也没必要预留一个足够大的buffer来处理;
multiplexing
消息分片一些规则如下(不全):
为分片消息(single-frame) 其FIN置为1,并且opcode code 不是 0;
分片消息序列如下, 第一帧FIN置为0,opcode code不是0; 接着是FIN置为0,opcode code也是0; 最后帧 FIN为1,opcode code为0.
在分片消息发送期间可能插入了控制帧
控制帧不能分片
控制帧的opcode符号位为1, 目前控制帧包括 0×8(Close), 0×9(Ping) 0xA (Pong). 0xB-0xF 被预留。
    ws-frame                = frame-fin
frame-rsv1
frame-rsv2
frame-rsv3
frame-opcode
frame-masked
frame-payload-length
[ frame-masking-key ]
frame-payload-data
frame-fin               = %x0 ; 表示这不是当前消息的最后一帧,后面还有消息
/ %x1 ; 表示这是当前消息的最后一帧
frame-rsv1              = %x0
; 1 bit, 如果没有扩展约定,该值必须为0
frame-rsv2              = %x0
; 1 bit, 如果没有扩展约定,该值必须为0
frame-rsv3              = %x0
; 1 bit, 如果没有扩展约定,该值必须为0
frame-opcode            = %x0 ; 表示这是一个连续帧消息
/ %x1 ; 表示文本消息
/ %x2 ; 表示二进制消息
/ %x3-7 ; 保留
/ %x8 ; 表示客户端发起的关闭
/ %x9 ; ping(用于心跳)
/ %xA ; pong(用于心跳)
/ %xB-F ; 保留
frame-masked            = %x0 ; 数据帧没有加掩码,后面没有掩码key
/ %x1 ; 数据帧加了掩码,后面有掩码key
frame-payload-length    = %x00-7D
/ %x7E frame-payload-length-16
/ %x7F frame-payload-length-63
; 表示数据帧的长度
frame-payload-length-16 = %x0000-FFFF
; 表示数据帧的长度
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 表示数据帧的长度
frame-masking-key       = 4( %0x00-FF ) ; 掩码key,只有当掩码位为1时出现
frame-payload-data      = (frame-masked-extension-data
frame-masked-application-data) 
                  ; 当掩码位为1时,这里的数据为带掩码的数据,扩展数据及应用数据都带掩码
/ (frame-unmasked-extension-data
frame-unmasked-application-data) ;
                 当掩码位为0时,这里的数据为不带掩码的数据,扩展数据及应用数据都不带掩码
frame-masked-extension-data     = *( %x00-FF ) ; 目前保留,以后定义
frame-masked-application-data   = *( %x00-FF )
frame-unmasked-extension-data   = *( %x00-FF ) ; 目前保留,以后定义
frame-unmasked-application-data = *( %x00-FF )
 
Close 处理

Close 帧的opcode是0×8. 接收到 Close 帧后,如果之前没发送过Close帧,则其必须发送Close 帧响应,但其可以延迟发送Close响应帧,例如在其发送完数据之后发送;但是,协议不保证对方在发送Close 帧后仍会处理其后续的数据。Close帧可能Client发起也可能是Server发起。
Ping-Pong 帧

接收到Ping帧后将响应Pong帧, 主要用于检测网络连接情况。
Extensions

WebSocket 支持协议扩展。 例如增加一个认证处理或者速率控制等,这通过client-server 协商完成。在WebSocket 握手处理时,通过头域Sec-WebSocket-Extensions来完成协商。 例如:
Sec-WebSocket-Extensions: mux; max-channels=4; flow-control,
deflate-stream
服务器接收一个或多个extensiions 通过再起响应的Sec-WebSocket-Extensions头域增加一个或多个extension完成。
 
说明:
 
服务器建立成功之后,如果有客户端请求连接本服务器,需要用socket_accept等方法建立一个新的socket连接,并接收客户端的请求信息,处理之后,返回响应信息,然后握手成功。 
接下来是字符串通信,客户端send过来一段字符串信息,服务器端接收到并返回给客户端这个字符串。   首先我们处理接收到的信息,根据上篇文章介绍的数据传输格式,并firefox的FIN一直为1,RSV1,2,3为0,如果是文本消息,那么 opcode为1,所以数据包的第一个数据是0x81,然后是一位mask值,firefox发来的数据是加了掩码的,所以mask值为1,后面跟7位是 数据信息长度,我们以客户端发送hi为例,那么长度就是2个字节,则第二个数据就是0x82,这里没有约定扩展数据,所以不存在扩展数据长度字节,接下来 是4个数据的掩码(因为我们这里是发送hi,2个字节的信息,小于125个字节,所以掩码是第3-第6个数据,根据数据长度的不同,掩码的位置也不同,如 果取到那7位表示的值是126,则掩码为第5-第8个数据,如果取到那7位表示的值是127,则掩码为第11-第14个数据),后面跟客户端发送的内容数 据,处理接收到的数据我们需要用取到的掩码依次轮流跟内容数据做异或(^)运算,第一个内容数据与第一个掩码异或,第二个内容数据与第二个掩码异或……第 五个内容数据与第一个掩码异或……以此类推,一直到结束,然后对内容进行编码。
 
举例:
 1 /// <summary>
 2 ///判断传入数据是否存在掩码
 3 /// 传入数据:hi
 4 /// socket接收到的二进制数据:
 5 ///      1000000110000010 1101011011101001
 6 ///      111110 111000 10111110 10000000
 7 /// 掩码异或的操作:
 8 ///             111110 111000 10111110 10000000
 9 ///   进行异或^ 111110 111001 11010110 11101001
10 ///    结果:                   1101000  1101001
11 /// 数据样例:
12 ///        [0]    129    byte
13 ///        [1]    130    byte
14 ///        [2]    214    byte
15 ///        [3]    233    byte
16 ///        [4]    62     byte
17 ///        [5]    56     byte
18 ///        [6]    190     byte
19 ///        [7]    128     byte
20 /// </summary>
21 /// <returns></returns>
22         private string UnWrap()
23         {
24             string result = string.Empty;
25
26             // 计算非空位置
27             int lastStation = GetLastZero();
28
29             // 利用掩码对org-data进行异或
30             int frame_masking_key = 1;
31             for (int i = 6; i <= lastStation; i++)
32             {
33                 frame_masking_key = i % 4;
34                 frame_masking_key = frame_masking_key == 0 ? 4 : frame_masking_key;
35                 frame_masking_key = frame_masking_key == 1 ? 5 : frame_masking_key;
36                 receivedDataBuffer[i] = Convert.ToByte(receivedDataBuffer[i] ^ receivedDataBuffer[frame_masking_key]);
37             }
38
39             System.Text.UTF8Encoding decoder = new System.Text.UTF8Encoding();
40             result = decoder.GetString(receivedDataBuffer, 6, lastStation - 6 + 1);
41
42             return result;
43
44         }
 
 
 WebSocket 协议:
 
public enum WebSocketProtocol
    {
        /*
         *
         * Request
            GET /WebIM5?uaid=200513807p8912-8de8c7e2-c963-4f67-8aca-8028797efbc1&re=0 HTTP/1.1
            Upgrade: WebSocket
            Connection: Upgrade
            Host: 10.10.150.60:5002
            Origin: https://localhost:444
            Sec-WebSocket-Key1: 3+3 1  8kgV"m 0 8  64u43
            Sec-WebSocket-Key2: 3_7891 6 4 `50 `8
         *
         * Response
            HTTP/1.1 101 WebSocket Protocol Handshake
            Upgrade: WebSocket
            Connection: Upgrade
            Sec-WebSocket-Origin: https://localhost:444
            Sec-WebSocket-Location: ws://192.168.110.....
            Sec-WebSocket-Protocol: WebIM5
         *
         *  asdfalskdfa
         * */
        draft_00 = 0,

        /*
         *
         * Request
            GET /WebIM5?uaid=200513807p8912-2e695e5b-9b46-4511-b59e-28981b4ab327&re=0 HTTP/1.1
            Upgrade: websocket
            Connection: Upgrade
            Host: 10.10.150.60:5002
            Origin: https://localhost:444
            Sec-WebSocket-Key: 1o4Jk9XPGvTX66OxmNMaww==
            Sec-WebSocket-Version: 13
         *
         * Response
            HTTP/1.1 101 Switching Protocols
            Upgrade: websocket
            Connection: Upgrade
            Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
            Sec-WebSocket-Protocol: WebIM5
         * */
        draft_17 = 17
    }

转载于:https://www.cnblogs.com/lchb/articles/3324250.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

heap python_数据结构-堆(Heap) Python实现

堆(Heap)可以看成近似完全二叉树的数组&#xff0c;树中每个节点对应数组中一个元素。除了最底层之外&#xff0c;该树是完全充满的&#xff0c;最底层是从左到右填充的。堆包括最大堆和最小堆&#xff1a;最大堆的每一个节点(除了根结点)的值不大于其父节点&#xff1b;最小堆…

多个 App 间启动

http://developer.nokia.com/: URI associations for Windows Phone http://msdn.microsoft.com/: Auto-launching apps using file and URI associations for Windows Phone 8 代码示例转载于:https://www.cnblogs.com/sirkevin/p/3325035.html

im4java 文档_im4java学习---阅读documentation文档

Utilities----im提供的一些工具类①、读取图片文件信息---Info类我们之前的做法&#xff1a;op.format("width:%w,height:%h,path:%d%f,size:%b%[EXIF:DateTimeOriginal]");IdentifyCmd identifyCmd new IdentifyCmd(useGM);使用工具类Info&#xff1a;Info imageIn…

函数体中定义的结构体和类型

源代码&#xff1a; 1 #include <stdio.h>2 struct smonth // point 13 {4 int a;5 int b;6 };7 8 int func1()9 { 10 struct smonth{ 11 int a; 12 int b; 13 }; 14 15 ty…

java listview用法_Java ListView.setMultiChoiceModeListener方法代码示例

import android.widget.ListView; //导入方法依赖的package包/类Overridepublic void onActivityCreated(Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);lAdapter new LabelAdapter(getActivity(), null, 0);setListAdapter(lAdapter);g…

预编译指令与宏定义

#if #elif [defined(), !defined()] #else #ifdef #ifndef #endif // 条件编译 /* 头文件防止多次被包含 */ #ifndef ZLIB_H #define ZLIB_H#endif /* ZLIB_H *//* 用C方式来修饰函数与变量 */ #ifdef __cplusplus extern "C" { #endif int add(int a, …

java mock server_java – 使用MockRestServiceServer模拟REST调用

我正在尝试编写一个JUnit测试用例,用于测试辅助类中的方法.该方法使用REST调用外部应用程序,这是我试图在JUnit测试中模拟的调用.辅助方法使用Spring的RestTemplate进行REST调用.在我的测试中,我创建了一个模拟REST服务器并模拟REST模板并将它们实例化为&#xff1a;Beforepubl…

BZOJ 2597 剪刀石头布(最小费用最大流)(WC2007)

Description 在一些一对一游戏的比赛&#xff08;如下棋、乒乓球和羽毛球的单打&#xff09;中&#xff0c;我们经常会遇到A胜过B&#xff0c;B胜过C而C又胜过A的有趣情况&#xff0c;不妨形象的称之为剪刀石头布情况。有的时候&#xff0c;无聊的人们会津津乐道于统计有多少这…

java swt最小化到托盘_SWT 中实现最小化到托盘图标,并只能通过托盘的弹出菜单关闭程序...

package com.unmi;import org.eclipse.swt.*;import org.eclipse.swt.events.*;import org.eclipse.swt.graphics.*;import org.eclipse.swt.widgets.*;/*** SWT 3.0 开始引入了 Tray&#xff0c;可以在系统栏放置你的程序图标了* 本程序实现的功能有四&#xff1a;* 1. 点击窗…

HDU 1476 Sudoku Killer

Sudoku Killer Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 3367 Accepted Submission(s): 1063 Problem Description自从2006年3月10日至11日的首届数独世界锦标赛以后&#xff0c;数独这项游戏越来越受到…

java .net 互通redis_C# servicestack.redis 互通 java jedis

本文是基于jedis的一致性环哈希来修改的&#xff0c;.net选的是servicestack.redis组件来修改无奈两个组件都有各自的一致性环哈希算法&#xff0c;不兼容&#xff0c;那就选一个作为标准&#xff0c;修改另一个咯。本文选择jedis的一致性环哈希作为标准&#xff0c;进而修改.n…

java 多重压缩下载_Java 多文件边压缩边下载

有时我们希望在后台实时生成文件并下载到客户端GetMapping(value "download")public void download(HttpServletResponse response) {try(OutputStream outputStream response.getOutputStream();ZipOutputStream zipOutputStream new ZipOutputStream(outputStre…

wdatepicker使用指南

wdatepicker使用指南 http://blog.csdn.net/zengxin2008/article/details/7248964#t63posted on 2013-09-22 15:45 moonfans 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/moonfans/p/3333251.html

纵表、横表互转的SQL

纵表、横表互转的SQL By&#xff1a;大志若愚 1、建表&#xff1a; 纵表结构 Table_A create table Table_A (姓名 varchar(20),课程 varchar(20),成绩 int ) insert into Table_A(姓名,课程,成绩) values(张三,语文,60) insert into Table_A(姓名,课程,成绩) values(张三,数学…

java按照商品价格排序_按照指定的类型排序

比如有三本书&#xff0c;书名和价格分别如下&#xff1a;《猎物》18.4元、《大明王朝》59.0元、《快乐密码》25.0元。前几天的一篇文章中讲到按照中文拼音进行排序&#xff0c;这里如果想按照价格排序的话怎么办&#xff1f;其实这里就牵涉到在Java中怎样对对象进行排序处理&a…

6款帮助 滚动视差jquery插件

在网页设计中&#xff0c;视差滚动&#xff08;Parallax Scrolling&#xff09;是当下流行的网页设计技术&#xff0c;通过让多层背景以不同的速度或者不同的方向移动来形成非常有趣的 3D 运动效果。下面是一些运用视差滚动效果的优秀网页案例&#xff1a; 视差&#xff08;Pa…

java i线程安全吗_Java中 i++ 是线程安全的么?为什么?

问题在 int i 0; i i; 语句中&#xff0c;i i是线程安全的么&#xff1f;如果不安全&#xff0c;请说明上面操作在JVM中的执行过程&#xff0c;为什么不安全&#xff1f;说出JDK中哪个类能达到以上的效果&#xff0c;并且是线程安全而且高效的&#xff0c;简述其原理。回答语…

EBS 多组织访问设置

如果想让你的职责下为单组织模式&#xff0c;那么你在Profile下只设置MO: Operating Unit的值&#xff0c;MO: Operating Unit提供只访问一个单独的OU。如果你想让你的职责下为多组织模式&#xff0c;你需要设置MO: Security Profile&#xff0c;MO: Security Profile提供多组织…

java第k小元素_java – 如何从对象列表中提取K“最小”元素?

我会对清单进行排序.然后,我将创建一个包含这10个最小对象的列表,并更改原始列表list1以包含其余对象.就像是&#xff1a;Collection.sort(list1);ArrayList yourSmallestElements (ArrayList)(list1.sublist(0, 9).clone());list1.removeAll(yourSmallestElements);注意&…

JQuery学习系列总结—菜单制作

1、页面中的菜单项可以通过嵌套的ul和li来表示 2、菜单最外层为ul&#xff0c;一层每个主菜单放在一个li中&#xff0c;如果有子菜单&#xff0c;在这个主菜单的li中建立新的ul&#xff0c;再依次嵌套即可构建多层的菜单 3、浏览器中ul和li元素默认情况下文字前都有圆点标识符&…