一、websocket定义
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
为实现两个一百年的奋斗目标、实现中华名族伟大复兴而努力
二、websocket出现之前是如何通信的
之前很多网站为了前后台通讯,基本采用技术为AJAX轮询,轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
三、websocket使用
看了这么多,该上代码的时候了,该项目基于spring + spring mvc + tomcat +websocket实现对指定用户通知
首先新建spring 项目
ps:如何用idea新建spring+spring mvc + maven项目
pom文件添加java-webSocket包,和servlet包
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
新建websocket类,路径如下图
wspool里代码如下,wspool类主要功能负责添加用户及给用户发信息。
package com.ceshi.util.websocketNotice;
import org.java_websocket.WebSocket;
import java.util.*;
public class WsPool {
private static final Map<WebSocket, String> wsUserMap = new HashMap<WebSocket, String>();
/**
* 通过websocket连接获取其对应的用户
*
* @param conn
* @return
*/
public static String getUserByWs(WebSocket conn) {
return wsUserMap.get(conn);
}
/**
* 根据userName获取WebSocket,这是一个list,此处取第一个
* 因为有可能多个websocket对应一个userName(但一般是只有一个,因为在close方法中,我们将失效的websocket连接去除了)
*
* @param userName
*/
public static WebSocket getWsByUser(String userName) {
Set<WebSocket> keySet = wsUserMap.keySet();
synchronized (keySet) {
for (WebSocket conn : keySet) {
String cuser = wsUserMap.get(conn);
if (cuser.equals(userName)) {
return conn;
}
}
}
return null;
}
/**
* 向连接池中添加连接
*
* @param userName
* @param conn
*/
public static void addUser(String userName, WebSocket conn) {
wsUserMap.put(conn, userName); // 添加连接
System.out.println(wsUserMap);
}
/**
* 获取所有连接池中的用户,因为set是不允许重复的,所以可以得到无重复的user数组
*
* @return
*/
public static Collection<String> getOnlineUser() {
List<String> setUsers = new ArrayList<String>();
Collection<String> setUser = wsUserMap.values();
for (String u : setUser) {
setUsers.add(u);
}
return setUsers;
}
/**
* 移除连接池中的连接
*
* @param conn
*/
public static boolean removeUser(WebSocket conn) {
if (wsUserMap.containsKey(conn)) {
wsUserMap.remove(conn); // 移除连接
return true;
} else {
return false;
}
}
/**
* 向特定的用户发送数据
*
* @param conn
* @param message
*/
public static void sendMessageToUser(WebSocket conn, String message) {
if (null != conn && null != wsUserMap.get(conn)) {
conn.send(message);
}
}
/**
* 向所有的用户发送消息
*
* @param message
*/
public static void sendMessageToAll(String message) {
Set<WebSocket> keySet = wsUserMap.keySet();
synchronized (keySet) {
for (WebSocket conn : keySet) {
String user = wsUserMap.get(conn);
if (user != null) {
conn.send(message);
}
}
}
}
}
新建WsService类,继承WebSocketService,检测WebSocket链接、断开、发信息、出现错误等的状态
package com.ceshi.util.websocketNotice;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
public class WsServer extends WebSocketServer {
public WsServer(int port) {
super(new InetSocketAddress(port));
}
public WsServer(InetSocketAddress address) {
super(address);
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
// ws连接的时候触发的代码,onOpen中我们不做任何操作
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
//断开连接时候触发代码
userLeave(conn);
System.out.println(reason);
}
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println(message);
if(null != message &&message.startsWith("online")){
String userName=message.replaceFirst("online", "");//用户名
userJoin(conn,userName);//用户加入
}else if(null != message && message.startsWith("offline")){
userLeave(conn);
}
}
@Override
public void onError(WebSocket conn, Exception ex) {
//错误时候触发的代码
System.out.println("on error");
ex.printStackTrace();
}
/**
* 去除掉失效的websocket链接
* @param conn
*/
private void userLeave(WebSocket conn){
WsPool.removeUser(conn);
}
/**
* 将websocket加入用户池
* @param conn
* @param userName
*/
private void userJoin(WebSocket conn,String userName){
WsPool.addUser(userName, conn);
}
}
在web-info下面新建静态资源包,如下图所示
配置文件增加下面一句话,支持访问静态资源
<!-- 对静态资源文件的访问 restful -->
<mvc:resources mapping="/static/**" location="WEB-INF/static/"/>
wbs内容如下,jquery可以从网上下载一个
var websocket = '';
var last_health;
var health_timeout = 10;
// 假装某个用户已经登录系统
var userName = "admin";
var layer;
if (window.WebSocket) {
// 建立链接
var URL = document.URL;
if(URL.indexOf("https") == -1){
websocket = new WebSocket(encodeURI('ws://' + document.domain + ':8887'));
}else{
websocket = new WebSocket(encodeURI('wss://' + document.domain + ':8887'));
}
websocket.onopen = function() {
console.log('已连接' + userName);
websocket.send("online" + userName);
heartbeat_timer = setInterval(function() {
keepalive(websocket)
}, 60000);
};
websocket.onerror = function() {
console.log('连接发生错误');
};
websocket.onclose = function() {
console.log('已经断开连接');
initWs();
};
// 消息接收
websocket.onmessage = function(message) {
alert(message.data);
};
} else {
alert("该浏览器不支持下单提醒。<br/>建议使用高版本的浏览器,<br/>如 IE10、火狐 、谷歌 、搜狗等");
}
var initWs = function() {
if (window.WebSocket) {
// websocket = new WebSocket( encodeURI('ws://' + document.domain + ':8887'));
websocket.onopen = function() {
console.log('已连接');
websocket.send("online"+userName);
heartbeat_timer = setInterval(function() {
keepalive(websocket)
}, 60000);
};
websocket.onerror = function() {
console.log('连接发生错误');
};
websocket.onclose = function() {
console.log('已经断开连接');
initWs();
};
// 消息接收
websocket.onmessage = function(message) {
console.log(message);
};
} else {
alert("该浏览器不支持下单提醒。<br/>建议使用高版本的浏览器,<br/>如 IE10、火狐 、谷歌 、搜狗等");
}
}
// 心跳包
function keepalive(ws) {
var time = new Date();
if (last_health != -1 && (time.getTime() - last_health > health_timeout)) {
// ws.close();
} else {
if (ws.bufferedAmount == 0) {
ws.send('');
// ws.send('~HC~');
}
}
}
hello.jsp引入js,如下图所示
controller类中模拟发送信息,代码如下图
package com.ceshi.controller;
import com.ceshi.util.websocketNotice.WsPool;
import org.java_websocket.WebSocket;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/ceshi")
public class CeshiController {
/**
* 测试页面,进入页面时,加载js,websocket建立连接
* @return
*/
@RequestMapping("/index")
public String ceshi(){
return "hello";
}
/**
* 测试返回数据
* @return
*/
@RequestMapping("/send")
@ResponseBody
public String index(){
WebSocket ws = WsPool.getWsByUser("admin");
if(ws != null){
WsPool.sendMessageToUser(ws, "你好,admin,我是使用websocket发送的一条信息!");
return "给admin发信息成功!";
}else{
return "给admin发信息失败!";
}
}
}
新建StartFilters类,继承Filter 类,代码如下
package com.ceshi.filter;
import com.ceshi.util.websocketNotice.WsServer;
import org.java_websocket.WebSocketImpl;
import javax.servlet.*;
import java.io.IOException;
public class StartFilters implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
}
public void init(FilterConfig arg0) throws ServletException {
this.startWebsocketInstantMsg();
}
/**
* 启动即时聊天服务
*/
public void startWebsocketInstantMsg() {
WebSocketImpl.DEBUG = false;
WsServer s;
s = new WsServer(8887);
s.start();
}
}
web.xml中配置StartFilters,使启动项目时,加载WsService类,启动websocket服务,ok,至此所有配置完成,启动项目测试一下
<filter>
<filter-name>startFilters</filter-name>
<filter-class>com.ceshi.filter.StartFilters</filter-class>
</filter>
进入hello.jsp页面,F12调试模式,显示已经链接
从新开一个浏览器 ,模拟发送信息,如下图所示
此时google浏览器已经接受到发送的信息,如下图,至此websocket配置成功!
项目源码地址
链接:https://pan.baidu.com/s/1OA21g_YhaX6SanWUb-ra5A
提取码:ngn2
一个相信坚持写好代码可以改变生活的程序员小哥哥
坚持、热爱、奋斗、幸福