摘要
Websocket 其实是一个新协议,跟 HTTP 协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是 HTTP 协议上的一种补充。
WebSocket 是一种网络通信协议,是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。RFC6455 定义了它的通信标准。
传统的HTTP通讯中,由于http协议是一种无状态的,无连接的单向通讯协议,只有客户端才能发起请求,服务端接收到请求后作出响应。以往的网站中,如果要做即时通信聊天窗口的话,就需要通过js频繁的刷新请求服务器获取最新的信息达到效果,轮询需要不停的建立和断开http连接,或者长期保持http连接。这样效率非常低,且浪费资源。故而需要像websocket这样一种双向通行的协议出现。websocket跟socket原理差不多,只需要建立一次连接,就可以一直保持连接状态,而且可以双向通讯。
WebSocket的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话
1、客户端,使用js,以下语法可以创建一个WebSocket对象
var Socket = new WebSocket(url, [protocol] );
2、服务端支持Node.js、Java、C++、Python 等多种语言,每个语言都有自己的解决方案。本文主要介绍springboot中配置WebSocket,即基于Java语言的
上干货,上干货,上干货,上干货:
1、客户端(html页面)
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket test</title>
<meta charset="UTF-8"/>
</head>
<body>
Welcome<br/>
当前连接状态:
<span id="showFirst">
关闭
</span>
<br/>
<input id="text" type="text"/>
<button id="sendClick" onclick="send()">发送</button>
<button id="switchClick" onclick="closeWebSocket()">关闭连接</button>
<div id="message">
打印信息:
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
//注意使用的是 ws://,而不是 http://
websocket = new WebSocket("ws://127.0.0.1:8085/websocket/manager");
}
else {
alert('浏览器不支持 websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setStatusHTML("连接错误");
};
//连接成功建立的回调方法
websocket.onopen = function (event) {
//设置按钮显示为 关闭
setStatusHTML("打开");
}
//连接关闭的回调方法
websocket.onclose = function () {
setStatusHTML("关闭");
}
/**
* 接收到服务端发过来的消息,
* 回调方法
* @param event
*/
websocket.onmessage = function (event) {
//将服务端的消息打印到屏幕
setMessageInnerHTML(event.data);
}
/**
* 给服务端发送消息
*
*/
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
/**
* 监听窗口的关闭事件,当窗口关闭时,主动去关闭websocket连接,
* 防止连接还没断开就关闭窗口,server端会抛异常。
*/
window.onbeforeunload = function () {
websocket.close();
}
//修改开关按钮的显示
function setStatusHTML(text) {
document.getElementById('showFirst').innerHTML = text;
}
//将消息显示在网页上 追加显示
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket() {
websocket.close();
}
</script>
</html>
2、服务端(springboot)
1)pom.xml依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2)application.yml的配置(正常配置就行)
server:
port: 8085
spring:
profiles:
active: dev #开发环境
application:
name: service-feign-push
3)新建一个websocket配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author zhaoxi
* @date 2018/12/4 14:49
* TODO:websocket配置类
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4)建一个服务端处理WebSocket的类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author zhaoxi
* @date 2018/12/4 16:28
* TODO:后台管理系统的推送
*/
@ServerEndpoint(value = "/websocket/manager")
@Component
public class ManagerWebSocket {
/**
* log4j日志
*/
private static final Logger log = LoggerFactory.getLogger(ManagerWebSocket.class);
/**
* 静态容器,记录每个客户端连接时,产生对应的ManagerWebSocket对象。
* concurrent包的线程安全Set。
*/
private static CopyOnWriteArraySet<ManagerWebSocket> webSocketSet = new CopyOnWriteArraySet<>();
/**
* 静态变量,记录当前在线连接数。
* 如果需要精确的统计,需要把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* 成员变量;每个客户端连接时,产生连接会话。
* 需要通过它来给对应的客户端发送数据
* session作为ManagerWebSocket对象的一个成员属性存储起来
*/
private Session session;
/**
* TODO: 连接建立成功调用的方法
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:32
* @params
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* TODO: 连接关闭调用的方法
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:36
* @params
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* TODO: 收到客户端消息后调用的方法
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:36
* @params
*/
@OnMessage
public void onMessage(Session session, String message) {
log.info("来自客户端的消息:" + message);
/**
* 回复此客户端
*/
try {
this.sendMessage(session, message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* TODO: 发生错误时调用
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:40
* @params
*/
@OnError
public void onError(Session session, Throwable error) {
log.info("发生错误");
error.printStackTrace();
}
/**
* TODO: 给客户端发消息
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:39
* @params
*/
public void sendMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(message);
}
public void sendMessage(String message) throws IOException {
session.getBasicRemote().sendText(message);
}
/**
* TODO: 群发自定义消息
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:44
* @params
*/
public static void sendInfo(String message) throws IOException {
for (ManagerWebSocket item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
ManagerWebSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
ManagerWebSocket.onlineCount--;
}
到此为止,我们的WebSocket客户端,服务端已经配置完毕。我们可以通过
/**
* TODO: 给客户端发消息
*
* @return
* @throws
* @author zhaoxi
* @time 2018/12/4 16:39
* @params
*/
public void sendMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(message);
}
这个方法给客户端发消息了。
3、Nginx反向代理WebSocket
发布到服务器时候,我们一般不开放8085端口,这时候直接域名转发,然后用域名访问一般是访问不到。而且还有跨域问题。
# push 接口 服务器代理
upstream server_push{
server 127.0.0.1:8085;
}
# push 推送接口
server {
listen 80;
listen 443;
server_name push.***.com;
#ssl证书(没有的话可以不配置)
ssl on;
ssl_certificate cert/21456751*****433.pem;
ssl_certificate_key cert/2145*****6480433.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# 允许跨域
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
location / {
#添加websocket代理
proxy_pass http://server_push;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}