本文将以简洁的讲述带你快速上手webscoket
Websocket是一个持久化的协议而HTTP是非持久性的,它可以做到保持长时间连接,而HTTP在请求一次完就结束了
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
WebSocket 的主要区别 此图来源于网络:
我们平常前端与后端交互时使用的都是HTTP协议,但HTTP是不支持持久连接的(长连接,循环连接的不算),例如我们需要做一个文件上传进度的回显,那么平常我们使用HTTP协议的话大部分都是通过轮询形式,每隔多长时间去访问一次后台 仔细想想这样做是对资源的一种浪费 一直在不断地在请求
webscoket的出现完美解决了这个问题,它与后端建立了一座桥梁 以持久性的形式保持连接,这样就能实时获取后台的数据。
长连接形式适用一些实时性高的场景,例如:
1.社交 / 订阅
2.股票报价
3.音视频聊天
4.运动轨迹相关业务(定位应用)
下面使用IM聊天的应用进行简单示例
后端我们使用springboot后台 ,我们需要先导入webscoket相关的pom文件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
开启webscoket支持
@Configuration
public class WebScoketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
注解说明:
//注解表示将该类升级为一个WebSocket服务端点
@ServerEndPoint
//收到客户端发来的消息时触发
@OnMessage
//客户端连接上服务端时触发
@OnOpen
//当连接关闭时触发
@OnClose
//发生错误时触发
@OnError
由于hashmap是线程不安全的使用ConcurrentHashMap来存储每个用户与服务器之间的会话。
1.有同学问不用考虑单例情况嘛?
单例在以下示例中并不会发生,这里的单例只是针对外部的webScoketSever,而我们的每个会话都已保存在了定义的map中并不会有影响
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author CodingRem
*/
@ServerEndpoint("/imserver/{userId}")
@Component
public class WebScoketServer {
static Logger logger = LoggerFactory.getLogger(WebScoketServer.class);
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentHashMap<String, WebScoketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收userId
*/
private String userId = "";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
//如果存在会话就删除重新创建
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
webSocketMap.put(userId, this);
//加入set中
} else {
webSocketMap.put(userId, this);
//加入set中
addOnlineCount();
//在线数加1
}
logger.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
try {
sendMessage("连接成功");
} catch (IOException e) {
logger.error("用户:" + userId + ",网络异常!!!!!!");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//从set中删除
subOnlineCount();
}
logger.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param content 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String content, Session session) {
logger.info("接收到来自id:" + userId + "的消息:" + content);
//消息保存到数据库、redis
if (StringUtils.isNotBlank(content)) {
try {
//解析JSON格式
JSONObject jsonObject = JSON.parseObject(content);
//为了保障安全 增加一个消息来源
jsonObject.put("fromUserId", this.userId);
String toId = jsonObject.getString("toId");
//发送给对应用户
if (StringUtils.isNotBlank(toId) && webSocketMap.containsKey(toId)) {
webSocketMap.get(toId).sendMessage(jsonObject.toJSONString());
} else {
logger.error("ID为:" + toId + "的用户不在线");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
}
/**
* 服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 主動發送消息
*/
public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
logger.info("发送消息到:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
logger.error("用户" + userId + ",不在线!");
}
}
/***
* 获取在线人数
* @return
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebScoketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebScoketServer.onlineCount--;
}
}
为了简化流程,这里我定义两条虚拟数据分别代表两个用户 张三与李四通过名字首拼进行登录,xmd可以根据自己的需求进行改造
import com.rem.websocket.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.ArrayList;
import java.util.List;
@Controller
public class IndexController {
@RequestMapping("/zs")
String index(ModelMap map) {
List<User> users = new ArrayList<>();
users.add(new User(2,"李四"));
users.add(new User(3,"王五"));
map.put("users",users);
map.put("user",new User(1,"张三"));
return "index";
}
@RequestMapping("/ls")
String index2(ModelMap map) {
List<User> users = new ArrayList<>();
users.add(new User(1,"张三"));
users.add(new User(3,"王五"));
map.put("users",users);
map.put("user",new User(2,"李四"));
return "index";
}
}
创建webscoket对象
var socketUrl = "http://localhost:80/imserver/" + $("#userId").val();
socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
console.log(socketUrl);
if (socket != null) {
socket.close();
socket = null;
}
socket = new WebSocket(socketUrl);
协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
主要的调用事件:
scoket.onopen //连接时触发
scoket.onmessage //接收到消息时触发
scoket.onclose //关闭时触发
scoket.onerror //发生错误时触发
<script>
$(function () {
//初始化: active为选择联系人css active-chat为对话显示css
var person = $("ul li:first.person");
var name = person.find("span.name").text();
var fpId = person.attr("data-chat");
$("div.top span.name").text(name);//初始的顶部显示
var chat = $("div.chat[data-chat='" + fpId + "']");
person.addClass("active");
chat.addClass("active-chat");
clickPerson(fpId);
//打开scoket连接
openSocket();
})
var socket;
function openSocket() {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
var socketUrl = "http://localhost:80/imserver/" + $("#userId").val();
socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
console.log(socketUrl);
if (socket != null) {
socket.close();
socket = null;
}
socket = new WebSocket(socketUrl);
//连接时触发
socket.onopen = function () {
console.log("websocket已打开");
};
//接收到消息时触发
socket.onmessage = function (msg) {
var obj = msg.data;
console.log(obj);
if (isJson(obj)) {//验证是否是json格式
obj = JSON.parse(obj);
console.log("用户:" + obj.fromUserId + "发来消息");
//处理接收到的消息
receiveMsg(obj);
} else {
console.log(obj);
}
};
//关闭时触发
socket.onclose = function () {
console.log("websocket已关闭");
};
//发生错误时触发
socket.onerror = function () {
console.log("websocket发生了错误");
}
}
}
function isJson(str) {
try {
JSON.parse(str)
return true
} catch (e) {
return false
}
}
function receiveMsg(obj) {
var chat = $(".chat[data-chat='" + obj.fromUserId + "']");
chat.append("" + obj.content + "");
}
$(".write-link.send").click(function () {
sendMessage();
var toId = $("#toId").val();
var chat = $("div.chat[data-chat='" + toId + "']");
var content = $("#content");
chat.append("" + content.val() + "");
content.val("");
});
//发送消息
function sendMessage() {
if (typeof (WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
} else {
console.log("您的浏览器支持WebSocket");
console.log('{"toId":"' + $("#toId").val() + '","content":"' + $("#content").val() + '"}');
socket.send('{"toId":"' + $("#toId").val() + '","content":"' + $("#content").val() + '"}');
}
}
//更改目标id为当前选择的联系人对应id
function clickPerson(id) {
$("#toId").val(id);
}
script>
至此这个示例已经完成,是不是感觉特别简单?让我们看看效果图
本篇文章到此结束,如果这篇文章对你有帮助欢迎点赞、评论、交流、学习。
下载Demo