完成内容:
知识点:
1.网络编程_TCP编程
### 编写客户端
1.创建Socket对象,指明服务端的ip以及端口号
2.调用socket中的getOutputStream,往服务端发送请求
3.调用socket中的getInputStream,读取服务端响应回来的数据
4.关流
public class Client {
public static void main(String[] args)throws Exception {
//1.创建Socket对象,指明服务端的ip以及端口号
Socket socket = new Socket("127.0.0.1", 6666);
//2.调用socket中的getOutputStream,往服务端发送请求
OutputStream os = socket.getOutputStream();
os.write("我想下载一个小电影".getBytes());
//3.调用socket中的getInputStream,读取服务端响应回来的数据
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
//4.关流
is.close();
os.close();
socket.close();
}
}
===================================================================================
### 编写服务端
1.创建ServerSocket对象,设置端口号
2.调用ServerSocket中的accept方法,等待客户端连接,返回Socket对象
3.调用socket中的getInputStream,用于读取客户端发送过来的数据
4.调用socket中的getOutputStream,用于给客户端响应数据
5.关闭资源
public class Server {
public static void main(String[] args)throws Exception {
//1.创建ServerSocket对象,设置端口号
ServerSocket ss = new ServerSocket(6666);
//2.调用ServerSocket中的accept方法,等待客户端连接,返回Socket对象
Socket socket = ss.accept();
//3.调用socket中的getInputStream,用于读取客户端发送过来的数据
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
//4.调用socket中的getOutputStream,用于给客户端响应数据
OutputStream os = socket.getOutputStream();
os.write("给你一个小电影".getBytes());
//5.关闭资源
os.close();
is.close();
socket.close();
ss.close();
}
}
知识点 |
核心内容 |
关键操作步骤 |
易混淆点 |
TCP交互过程 |
客户端主动连接服务端的五步流程 |
1. 客户端发送请求; 2. 服务端接收请求; 3. 服务端分析请求; 4. 服务端响应结果; 5. 客户端接收响应 |
连接方向:必须客户端主动连接服务端 |
Socket对象 |
客户端使用Socket类; 服务端使用ServerSocket类 |
- 客户端创建需指定IP和端口; - 服务端只需设置端口 |
流对象获取:必须通过socket.getOutputStream()而非直接new FileOutputStream |
客户端编程 |
四步实现流程 |
1. 创建Socket对象; 2. 获取输出流发送请求; 3. 获取输入流读取响应; 4. 关闭资源 |
执行顺序:必须先启动服务端再启动客户端 |
服务端编程 |
五步实现流程 |
1. 创建ServerSocket; 2. accept()等待连接; 3. 获取输入流读取请求; 4. 获取输出流发送响应; 5. 关闭资源 |
accept()方法:返回的是连接客户端的Socket对象 |
流操作对比 |
网络流 vs 本地文件流 |
- 网络流:socket.getInputStream(); - 本地流:new FileInputStream() |
常见错误:混淆两种流的获取方式 |
三次握手 |
TCP连接建立机制 |
必须服务端先运行,客户端才能成功连接 |
错误现象:直接运行客户端会报"拒绝连接"错误 |
2.网络编程_文件上传
### 文件上传客户端以及服务端实现
public class Client {
public static void main(String[] args)throws Exception {
//1.创建Socket对象
Socket socket = new Socket("127.0.0.1", 6666);
//2.创建FileInputStream,用于读取本地上的图片
FileInputStream fis = new FileInputStream("E:\\Idea\\io\\24.jpg");
//3.调用getOutputStream,用于将读取过来的图片写给服务端
OutputStream os = socket.getOutputStream();
//4.边读边写
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
//给服务端写一个结束标记
socket.shutdownOutput();
System.out.println("======以下代码是读取响应的结果======");
//5.调用getInputStream,读取响应结果
InputStream is = socket.getInputStream();
byte[] bytes1 = new byte[1024];
int len1 = is.read(bytes1);
System.out.println(new String(bytes1,0,len1));
//6.关流
is.close();
os.close();
fis.close();
socket.close();
}
}
public class Server {
public static void main(String[] args)throws Exception {
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(6666);
//2.调用accept方法等待客户端的连接
Socket socket = ss.accept();
//3.调用socket中的getInputStream,读取客户端发送过来的图片
InputStream is = socket.getInputStream();
/*
UUID调用randomUUID(),再调用toString,将其转成String
*/
String s = UUID.randomUUID().toString();
String name = s + System.currentTimeMillis();
//4.创建FileOutputStream,将读取过来的图片写到硬盘上
FileOutputStream fos = new FileOutputStream("E:\\Idea\\io\\upload\\"+name+".jpg");
//5.边读边写
byte[] bytes = new byte[1024];
int len;
while((len = is.read(bytes))!=-1){
fos.write(bytes,0,len);
}
System.out.println("======以下代码是给客户端的响应结果======");
//6.调用socket中的getOutputStream,给客户端响应结果
OutputStream os = socket.getOutputStream();
os.write("上传成功".getBytes());
//7.关流
os.close();
fos.close();
is.close();
socket.close();
ss.close();
}
}
public class Demo01UUID {
public static void main(String[] args) {
String string = UUID.randomUUID().toString();//生成一个十六进制的随机数
System.out.println("string = " + string);
}
}
======================================================================================
### 文件上传服务端实现(多线程)
public class ServerThread {
public static void main(String[] args) throws Exception {
//1.创建ServerSocket对象
ServerSocket ss = new ServerSocket(6666);
while (true) {
//2.调用accept方法等待客户端的连接
Socket socket = ss.accept();
new Thread(new Runnable() {
@Override
public void run() {
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
//3.调用socket中的getInputStream,读取客户端发送过来的图片
is = socket.getInputStream();
/*
UUID调用randomUUID(),再调用toString,将其转成String
*/
String s = UUID.randomUUID().toString();
String name = s + System.currentTimeMillis();
//4.创建FileOutputStream,将读取过来的图片写到硬盘上
fos = new FileOutputStream("E:\\Idea\\io\\upload\\" + name + ".jpg");
//5.边读边写
byte[] bytes = new byte[1024];
int len;
while ((len = is.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
System.out.println("======以下代码是给客户端的响应结果======");
//6.调用socket中的getOutputStream,给客户端响应结果
os = socket.getOutputStream();
os.write("上传成功".getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
//7.关流
CloseUtils.closeQ(socket,fos,is,os);
}
}
}).start();
}
}
}
public class CloseUtils {
private CloseUtils(){
}
public static void closeQ(Socket socket, FileOutputStream fos, InputStream is, OutputStream os){
if (os!=null){
try {
os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos!= null){
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (is!=null){
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (socket!=null){
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
知识点 |
核心内容 |
重点 |
TCP文件上传流程 |
客户端通过FileInputStream读取本地文件,经Socket传输至服务端,服务端通过FileOutputStream写入目标位置 |
结束标记未显式传递导致阻塞(需shutdownOutput解决) |
多线程服务端改造 |
使用Thread为每个客户端连接创建独立线程,处理文件上传任务 |
资源关闭需线程安全(工具类封装closeQuietly) |
UUID文件名防覆盖 |
通过UUID.randomUUID()生成唯一文件名,避免服务端存储冲突 |
拼接时间戳进一步降低重复概率 |
IO流选择原则 |
文件操作用FileInputStream/FileOutputStream,网络传输用Socket流 |
普通流与Socket流混用场景 |
阻塞问题分析 |
客户端未发送结束标记时,服务端read()会持续阻塞 |
需显式调用socket.shutdownOutput() |
3.正则表达式_介绍
## 1.正则表达式的概念及演示
1.概述:正则表达式是一个具有特殊规则的字符串
2.作用:校验
比如:校验手机号,身份证号,密码,用户名,邮箱等
3.String中有一个校验正则的方法:
boolean matches(String regex) 校验字符串是否符合指定的regex的规则
4.比如:校验QQ号(不能以0开头,必须都是数字,必须是5-15位的)
public class Demo01Regex {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String data = scanner.next();
//boolean result01 = method01(data);
//System.out.println("result01 = " + result01);
boolean result02 = method02(data);
System.out.println("result02 = " + result02);
}
private static boolean method02(String data) {
boolean result = data.matches("[1-9][0-9]{4,14}");
return result;
}
private static boolean method01(String data) {
//不能是0开头的
if (data.startsWith("0")) {
return false;
}
//必须都是数字
char[] chars = data.toCharArray();
for (char aChar : chars) {
if (aChar < '0' || aChar > '9') {
return false;
}
}
//必须是5-15位
if (data.length()<5 || data.length()>15){
return false;
}
return true;
}
}
知识点 |
核心内容 |
重点 |
正则表达式概念 |
特殊规则的字符串,用于校验特定格式的数据 |
区分普通字符串与正则表达式的本质差异 |
正则表达式作用 |
数据校验(手机号/身份证/密码/邮箱等格式验证) |
理解正则表达式在表单验证中的实际应用场景 |
String.matches()方法 |
通过字符串.matches(正则表达式)进行格式校验 |
方法返回值是布尔类型,注意与Pattern类的区别 |
QQ号校验案例 |
非零开头(^[1-9]) + 纯数字([0-9]{4,14}) + 5-15位长度 |
边界值验证:首位数字限制与长度范围的组合判断 |
传统校验方式对比 |
通过charAt()、length()等基础方法逐条验证 |
代码复杂度:需要编写多重if-else嵌套结构 |
正则表达式优势 |
单行代码完成复杂校验(如[1-9][0-9]{4,14}) |
元字符含义理解([]范围限定符、{}次数限定符等) |
4.正则表达式_基本使用
## 2.正则表达式-字符类
java.util.regex.Pattern:正则表达式的编译表示形式。
正则表达式-字符类:[]表示一个区间,范围可以自己定义
语法示例:
1. [abc]:代表a或者b,或者c字符中的一个。
2. [^abc]:代表除a,b,c以外的任何字符。
3. [a-z]:代表a-z的所有小写字符中的一个。
4. [A-Z]:代表A-Z的所有大写字符中的一个。
5. [0-9]:代表0-9之间的某一个数字字符。
6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符
//字符类
private static void method01() {
//1.验证字符串是否以h开头,d结尾,中间是aeiou的某一个字符
boolean result01 = "had".matches("[h][aeiou][d]");
System.out.println("result01 = " + result01);
//2.验证字符串是否以h开头,d结尾,中间不是aeiou的某个字符
boolean result02 = "hyd".matches("[h][^aeiou][d]");
System.out.println("result02 = " + result02);
//3.验证字符串是否是开头a-z的任意一个小写字母,后面跟ad
boolean result03 = "had".matches("[a-z][a][d]");
System.out.println("result03 = " + result03);
}
=======================================================================================
## 3.正则表达式-逻辑运算符
正则表达式-逻辑运算符
语法示例:
1. &&:并且
2. | :或者
/*
逻辑运算符
*/
private static void method02() {
//1.要求字符串是小写字母并且字符不能以[aeiou]开头,后面跟ad
boolean result01 = "yad".matches("[[a-z]&&[^aeiou]][a][d]");
System.out.println("result01 = " + result01);
//2.要求字符是aeiou的某一个字符开头,后面跟ad
boolean result02 = "had".matches("[a|e|i|o|u][a][d]");
System.out.println("result02 = " + result02);
}
=======================================================================================
## 4.正则表达式-预定义字符
正则表达式-预定义字符
语法示例:
1. "." : 匹配任何字符。(重点) 不能加[]
2. "\\d":任何数字[0-9]的简写;(重点)
3. "\\D":任何非数字[^0-9]的简写;
4. "\\s": 空白字符:[ \t\n\x0B\f\r] 的简写
5. "\\S": 非空白字符:[^\s] 的简写
6. "\\w":单词字符:[a-zA-Z_0-9]的简写(重点)
7. "\\W":非单词字符:[^\w]
//预定义字符
private static void method03() {
//1.验证字符串是否是三位数字
//boolean result01 = "111".matches("[0-9][0-9][0-9]");
boolean result01 = "111".matches("\\d\\d\\d");
System.out.println("result01 = " + result01);
//2.验证手机号: 1开头 第二位3 5 8 剩下的都是0-9的数字
boolean result02 = "13838381438".matches("[1][358]\\d\\d\\d\\d\\d\\d\\d\\d\\d");
System.out.println("result02 = " + result02);
//3.验证字符串是否以h开头,d结尾,中间是任意一个字符
boolean result03 = "had".matches("[h].[d]");
System.out.println("result03 = " + result03);
}
=======================================================================================
## 5. 正则表达式-数量词
正则表达式-数量词
语法示例:x代表字符
1. X? : x出现的数量为 0次或1次
2. X* : x出现的数量为 0次到多次 任意次
3. X+ : x出现的数量为 1次或多次 X>=1次
4. X{n} : x出现的数量为 恰好n次 X=n次
5. X{n,} : x出现的数量为 至少n次 X>=n次 x{3,}
6. X{n,m}: x出现的数量为 n到m次(n和m都是包含的) n=<X<=m
//数量词
private static void method04() {
//1.验证字符串是否是三位数字
boolean result01 = "111".matches("\\d{3}");
System.out.println("result01 = " + result01);
//2.验证手机号: 1开头 第二位3 5 8 剩下的都是0-9的数字
boolean result02 = "13838381438".matches("[1][358]\\d{9}");
System.out.println("result02 = " + result02);
//3.验证qq号: 不能是0开头,都是数字,长度为5-15
boolean result03 = "111111".matches("[1-9][0-9]{4,14}");
System.out.println("result03 = " + result03);
}
=======================================================================================
## 6.正则表达式-分组括号( )
正则表达式-分组括号( ) (abc)
//分组括号
private static void method05() {
//校验abc可以出现任意次
boolean result = "abcabc".matches("(abc)*");
System.out.println("result = " + result);
}
=======================================================================================
## 7.String类中和正则表达式相关的方法
String类中和正则表达式相关的方法
boolean matches(String regex) 判断字符串是否匹配给定的正则表达式。
String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符
private static void method06() {
//String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
String s1 = "abc hahah hehe hdhshsh";
String[] arr1 = s1.split(" +");
System.out.println(Arrays.toString(arr1));
//String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符
String s2 = s1.replaceAll(" +", "z");
System.out.println("s2 = " + s2);
}
知识点 |
核心内容 |
重点 |
正则表达式基础 |
字符类使用(中括号表示范围,尖角号取反) |
中括号与圆括号的区别; 取反符号的两种含义(异或/取反) |
预定义字符 |
点号(任意字符)、\d(数字)、\s(空白符)等 |
split方法处理点号需转义; 大小写字母的不同含义(如\d与\D) |
数量词 |
?(0或1次)、*(任意次)、+(1次以上)、{n,m}(范围次数) |
{n,}表示至少n次; 手机号验证的9位数字处理 |
分组匹配 |
圆括号创建捕获组,要求元素必须连续出现 |
分组与字符类的本质区别; ABC*与(ABC)*的不同含义 |
字符串方法 |
matches()/split()/replaceAll()的正则支持 |
replaceAll处理连续空格; split多空格切割需用" +" |
实用技巧 |
在线正则生成工具使用(regulex等) |
现成正则表达式复用; 不同语言的正则语法差异 |
5.设计模式_模版方法&单例模式
# 第三章.设计模式
设计模式(Design pattern),是一套被反复使用、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性,稳定性。
1995 年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式。<大话设计模式>
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。-->创建对象
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。-->对功能进行增强
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
## 1.模版方法设计模式
模板方法(Template Method)模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。明确了一部分功能,而另一部分功能不明确。需要延伸到子类中实现
饭店中吃饭: 点菜,吃菜和买单三个步骤。点菜和买单基本上一致的,但是吃菜不同,吃法也不同。明确了一部分功能,而另一部分功能不明确。
public abstract class Hotel {
public void eat(){
System.out.println("点菜");
eatCai();
System.out.println("买单");
}
public abstract void eatCai();
}
public class QuanJuDe extends Hotel{
@Override
public void eatCai() {
System.out.println("薄饼");
System.out.println("放鸭肉");
System.out.println("酱");
System.out.println("葱丝");
System.out.println("黄瓜丝");
System.out.println("卷着吃");
}
}
public class ZhangLiang extends Hotel{
@Override
public void eatCai() {
System.out.println("调麻酱");
System.out.println("放辣椒油");
System.out.println("倒到大碗中吃");
}
}
public class Test01 {
public static void main(String[] args) {
QuanJuDe quanJuDe = new QuanJuDe();
quanJuDe.eat();
System.out.println("================");
ZhangLiang zhangLiang = new ZhangLiang();
zhangLiang.eat();
}
}
========================================================================================
## 2.单例模式
1.目的:单(一个) 例(实例,对象)
让一个类只产生一个对象,供外界使用
2.分类:
a.饿汉式:我好饥渴呀,好饥饿呀,迫不及待要这个对象,所以和对象就需要赶紧new出来
b.懒汉式:我好懒呀,不着急要对象,想啥时候使用,你啥时候new给我
### 2.1.饿汉式:
```properties
饿汉式:我好饥渴呀,好饥饿呀,迫不及待要这个对象,所以和对象就需要赶紧new出来
```
public class Singleton {
/*
防止外界随意使用构造方法new对象,我们需要将构造私有化
*/
private Singleton(){
}
/*
为了赶紧new对象,我们new对象的时候变成静态的,让其随着类的加载而加载
为了不让外界随便使用类名调用此静态对象,我们将其变成private
*/
private static Singleton singleton = new Singleton();
/*
为了将内部new出来的对象给外界
我们可以定义 一个方法,将内部的对象返回给外界
*/
public static Singleton getSingleton(){
return singleton;
}
}
public class Test01 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Singleton singleton = Singleton.getSingleton();
System.out.println(singleton);
}
}
}
====================================================================================
### 2.2.懒汉式:
```properties
懒汉式:我好懒呀,不着急要对象,想啥时候使用,你啥时候new给我
```
public class Singleton1 {
/*
防止外界随意使用构造方法new对象,我们需要将构造私有化
*/
private Singleton1() {
}
/*
懒汉式,不着急new对象
*/
private static Singleton1 singleton1 = null;
/*
为了将内部new出来的对给外界
定义一个方法,将内部new出来的对返回
*/
public static Singleton1 getSingleton1() {
//如果singleton1不是null就没必要抢锁了,直接返回,是null再抢锁
if (singleton1==null){
synchronized (Singleton1.class){
if (singleton1 == null) {
singleton1 = new Singleton1();
}
}
}
return singleton1;
}
}
知识点 |
核心内容 |
重点 |
设计模式概述 |
23种设计模式的分类与目的,强调代码可重用性、可靠性和稳定性 |
设计模式三大分类(创建型/结构型/行为型) |
模板方法模式 |
通过固定框架(如饭店三步流程)实现部分确定方法+子类实现抽象方法 |
抽象方法延伸实现 vs 普通继承 |
单例模式目的 |
确保类只产生一个对象供全局使用 |
私有构造方法+静态实例的核心实现原理 |
饿汉式实现 |
类加载时立即创建实例(private static final) |
线程安全性天然保证 |
懒汉式实现 |
延迟加载+双重检查锁定(if null→synchronized→if null) |
线程安全陷阱与解决方案 |
模式对比维度 |
初始化时机(饿汉立即/懒汉延迟) | 线程安全(饿汉天然/懒汉需同步) | 性能开销 |
面试高频手撕代码点 |
6.Lombok的使用
# Lombok
1.作用:简化javabean开发
2.使用:
a.下插件 -> 如果是idea2022不用下载了,自带
b.导lombok的jar包
c.修改设置
## 1.lombok介绍
Lombok通过增加一些“处理程序”,可以让javabean变得简洁、快速。
Lombok能以注解形式来简化java代码,提高开发效率。开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护。
Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。
======================================================================================
## 2.lombok常用注解
### @Getter和@Setter
- 作用:生成成员变量的get和set方法。
- 写在成员变量上,指对当前成员变量有效。
- 写在类上,对所有成员变量有效。
- 注意:静态成员变量无效。
### @ToString
- 作用:生成toString()方法。
- 注解只能写在类上。
### @NoArgsConstructor和@AllArgsConstructor
- @NoArgsConstructor:无参数构造方法。
- @AllArgsConstructor:满参数构造方法。
- 注解只能写在类上。
### @EqualsAndHashCode
- 作用:生成hashCode()和equals()方法。
- 注解只能写在类上。
### @Data
- 作用:生成get/set,toString,hashCode,equals,无参构造方法
- 注解只能写在类上。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
}
public class Test01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("涛哥");
person.setAge(10);
System.out.println(person.getName()+"..."+person.getAge());
System.out.println("================");
Person p1 = new Person("张三", 28);
System.out.println(p1.getName()+"..."+p1.getAge());
}
}
知识点 |
核心内容 |
重点 |
Lombok工具介绍 |
简化JavaBean开发的注解工具,自动生成构造器/getter/setter等方法 |
区分手动编码与Lombok自动生成的区别 |
Lombok安装配置 |
IDEA 2022+版本内置插件,旧版本需通过Plugins市场安装小辣椒图标插件 |
版本兼容性问题(2022前后版本差异) |
基础注解功能 |
@Getter/@Setter自动生成方法; @Data包含toString/equals/hashCode/无参构造 |
@Data不包含有参构造,需配合@AllArgsConstructor使用 |
环境配置要点 |
1. 启用Annotation Processors; 2. 开启Build project automatically |
注解失效的常见配置错误排查 |
实际应用案例 |
java; @Data; public class Person{; private String name;; private int age;; } |
编译后字节码验证getter/setter的存在性 |
对比优势分析 |
传统开发需手动编写20+行代码 vs Lombok1行注解解决 |
属性修改时的维护成本对比 |
高级注解组合 |
@NoArgsConstructor+@AllArgsConstructor实现全构造覆盖 |
有参构造导致无参构造消失的解决方案 |
7.WebSocket
WebSocket 原理概述
WebSocket 协议的核心在于建立持久连接的能力。当客户端发起请求并与服务器握手成功后,双方之间的通道保持打开状态,从而允许任意一方随时发送消息给对方。这种机制特别适合于需要频繁更新的应用程序,比如在线聊天室、实时协作工具以及金融市场的行情推送服务等场景。
以下是 WebSocket 握手的过程描述:
初始阶段: 客户端通过标准的 HTTP/HTTPS URL 向目标地址发出升级请求 (Upgrade Request),其中包含了特定头字段
Sec-WebSocket-Key
和其他必要的参数。验证过程: 如果服务器支持该协议,则会返回带有相应确认信息的状态码 101 Switching Protocols 及其对应的头部定义 (
Sec-WebSocket-Accept
) 来完成协商流程并切换至真正的 WebSockets 模式下运作。
实现示例
一个基本的例子来展示如何利用 Node.js 构建一个简易版的服务端逻辑:
const http = require('http');
const { Server } = require('ws');
// 创建HTTP服务器实例
let server = http.createServer((req,res)=>{
res.writeHead(200);
res.end("Hello World");
});
server.listen(8989);
// 初始化WS对象并将之绑定到已有的HTTP服务上运行
var wss=new Server({server});
wss.on('connection',(socket)=>{
console.log("New client connected!");
// 当收到新消息的时候触发此事件处理函数
socket.on('message',function incoming(message){
console.log(`Received:${message}`);
// 将接收到的内容再发回去作为回应动作之一
socket.send(`Echo back: ${message}`);
});
// 断开链接后的回调操作
socket.on('close',()=>{
console.log("Client disconnected.");
});
});
关键注意事项
为了保障系统的稳定性和安全性,在开发过程中还需要考虑以下几个方面的问题:
- 数据加密:由于普通的 WS 链接容易受到中间人攻击的影响,因此建议尽可能采用 WSS 加密形式进行保护;
- 超时重连策略设计合理化方案以防止单次失败造成整个应用程序崩溃的情况发生;
- 输入校验过滤非法字符防止 XSS 注入风险等问题出现等等。