# WebSocket协议
WebSocket协议是一种网络通信协议,是HTML5之后提出的一种在单个TCP上进行`全双工`通讯的协议。可以使服务端主动的向客户端发送消息,常用于`推送服务。`其特点如下:
* HTTP协议(1.0)是单向的没有连接状态的协议,客户端浏览器为了获取服务器的动态变化信息,需要配合JavaScript和AJAX进行不断的异步`轮询请求`,需要不断的建立连接,非常耗费资源。
* WebSocket能够使任一方主动的建立起连接,并且连接只要建立一次就`一直保持连接`状态。
WebSocket并不是全新的协议,而是利用HTTP协议来建立连接的,其创建过程如下:
1. 浏览器发送HTTP请求
~~~
GET ws://localhost:3000/hello HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
~~~
说明:
* GET请求的地址不是类似`/path/`,而是以`ws://`开头的地址;
* 请求头`Upgrade: websocket`和`Connection: Upgrade`表示这个连接将要被转换为WebSocket连接;
* `Sec-WebSocket-Key`是用于标识这个连接,并非用于加密数据;
* `Sec-WebSocket-Version`指定了WebSocket的协议版本。
2. 服务器接受该请求,就会返回下面的响应:
~~~
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
~~~
该响应代码`101`表示本次连接的HTTP协议即将被更改,更改后的协议就是`Upgrade: websocket`指定的WebSocket协议。
3. WebSocket连接建立,服务器端可以主动的发送消息给浏览器,可以是二进制数据和文本数据。
下面是如何在前端分离的项目中使用WebSocket协议,前端Vue,后端Spring Boot。
## WebSocket客户端
适用于支持html5的浏览器,其开放的API(不需要引入)为:
1. 创建Web Socket连接
~~~
var Socket = new WebSocket(url, [protocol] );
~~~
url表示指定连接的URL,protocol是子协议,可选。
对象属性:
~~~
Socket.readyState: 连接状态,0表示连接未建立;1表示连接建立,可以进行通信;2表示连接正在关闭;3表示连接已经关闭,并且不能打开。
Socket.bufferAmount:等待传输的队列。
~~~
对象事件:
| 事件 | 事件处理程序 | 描述 |
| --- | --- | --- |
| open | Socket.onopen | 连接建立时触发 |
| message | Socket.onmessage | 浏览器客户端接收服务器的信息时触发 |
| error | Socket.onerror | 通信发生错误时触发 |
| close | Socket.onclose | 连接关闭时触发 |
对象方法:
* Socket.send():使用连接发送数据。
* Socket.close():关闭连接。
~~~
// 初始化一个 WebSocket 对象
var socket = new WebSocket('ws://localhost:9998/echo');
// 建立 web socket 连接成功触发事件
socket.onopen = function() {
// 使用 send() 方法发送数据
socket.send('发送数据');
alert('数据发送中...');
};
// 接收服务端数据时触发事件
socket.onmessage = function(evt) {
var received_msg = evt.data;
alert('数据已接收...');
};
// 断开 web socket 连接成功触发事件
socket.onclose = function() {
alert('连接已关闭...');
};
~~~
### Vue使用web socket
~~~
// 建立web socket连接
webSocket() {
if ('WebSocket' in window) {
this.websocket = new WebSocket("ws://localhost:8081/websocket/hello");
} else {
console.log("你的浏览器还不支持web socket");
}
console.log(this.websocket);
this.websocket.onopen = this.webSocketOnOpen;
this.websocket.onclose = this.webSocketOnClose;
this.websocket.onmessage = this.webSocketOnMessage;
this.websocket.onerror = this.webSocketOnError;
},
// 连接建立之后
webSocketOnOpen() {
console.log("与后端建立连接");
var data = "这里是端口8001建立连接";
this.websocket.send(data);
},
// 连接关闭之后
webSocketOnClose() {
console.log("与后端关闭连接");
},
// 发送消息
webSocketOnMessage(res) {
console.log("接收到后端发送回来的消息");
console.log(res.data)
},
// 发生错误
webSocketOnError() {
console.log("发生错误~");
},
~~~
使用原生的web socket就行了,也有封装了web socket的工具可以使用。
## WebSocket服务端
如果是使用Tomcat服务器的话,要版本7以上才支持,使用Spring Boot整合WebSocket的过程如下:
1. 引入依赖
~~~
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
~~~
2. 创建核心配置类
~~~
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* spring boot web socket 核心配置类
*/
@Configuration
public class WebSocketConfig {
/**
* 配置服务端点导出器
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
~~~
3. 创建服务端点
~~~
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 后端实现web socket
*/
@Component
@ServerEndpoint("/hello")
@Slf4j
public class WebSocketServer {
/**
* 静态变量,用来记录当前在线连接数
* 注意线程安全问题
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全集合,存放每个客户端对应的web socket对象
*/
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
/**
* 与某个客户端的连接会话,需要听过它来给客户端发送数据
* 每个前端的WebSocket对象建立起来的有一个session对象
*/
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
System.out.println("session = " + session);
// 添加当前web socket对象
webSocketSet.add(this);
// 累加在线人数
addOnlineCount();
log.info("有新连接加入!当前在线人数为" + getOnlineCount());
try {
sendMessage("有新连接加入!当前在线人数为" + getOnlineCount());
} catch (IOException e) {
log.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
// 将连接的web socket对象从set中移除
webSocketSet.remove(this);
// 在线人数-1
subOnlineCount();
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("来自客户端的消息:" + message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 给客户端浏览器发送消息
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendInfo(String message) throws IOException {
log.info(message);
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
~~~
如果要获取前端发送的消息可用:
~~~
@Component
@ServerEndPoint("/hello/{name}/{age}")
public class WebSocketServer {
@OnOpen
public void onOpen(@PathParam("name")String name, @PathParam("age")int age) {
}
}
~~~
【参考】廖雪峰的官方网站[https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096](https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096)
- 第一章 Java基础
- ThreadLocal
- Java异常体系
- Java集合框架
- List接口及其实现类
- Queue接口及其实现类
- Set接口及其实现类
- Map接口及其实现类
- JDK1.8新特性
- Lambda表达式
- 常用函数式接口
- stream流
- 面试
- 第二章 Java虚拟机
- 第一节、运行时数据区
- 第二节、垃圾回收
- 第三节、类加载机制
- 第四节、类文件与字节码指令
- 第五节、语法糖
- 第六节、运行期优化
- 面试常见问题
- 第三章 并发编程
- 第一节、Java中的线程
- 第二节、Java中的锁
- 第三节、线程池
- 第四节、并发工具类
- AQS
- 第四章 网络编程
- WebSocket协议
- Netty
- Netty入门
- Netty-自定义协议
- 面试题
- IO
- 网络IO模型
- 第五章 操作系统
- IO
- 文件系统的相关概念
- Java几种文件读写方式性能对比
- Socket
- 内存管理
- 进程、线程、协程
- IO模型的演化过程
- 第六章 计算机网络
- 第七章 消息队列
- RabbitMQ
- 第八章 开发框架
- Spring
- Spring事务
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 数据库
- Mysql
- Mysql中的索引
- Mysql中的锁
- 面试常见问题
- Mysql中的日志
- InnoDB存储引擎
- 事务
- Redis
- redis的数据类型
- redis数据结构
- Redis主从复制
- 哨兵模式
- 面试题
- Spring Boot整合Lettuce+Redisson实现布隆过滤器
- 集群
- Redis网络IO模型
- 第十章 设计模式
- 设计模式-七大原则
- 设计模式-单例模式
- 设计模式-备忘录模式
- 设计模式-原型模式
- 设计模式-责任链模式
- 设计模式-过滤模式
- 设计模式-观察者模式
- 设计模式-工厂方法模式
- 设计模式-抽象工厂模式
- 设计模式-代理模式
- 第十一章 后端开发常用工具、库
- Docker
- Docker安装Mysql
- 第十二章 中间件
- ZooKeeper