介绍一下主人翁吧
WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。
为什么需要WebSocket
正所谓技术服务于业务,如果技术不能解决业务上的痛点,那它存在的意义在哪?咱先分析几个业务场景:
1. 订单支付 2. 系统通知 3. 即时通讯 在很多电商项目中,当我们利用支付宝或者微信扫码支付后,我们要等待网页响应,然后跳转支付结果页面。其实在整个支付过程中,浏览器先向服务器发送支付请求,然后服务端给浏览器返回一个二维码,然后客户扫码支付,但是支付没支付,浏览器是不知道的啊,只有服务端知道,所以浏览器要不停去问服务端:“xxx订单支付成功了吗?”,就像下图这样 这种实现方法很简单,就是ajax轮询。
系统通知:比如有些论坛,当你发了一篇帖子,过一会有人回复你了,这个时候一般系统都会推送给你,告诉你有个新回复。
即时通讯:网页上的在线客服,你与客服的对话。
对于实时性要求不高的业务场景,我们可以用ajax轮询基本就可以实现需求。但是对于实时性要求特别高的呢,比如即时通讯,这你不能一直ajax去轮询吧。 所以我们希望服务端能自己主动通知浏览器,而不是浏览器每次都是自己去问服务端。
所以!WebSocket协议,它来了! 当浏览器和服务端建立WebSocket协议后,他两的对话就变成下图这样了
项目
花了亿点点时间算是大概介绍了一下websocket,和它的作用。 现在到正题了,用Java写一个聊天室! 实现功能如下:好友列表,好友在线状态提醒,一对一聊天,收到新消息提醒。
技术栈:
后端(主要):Spring Boot + Socket.io + Mybatis Plus + Mysql 前端(主要):Vue + Element UI + Socket.io
实现效果如下:
前端代码
1.引入Socket.io
npm install vue-socket.io --save
import VueSocketIO from 'vue-socket.io'
Vue.use(new VueSocketIO({
debug: true,
connection: `http://127.0.0.1:9999/?token=${token}`,
vuex: {
store,
actionPrefix: 'SOCKET_',
mutationPrefix: 'SOCKET_'
}
}))
2.具体在组件中使用
<template>
</template>
<script>
export default {
name: 'demo',
data() {
return {
}
},
sockets: {
connect() {
console.log('connect连接服务端')
},
disconnect() {
console.log('从服务器断开连接....')
},
reconnect() {
console.log('正在尝试重新连接....')
},
server_event(data) {
console.log('收到服务端的通知',data)
}
}
}
</script>
后端代码
Maven主要引入
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.18</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>`
配置文件(.yml)
socketio:
host: 127.0.0.1
port: 9999
bossCount: 1
workCount: 100
allowCustomRequests: true
upgradeTimeout: 10000
pingTimeout: 60000
pingInterval: 25000
maxHttpContentLength: 1048576
maxFramePayloadLength: 1048576
Socket.io配置代码
@Data
@ConfigurationProperties("socketio")
public class AppProperties {
private String host;
private Integer port;
private int bossCount;
private int workCount;
private boolean allowCustomRequests;
private int upgradeTimeout;
private int pingTimeout;
private int pingInterval;
private int maxHttpContentLength;
private int maxFramePayloadLength;
}
@Slf4j
@Configuration
@EnableConfigurationProperties({
AppProperties.class,
})
public class AppConfiguration {
@Bean
public SocketIOServer socketIoServer(AppProperties appProperties) {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
config.setSocketConfig(socketConfig);
config.setHostname(appProperties.getHost());
config.setPort(appProperties.getPort());
config.setBossThreads(appProperties.getBossCount());
config.setWorkerThreads(appProperties.getWorkCount());
config.setAllowCustomRequests(appProperties.isAllowCustomRequests());
config.setUpgradeTimeout(appProperties.getUpgradeTimeout());
config.setPingTimeout(appProperties.getPingTimeout());
config.setPingInterval(appProperties.getPingInterval());
config.setMaxHttpContentLength(appProperties.getMaxHttpContentLength());
config.setMaxFramePayloadLength(appProperties.getMaxFramePayloadLength());
return new SocketIOServer(config);
}
}
这里很关键!!!主要是发送和接受消息!
@Slf4j
@Service
@RequiredArgsConstructor
public class MsgSendService {
private static final Map<String, SocketIOClient> CLIENT_MAP = new ConcurrentHashMap<>();
private static final Map<String, String> USER_CLIENT_MAP = new ConcurrentHashMap<>();
private final SocketIOServer socketIOServer;
@PostConstruct
private void autoStart() {
log.info("start ws");
socketIOServer.addConnectListener(client -> {
String token = getClientToken(client, "token");
if (checkToken(client, token)) {
CLIENT_MAP.put(client.getSessionId().toString(), client);
} else {
client.disconnect();
}
log.info("目前在线用户有:{}", CLIENT_MAP.size());
});
socketIOServer.addDisconnectListener(client -> {
String token = getClientToken(client, "token");
CLIENT_MAP.remove(client.getSessionId().toString());
client.disconnect();
log.info("移除client:{}", client.getSessionId());
});
socketIOServer.addEventListener("sendMsg",Integer.class,((socketIOClient, integer, ackRequest) -> {
System.out.println(integer);
}));
socketIOServer.start();
log.info("start finish");
}
private String getClientToken(SocketIOClient client, String key) {
Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
List<String> list = params.get(key);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
}
return null;
}
private boolean checkToken(SocketIOClient client, String token) {
log.info("检查token是否有效:{}", token);
if (StrUtil.isNotEmpty(token) && !"undefined".equals(token)){
Boolean tokenFlag = true;
if(tokenFlag){
USER_CLIENT_MAP.put(userId,client.getSessionId().toString());
return true;
}
}
}
return false;
}
@PreDestroy
private void onDestroy() {
if (socketIOServer != null) {
socketIOServer.stop();
}
}
public void sendMsg(Object demo) {
CLIENT_MAP.forEach((key, value) -> {
UUID sessionId = value.getSessionId();
System.out.println(sessionId);
value.sendEvent("server_event", demo);
log.info("发送数据成功:{}", key);
});
}
}
结束
首先,我要道歉,我没有放出具体实现的代码,但是我把最基础的代码放出来了,就是服务端通知浏览器的代码。 主要是因为目前我代码写的太乱了,完全就是一个练手项目。其实我说的功能基本都是基于那两个集合实现的,有兴趣的可以自己捣鼓。
|