WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。
目前主流的浏览器都已经支持了该新协议,具体版本就需要自己去查。
在工作中需要做一个实时的监控告警系统,因为web前端需要实时拉取数据,我就选择了webscoket这项技术,用的也比较简单。由于在该项目中前端需要的数据量比较大,因此没有用webscoket来推送真实数据,而是当后端(Java)数据有更新时推送一条消息给前端,前端再进行拉取。废话不多说,直接上代码。
一、后端代码。后端用的SpringBoot框架,在pom.xml中引入了spring-boot-starter-websocket依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.python.modules.synchronize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import cn.xunlei.cbase.xlalarm.calculate.CalculateService;
@ServerEndpoint(value = "/pushMsg")
@Component
public class WebSocketPushMsg {
private static Logger log = LoggerFactory.getLogger(CalculateService.class);
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineCount = new AtomicInteger(0);
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<WebSocketPushMsg> webSocketSet = new CopyOnWriteArraySet<WebSocketPushMsg>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新连接加入!当前在线客户端数为:{}",getOnlineCount());
// try {
// sendMessage("欢迎!!!");
// } catch (IOException e) {
// e.printStackTrace();
// log.error("WebSocket连接异常!");
// }
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有连接断开!当前在线客户端数为:{}",getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
// System.out.println("来自客户端的消息:" + message);
//群发消息
// for (MyWebSocket 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("WebSocket发生错误!");
error.printStackTrace();
}
public synchronized void sendMessageToOne(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendMessage(String message) {
for (WebSocketPushMsg item : webSocketSet) {
try {
item.sendMessageToOne(message);
} catch (IOException e) {
log.error("消息发送失败:{},{}",item.session.getId(),e.getMessage());
webSocketSet.remove(item);
try {
item.session.close();
} catch (IOException e1) {
e1.printStackTrace();
log.error("连接断开异常:{},{}",item.session.getId(),e.getMessage());
}
}
}
}
public static int getOnlineCount() {
return onlineCount.get();
}
private static void addOnlineCount() {
onlineCount.incrementAndGet();
}
private static void subOnlineCount() {
onlineCount.decrementAndGet();
}
}
二、前端js代码。
<!DOCTYPE HTML>
<html>
<head>
<title>My WebSocket</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button οnclick="send()">Send</button> <button οnclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
//无nginx
//websocket = new WebSocket("ws://localhost:8090/websocket");
websocket = new WebSocket("ws://www.gudao.red/websocket");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
三、nginx反向代理配置,这里要千万注意,如果使用nginx代理,那么websocket的连接默认超时时间是60s,因此如果60s不满足要求,则需要自行配置proxy_read_timeout超时时间
location /websocket {
proxy_pass http://127.0.0.1:8090;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 600;
}
到此,整个过程就完成了。值得注意是在这个过程中我遇到一个很奇怪的问题,在我的项目中是这样一个流程,用户登录之后前端立马跟后端建立webscoket在连接,但在IE浏览器上却无法建立连接,也不报任务异常。反正就是连接建立之后马上就会断掉,在网上搜索无果之后想了一个折中的办法,登录成功之后不立马建立连接而是延迟10s之后再建立webscoket连接,这个办法成功解决了问题,但至今任然没找到这个问题的真正原因,如果那么大神有缘看到这个博客,知道该问题的解决办法还请赐教!