集群版本的websocket,不能单独使用,需要和redis来做消息分发。至于原因,我觉得大家都懂了,bb再多,不如代码一行。
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
package com.cjkj.message.config;
import com.cjkj.common.redis.template.StringRedisUtil;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.*;
/**
* @program: cjkj
* @description:
* @author: Mr.Wang
* @create: 2020-05-14 16:50
**/
@Service
public class CTIHandler implements WebSocketHandler, RedisMsg {
@Autowired
private StringRedisUtil stringRedisUtil;
/**
* 配置日志
*/
private static Logger logger = Logger.getLogger(CTIHandler.class);
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static Map<String, List<WebSocketSession>> socketMap = new HashMap<String, List<WebSocketSession>>();
//新增socket
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("成功建立连接");
//获取用户信息
String userName = (String) session.getAttributes().get("userName");
logger.info("获取当前用户信息:"+userName);
List<WebSocketSession> sessionList = socketMap.get(userName);
if (CollectionUtils.isEmpty(sessionList)) {
List list=new ArrayList();
list.add(session);
socketMap.put(userName,list);
}else {
socketMap.get(userName).add(session);
}
sendMessageToUser(userName, new TextMessage("ok"));
// if(socketMap.get(userName)==null) {
// List list=new ArrayList();
// list.add(session);
// socketMap.put(userName,list);
// sendMessageToUser(userName, new TextMessage("ok"));
// //并且通过redis发布和订阅广播给其他的的机器,或者通过消息队列
// }
logger.info("链接成功");
}
//接收socket信息
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
logger.info("收到信息:"+webSocketMessage.toString());
String userName = (String) webSocketSession.getAttributes().get("userName");
String payload = webSocketMessage.getPayload().toString();
if (StringUtils.isNotBlank(payload) && "ping".equals(payload)){
sendMessageToUser(userName, new TextMessage("pong"));
}
// webSocketSession.sendMessage(new TextMessage("aaa"));
// sendMessageToUser(userName, new TextMessage("我收到你的信息了"));
}
/**
* 发送信息给指定用户
* @param clientId
* @param message
* @return
*/
public boolean sendMessageToUser(String clientId, TextMessage message) {
logger.info("clientId:" + clientId);
WebSocketSession session = socketMap.get(clientId).get(socketMap.get(clientId).size()-1);
if(session==null) {
return false;
}
logger.info("进入发送消息");
if (!session.isOpen()) {
return false;
}
try {
logger.info("正在发送消息");
session.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public boolean sendMessageToUserList(String clientId, TextMessage message) {
logger.info("clientId:" + clientId);
List<WebSocketSession> sessionList = socketMap.get(clientId);
if(CollectionUtils.isEmpty(sessionList)) {
return false;
}
sessionList.forEach(session -> {
logger.info("进入发送消息");
if (!session.isOpen()) {
}
try {
logger.info("正在发送消息");
session.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
});
return true;
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
logger.info("连接出错");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
//获取用户信息
String userName = (String) session.getAttributes().get("userName");
if(socketMap.get(userName)!=null) {
//socketMap.remove(userName);
ListIterator<WebSocketSession> listIter = socketMap.get(userName).listIterator();
while (listIter.hasNext()) {
WebSocketSession s = listIter.next();
if (s.getId().equals(session.getId())) {
listIter.remove();
}
}
//并且通过redis发布和订阅广播给其他的的机器,或者通过消息队列
}
logger.info("连接已关闭:" + status);
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 接受订阅信息
*/
@Override
public void receiveMessage(String message) {
// TODO Auto-generated method stub
JSONObject sendMsg = JSONObject.fromObject(message.substring(message.indexOf("{")));
String clientId = sendMsg.getString("userName");
TextMessage receiveMessage = new TextMessage(sendMsg.getString("message"));
boolean flag = sendMessageToUserList(clientId, receiveMessage);
if(flag) {
logger.info("我发送消息成功了!");
}
}
}
package com.cjkj.message.config;
import org.springframework.stereotype.Component;
@Component
public interface RedisMsg {
/**
* 接受信息
* @param message
*/
public void receiveMessage(String message);
}
package com.cjkj.message.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
/**
* @program: cjkj
* @description:
* @author: Mr.Wang
* @create: 2020-05-18 12:49
**/
@Configuration
public class RedisPublishConfig {
/**
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
*
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean // 相当于xml中的bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅了一个叫chat 的通道
container.addMessageListener(listenerAdapter, new PatternTopic("websocket"));
// 这个container 可以添加多个 messageListener
return container;
}
/**
* 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
*
* @param receiver
* @return
*/
@Bean
MessageListenerAdapter listenerAdapter(RedisMsg receiver) {
// 这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
// 也有好几个重载方法,这边默认调用处理器的方法 叫handleMessage 可以自己到源码里面看
return new MessageListenerAdapter(receiver, "receiveMessage");
}
}
package com.cjkj.message.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* @program: cjkj
* @description:
* @author: Mr.Wang
* @create: 2020-05-14 20:35
**/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//handler是webSocket的核心,配置入口
registry.addHandler(new CTIHandler(), "/ws/websocket/{ID}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
}
}
package com.cjkj.message.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.log4j.Logger;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import java.util.Map;
/**
* @program: cjkj
* @description:
* @author: Mr.Wang
* @create: 2020-05-14 16:47
**/
@Slf4j
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
/**
* 配置日志
*/
private static Logger logger = Logger.getLogger(WebSocketInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse seHttpResponse,
WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
logger.info("当前连接:"+serverHttpRequest.getURI());
String userName = serverHttpRequest.getURI().toString().split("/websocket/")[1];
attributes.put("userName", userName);
logger.info("握手之前");
//从request里面获取对象,存放attributes
return super.beforeHandshake(serverHttpRequest, seHttpResponse, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
logger.info("握手之后");
super.afterHandshake(request, response, wsHandler, ex);
}
}
package com.cjkj.message.controller.websocket;
import com.cjkj.common.redis.template.StringRedisUtil;
import com.cjkj.message.config.CTIHandler;
import net.sf.json.JSONObject;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.TextMessage;
/**
* @program: cjkj
* @description:
* @author: Mr.Wang
* @create: 2020-05-14 17:13
**/
@RestController
@RequestMapping("/socket")
public class WebSocketPushMegController {
private static Logger logger = Logger.getLogger(WebSocketPushMegController.class);
@Autowired
private StringRedisUtil stringRedisUtil;
@PostMapping("/sendmessage")
public String list(String id, String message) {
JSONObject result = new JSONObject();
try {
CTIHandler ctiHandler=new CTIHandler();
Boolean flag = ctiHandler.sendMessageToUser(id, new TextMessage(message));
//if (!flag) {//发送失败广播出去,让其他节点发送
//广播消息到各个订阅者
JSONObject message1 = new JSONObject();
message1.put("userName", id);
message1.put("message", message);
stringRedisUtil.convertAndSend("websocket", message1.toString());
// }
} catch (Exception e) {
e.printStackTrace();
logger.error("推送给客户端失败");
result.put("result", "error");
}
result.put("result", "success");
return result.toString();
}
}
其中redis的工具类可以自己用redistemplate。
registry.addHandler(new CTIHandler(), "/ws/websocket/{ID}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
这个就是接口访问地址,可以用postman测试
遇到的问题,因为我们项目的网关用的zuul,但是zuul并不支持tcp,所以连接不通。然后我们就绕过网关,用nginx做代理直接访问。如果大家对这个又研究,还请告知具体实现方式。
nginx配置websocket也需要配置,否则访问不通。
upstream wsserver {
server 10.253.96.128:8088;
server 10.253.96.127:8088;
#server 10.253.96.122:8080;
}
server {
listen 80;
server_name localhost;
client_max_body_size 50M;
#charset koi8-r;
location /ws/{
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#rewrite ^/ws/(.*)$ /$1 break;
proxy_pass http://wsserver/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
716

被折叠的 条评论
为什么被折叠?



