本号主要用于分享企业中常用的技术,更加侧重于实用,欢迎关注,便于浏览其它更多实用的历史文章。
一:HTTP和WebSocket
HTTP : 客户端 -> 服务器端
传统客户端(浏览器)向服务器获取数据只能使用http主动向服务器拉数据,因为http只能从客户端发起请求,没办法从服务器端发起请求。项目中如果要即时的获取消息就只能写个定时器,每隔几秒钟去向服务器发一个http请求,获取最新的数据。http虽然是短连接,但是定时每隔几秒钟去定时发请求,会不断的占用请求,当客户端从服务端没有拉取到数据时,此时这次连接就显的浪费。
WebSocket: 客户端 <-> 服务器端
WebSocket与http最大的不同就是客户端可以主动向服务器端拉取数据,服务器端也可以主动向客户端推送数据,当客户端启动的时候会首先建立一个长连接,当需要的时候服务器端就可以通过该长连接向客户端推送数据。
HTTP与WebSocket的区别
- HTTP采用http协议,WebSocket采用ws协议
- HTTP是短连接,连接响应后即断开;WebSocket是长连接(因是长连接所以同时连接的数量不能太大)
- HTTP只能客户端向服务器发送请求,不能服务器端向客户端发起请求;WebSocket都可以
WebSocket常见使用场景
- 聊天
- 服务器端向客户端推送消息(如常见的右下角向上弹出个消息、新的未读消息数量等)
二:示例
1. pom.xml
spring-boot-starter-thymeleaf
spring-boot-starter-web
spring-boot-starter-websocket
fastjson
lombok
2. Configuration
/**
* 开启WebSocket支持
*/
@Configuration
public class WebSocketConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3. WebSocketServer
@Data
@ToString
@RequiredArgsConstructor
public class Payload {
private String from;
private String to;
private String content;
}
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thymeleaf.util.DateUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* ServerEndpoint 用户定义客户端连接的地址, 可以在路径上指定路径参数
*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {
/** 在线连接数量 */
private static final AtomicInteger onlineCount = new AtomicInteger(0);
private static ConcurrentHashMap
/** 当前连接的用户id */
private String userId;
/**
* 连接成功建立时调用该方法
* @param session
* @param userId
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
sessionMap.put(userId, session);
this.userId = userId;
int currentOnlineCount = onlineCount.incrementAndGet();
log.info("{} 连接创建成功,当前用户id为{}, 当前在线人数{}", now() , userId, currentOnlineCount);
JSONObject succesJson = new JSONObject();
succesJson.put("from", "服务器");
succesJson.put("to", userId);
succesJson.put("content", "WebSocket服务器连接成功!");
sendMessage(session, succesJson.toJSONString());
}
@OnMessage
public void onMessage(String message, Session session) {
sendMessage(session, message);
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
log.error(throwable.toString());
}
@OnClose
public void onClose(Session session) {
sessionMap.remove(this.userId);
int currentOnlineCount = onlineCount.decrementAndGet();
log.info("用户:{} 退出连接,当前连接数为:{}", this.userId, currentOnlineCount);
}
/**
* 向指定会话发送消息
* @param session 当前会话session
* @param message 消息内容
*/
public void sendMessage(Session session, String message) {
Payload payload = JSONObject.parseObject(message, Payload.class);
String toUserId = payload.getTo();
Session targetSession = sessionMap.get(toUserId);
if (targetSession == null) {
log.info("目标用户{} 已退出连接", toUserId);
return;
}
try {
targetSession.getBasicRemote().sendText(now() + " " + message);
} catch (IOException e) {
log.error("发送消息失败, 失败原因为:{}", e);
}
}
/**
* 向指定用户发送消息
* @param userId 用户id
* @param message 消息
*/
public void sendMessage(String userId, String message) {
Session session = sessionMap.get(userId);
if (session != null) {
JSONObject succesJson = new JSONObject();
succesJson.put("from", "服务器");
succesJson.put("to", userId);
succesJson.put("content", message);
this.sendMessage(session, succesJson.toJSONString());
} else {
log.info("用户{} 已退出连接,无法发送消息", userId);
}
}
private String now() {
return DateUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss", Locale.CHINESE);
}
}
4. Controller
@Controller
@RequestMapping("/im")
public class WebSocketController {
@Autowired
private WebSocketServer webSocketServer;
@RequestMapping("/users/{from}/{to}")
public ModelAndView index(@PathVariable("from") String from, @PathVariable("to") String to){
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.addObject("from", from);
modelAndView.addObject("to", to);
return modelAndView;
}
/**
* 模拟服务器端向客户端推送消息
* @param userId
* @param message
*/
@ResponseBody
@RequestMapping("/push/{userId}")
public void mockPushMessageToClient(@PathVariable String userId, String message) {
webSocketServer.sendMessage(userId, message);
}
}
5. html
templates/index.html
var webSocket = null;
var from = [[${from}]];
var to = [[${to}]];
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket, 请升级您的浏览器的版本");
}else{
// 连接WebSocket服务器端
var userId = [[${from}]];
webSocket = new WebSocket("ws://localhost:8080/websocket/" + userId);
// 打开事件
webSocket.onopen = function() {
console.log("连接服务器成功。");
};
// 获得消息事件
webSocket.onmessage = function(msg) {
console.log(msg.data);
document.getElementById('message').innerHTML += msg.data + '
';
};
// 关闭事件
webSocket.onclose = function() {
console.log("websocket已关闭");
};
// 错误事件
webSocket.onerror = function(socket, event) {
console.log("websocket发生了错误");
};
// 监听浏览器窗口关闭事件,当关闭窗口时关闭websocket连接,节省连接资源
window.onbeforeunload = function () {
websocket.close();
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
var content = document.getElementById('msg').value;
var payload = {'from': from, 'to': to, 'content': content}
webSocket.send(JSON.stringify(payload));
}
}
本号主要用于分享企业中常用的技术,更加侧重于实用,欢迎关注,便于浏览其它更多实用的历史文章。
本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/107157.html