前端(192.168.1.99)
引入js(需自行搜索咯)
<script src="/socket.io/socket.io.js"></script>
连接例子
// 连接例子
function () {
try {
// url为gateway地址,如:http://192.168.1.100:8001
var socket = io.connect(url);
socket.on('connect', function () {
console.log('ws推送onconnect:websocket连接成功');
});
socket.on('disconnect', function () {
console.log('ws推送ondisconnect:已从websocket服务器断开连接');
});
socket.on('getTestData', function (data) {
console.log('获取取getTestData内容');
});
} catch (e) {
alert("连接实时数据通讯服务失败!");
}
}
// 发送消息例子
socket.emit('setTestData', "{'roleId':2, 'projectId':2}");
// 给指定的客户端发送消息
io.sockets.socket(socketid).emit('setTestData', data);
前端域名https(证书自签)访问但后端是http的情况下,需要安装nginx
加入如下配置
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /ssl/rootCA.pem;
ssl_certificate_key /ssl/yunNX.key;
location ^~ /socket.io/ {
#启用支持websocket连接
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://192.168.1.100:8001/socket.io/;
}
}
// 前端带参连接
var socket2 = io.connect("https://192.168.1.99:443", {
'query': 'type=myTestParam'
});
后端微服务GateWay
过滤器WsFilter,根据bootstrap.yml配置文件转发/socket.io请求
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.config.LoadBalancerProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.DelegatingServiceInstance;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.net.URISyntaxException;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
@Component
@AllArgsConstructor
@Slf4j
public class WsFilter implements GlobalFilter, Ordered {
public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;
// private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
protected final LoadBalancerClient loadBalancer;
private LoadBalancerProperties properties;
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
}
@Override
@SuppressWarnings("Duplicates")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null
|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url before: " + url);
}
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
URI uri = exchange.getRequest().getURI();
// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
// if the loadbalancer doesn't provide one.s
String overrideScheme = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
if(requestUrl.getPath().startsWith("/socket.io"))
{
if(!exchange.getRequest().getMethod().equals("OPTIONS"))
{
try {
if(StringUtils.isNotEmpty(requestUrl.getQuery())){
// 带参socket,默认端口为对应微服务端口+1
requestUrl = new URI(overrideScheme + "://" + requestUrl.getHost() + ":" + (requestUrl.getPort()+1) + "/socket.io/?"+requestUrl.getQuery());
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
log.info("转向地址 socket.io "+requestUrl);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
return chain.filter(exchange);
}
protected ServiceInstance choose(ServerWebExchange exchange) {
return loadBalancer.choose(
((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
}
}
设置请求路径/socket.io/**免token校验
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
......
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity httpSecurity){
httpSecurity.oauth2ResourceServer().jwt().
jwtAuthenticationConverter(jwtAuthenticationConverter());
httpSecurity.authorizeExchange()
.pathMatchers("/xxx", "/yyy")
.access(authorizationManager)
.pathMatchers("/socket.io/**")
.permitAll()
.pathMatchers("/**")
.access(authorizationManager)
.and().exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint)
.and().csrf().disable();
httpSecurity.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint);
return httpSecurity.build();
}
......
}
配置文件bootstrap.yml
server:
port: 8001
spring:
application:
name: my-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
routes:
- id: service111-socket
uri: lb://my-service111
predicates:
- Path=/socket.io/**
- Query=type,myTestParam
- id: service222-socket
uri: lb://my-service222
predicates:
- Path=/socket.io/**
- Query=type,222
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
nacos:
server-addr: 192.168.1.88:8848
config:
file-extension: yaml
namespace: bc6ede3b-aac6-4f43-82e0-130bd2333026
extension-configs:
- data-id: redis.yml
discovery:
server-addr: 192.168.1.88:8848
namespace: bc6ede3b-aac6-4f43-82e0-130bd2333026
ip: 192.168.1.100
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: 'http://192.168.1.101:8001/my-authentication/rsa/publicKey'
微服务模块my-service111
pom.xml新增依赖
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.18</version>
</dependency>
bootstrap.yml配置socket端口号为服务端口+1
server:
port: 44446
max-http-header-size: 1024KB
spring:
application:
name: my-service111
main:
allow-bean-definition-overriding: true
cloud:
nacos:
server-addr: 192.168.1.88:8848
config:
file-extension: yaml
namespace: xxxxxxxxxx
extension-configs:
- data-id: mysql.yml
discovery:
server-addr: 192.168.1.88:8848
namespace: xxxxxxxxxx
ip: 192.168.1.102
ws:
server:
port: 44447
host: localhost
定义配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "ws.server")
@Data
public class SocketIOWsConfig {
private int port;
private String host;
}
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(SocketIOWsConfig.class)
public class SocketIOServerConfig {
@Bean
public SocketIOServer GetServer(SocketIOWsConfig socketIOWsConfig)
{
com.corundumstudio.socketio.Configuration configuration = new com.corundumstudio.socketio.Configuration();
configuration.setPort(socketIOWsConfig.getPort());
configuration.setOrigin(null);
configuration.setKeyStorePassword("1234");
configuration.setKeyStore(this.getClass().getResourceAsStream("www.xxx"));
configuration.setRandomSession(true);
return new SocketIOServer(configuration);
}
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer server) {
return new SpringAnnotationScanner(server);
}
}
随服务启动socket服务器
import com.corundumstudio.socketio.SocketIOServer;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@AllArgsConstructor
public class SocketIOServerRunner implements CommandLineRunner {
private final SocketIOServer socketIOServer;
@Override
public void run(String... args) throws Exception {
socketIOServer.start();
log.info("SocketIOServer started !");
}
}
socket处理类
@Component
@Slf4j
public class SocketIOMsgHandler {
@OnConnect
public void onConnection(SocketIOClient client) {
if (client != null) {
UUID sessionId = client.getSessionId();
log.info("用户{}连接成功", sessionId);
} else {
log.error("用户连接异常");
}
}
@OnDisconnect
public void OnDisconnect(SocketIOClient client) {
if (client != null) {
UUID sessionId = client.getSessionId();
log.info("客户端断开连接,【sessionId】= {}", sessionId);
client.disconnect();
} else {
log.error("客户端断开连接异常");
}
}
@OnEvent(value = "setTestData")
public void setTestData(SocketIOClient client, AckRequest request, String paramStr) {
if (StrUtil.isNotBlank(paramStr)) {
JSONObject jsonObject = JSONUtil.parseObj(paramStr);
Integer roleId = jsonObject.get("roleId", Integer.class);
Integer projectId = jsonObject.get("projectId", Integer.class);
......
}
}
}
另一种socket实现
微服务模块my-service111
pom.xml新增依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置类
import org.springframework.beans.factory.annotation.Autowired;
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();
}
}
socket服务器(前端连接)
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/websockets/{sid}/info")
@Component
@Slf4j
public class WebSocketHandler {
// 存放每个用户对应的WebSocket连接对象,key为custId_HHmmss,确保一个登录用户只建立一个连接
private static Map<String, Session> webSocketSessionMap = new ConcurrentHashMap<String, Session>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// 当前在线连接数
private static int onlineCount = 0;
private static WebSocketHandler webSocketHandler;
// 通过@PostConstruct实现初始化bean之前进行的操作
@PostConstruct
public void init() {
// 初使化时将已静态化的webSocketServer实例化
webSocketHandler = this;
}
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
try {
if (!webSocketSessionMap.containsKey(sid)) {
this.session = session;
webSocketSessionMap.put(sid, session);
addOnlineCount();
log.info("sid:{}连接成功", sid);
}
sendMessage(sid, sid + "连接成功");
} catch (Exception e) {
log.error("客户端连接websocket服务异常", e);
}
}
@OnClose
public void onClose(@PathParam("sid") String sid) {
if (webSocketSessionMap.containsKey(sid)) {
try {
webSocketSessionMap.get(sid).close();
webSocketSessionMap.remove(sid);
} catch (IOException e) {
log.error("连接[{}]关闭失败。", sid);
e.printStackTrace();
}
subOnlineCount();
log.info("连接[{}]关闭,当前websocket连接数:{}", sid, onlineCount);
}
}
@OnMessage
public void onMessage(String message, Session session) throws Exception {
log.info("推送内容为:{}", message);
session.getBasicRemote().sendText(message);
}
/**
* 发生错误时调用
*
* @OnError
*/
@OnError
public void onError(Session session, Throwable error) {
try {
session.close();
} catch (IOException e) {
log.error("发生错误,连接[{}]关闭失败。");
e.printStackTrace();
}
}
public boolean sendMessage(String sessionKeys, String message) {
boolean result = true;
if (StrUtil.isNotBlank(sessionKeys)) {
String[] sessionKeyArr = sessionKeys.split(",");
for (String key : sessionKeyArr) {
try {
// 可能存在一个账号多点登录
List<Session> sessionList = getLikeByMap(webSocketSessionMap, key);
if (CollUtil.isEmpty(sessionList)) {
result = false;
}
for (Session session : sessionList) {
synchronized (session) {
session.getBasicRemote().sendText(message);
}
result = true;
}
} catch (IOException e) {
e.printStackTrace();
result = false;
continue;// 某个客户端发送异常,不影响其他客户端发送
}
}
} else {
result = false;
log.info("sessionKeys为空,没有目标客户端");
}
return result;
}
/**
* 给当前客户端推送消息,首次建立连接时调用
*/
public void sendMessage(String message)
throws IOException {
synchronized (session) {
this.session.getBasicRemote().sendText(message);
}
}
private List<Session> getLikeByMap(Map<String, Session> map, String keyLike) {
List<Session> list = new ArrayList<>();
for (String key : map.keySet()) {
if (key.contains(keyLike)) {
list.add(map.get(key));
}
}
return list;
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
webSocketHandler.onlineCount++;
}
public static synchronized void subOnlineCount() {
webSocketHandler.onlineCount--;
}
}
前端连接,懒所以用在线工具
ws://ip:44446/websockets/323/info
后端推送消息
@Component
@Slf4j
public class PushService {
@Autowired
private WebSocketHandler webSocketHandler;
// 往socket推送数据
@Async
public void push(String socketId, String content) {
String content = "";
try {
boolean result = true;
int i = 0;
while (result) {
result = webSocketHandler.sendMessage(socketId, content);
if(!result && i<5){
//推送失败重试3次
i++;
result =!result;
}
Thread.sleep(4000);
log.info("{}推送内容为:{}", socketId, content);
}
}catch (Exception e){
log.info("{}推送已停止", socketId);
log.info("", e);
}
}
}
socket客户端(连接第三方socket)
@ClientEndpoint
@Component
@Slf4j
public class WebSocketClient {
// 存放每个用户对应的WebSocket连接对象,key为custId_HHmmss,确保一个登录用户只建立一个连接
private static Map<String, Session> webSocketSessionMap = new ConcurrentHashMap<String, Session>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// 当前在线连接数
private static int onlineCount = 0;
private static WebSocketClient webSocketHandler;
@Autowired
private SocketIOServer server;
// 存放每个用户对应的WebSocket连接对象,key为custId_HHmmss,确保一个登录用户只建立一个连接
public static Map<UUID, UUID> socketIOServerSessionMap = new ConcurrentHashMap<UUID, UUID>();
// 通过@PostConstruct实现初始化bean之前进行的操作
public void init()
{
// 初使化时将已静态化的webSocketServer实例化
webSocketHandler = this;
}
@OnOpen
public void onOpen(Session session){
try {
String sid = session.getId();
if (CollUtil.isEmpty(webSocketSessionMap)) {
this.session = session;
webSocketSessionMap.put(sid, session);
addOnlineCount();
log.info("sid:{}连接成功", sid);
}
// sendMessage(sid, sid+"连接成功");
}catch (Exception e){
log.error("客户端连接websocket服务异常", e);
}
}
@OnClose
public void onClose(Session session) {
String sid = session.getId();
if (webSocketSessionMap.containsKey(sid))
{
try
{
webSocketSessionMap.get(sid).close();
webSocketSessionMap.remove(sid);
}
catch (IOException e)
{
log.error("连接[{}]关闭失败。", sid);
e.printStackTrace();
}
subOnlineCount();
log.info("连接[{}]关闭,当前websocket连接数:{}", sid, onlineCount);
}
}
@OnMessage
public void onMessage(String message, Session session) throws Exception {
log.info("推送内容为:{}", message);
List<UUID> uuidList = new ArrayList<>();
socketIOServerSessionMap.entrySet().stream().forEach(e -> {
if(null != server.getClient(e.getKey())){
server.getClient(e.getKey()).sendEvent("getTestData", message);
}else{
uuidList.add(e.getKey());
}
});
// 清除无用的uuid
uuidList.forEach(u -> {
socketIOServerSessionMap.remove(u);
});
}
/**
* 发生错误时调用
*
* @OnError
*/
@OnError
public void onError(Session session, Throwable error) {
try{
session.close();
}catch (IOException e){
log.error("发生错误,连接[{}]关闭失败。");
e.printStackTrace();
}
}
}
建立连接调用例子
@Autowired
WebSocketClient webSocketClient;
// 获取socket上报数据
@OnEvent(value = "setTestData")
public void setTestData(SocketIOClient client, AckRequest request, String paramStr) throws IOException {
String uri = "ws://IP:7001/ws";
WebSocketContainer container = null;
try {
container = ContainerProvider.getWebSocketContainer();
URI r = URI.create(uri);
webSocketClient.socketIOServerSessionMap.put(client.getSessionId(), client.getSessionId());
container.connectToServer(webSocketClient, r);
log.info("连接socket成功");
} catch (DeploymentException | IOException e) {
e.printStackTrace();
}
}