websocket 单机服务 和 分布式集群解决方案

1.为什么有了http还需要websocket
为 HTTP 协议有一个缺陷:通信只能由客户端发起,如果服务器有连续的状态变化,只能使用"轮询"
WebSocket 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息(这个功能可以做个聊天器哦)
默认端口也是80和443 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL

关于短连接 ,长连接

长连接: HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。
短连接: Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯。
关键字:Keep-Alive

websocket 单机解决方案:

1。引入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
@Component
@Slf4j
@ServerEndpoint("/iflytek/{userId}") //此注解相当于设置访问URL
public class IFlytekVoiceSocket {
    private Session session;
    private String userId;
    private static CopyOnWriteArraySet<IFlytekVoiceSocket> iFlytekVoiceSockets = new CopyOnWriteArraySet<>();
    private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<String, Session>();
    // 每当发生websocket连接时,会执行此方法,可以在这是缓存用户和session
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        try {
            this.session = session;
            this.userId = userId;
            iFlytekVoiceSockets.add(this);
            sessionPool.put(userId, session);
            log.info("【websocket消息】有新的连接,总数为:" + iFlytekVoiceSockets.size()
                    + "类型type:" + userId
                    + "连接userId:" + userId);
        } catch (Exception e) {
        }
    }
       // 每当断开websocket连接时,会执行此方法,可以去除用户和session
    @OnClose
    public void onClose() {
        try {
            iFlytekVoiceSockets.remove(this);
            sessionPool.remove(this.userId);
            log.info("【websocket消息】连接断开,总数为:" + iFlytekVoiceSockets.size()
                    + "类型type:" + userId
                    + "断开userid:" + userId);
        } catch (Exception e) {
        }
    }
        @OnMessage
    public void onMessage(String message) {
        IFlytekVoice iFlytekVoice = JSONObject.parseObject(message, IFlytekVoice.class);
        log.info("【iflytek】收到客户端消息:" + iFlytekVoice.toString());
        LambdaQueryWrapper<SysBaseDict> sysBaseDictLambdaQueryWrapper = new LambdaQueryWrapper<>();
        sysBaseDictLambdaQueryWrapper.eq(SysBaseDict::getDictCode, "iflytek");
        System.out.println("iFlytekVoice " + iFlytekVoice);
        ISysBaseDictService sysBaseDictService = (ISysBaseDictService) SpringContextUtils.getBean("sysBaseDictServiceImpl");
        SysBaseDict one = sysBaseDictService.getOne(sysBaseDictLambdaQueryWrapper);
        LambdaQueryWrapper<SysBaseDict> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysBaseDict::getDictId, one.getId());
        queryWrapper.eq(SysBaseDict::getItemCode, iFlytekVoice.getCmd());
        SysBaseDict one1 = sysBaseDictService.getOne(queryWrapper);
        System.out.println("one1 " + one1);
        sessionPool.forEach((k, v) -> {
            if ("saas".equals(k)) {
                v.getAsyncRemote().sendText(one1.getItemValue());
            }
        });
    }
}

分布式方案解决:

但是以上只能是单机服务下有效 ,在分布式应用下,由于每个服务的jvm隔离,sessionPool 存的也不一样,所以需要借助第三方工具实现,这里使用redis 发布订阅系统实现

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2 。注册一个redis消息监听配置类 RedisConfig
@Slf4j
@EnableCaching
@Configuration
public class RedisConfig {

/**
 * redis 监听容器, 可以添加多个监听不同的频道,只需要把消息监听器和对应的监听处理器绑定     绑定的方式是监听容器的入参 监听处理器 的名称 和  下面监听处理器的 bean name 一致
 *
 * @param redisConnectionFactory redis 配置
 * @return
 */
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory,
													RedisReceiver redisReceiver,
													MessageListenerAdapter commonListenerAdapter,
													MessageListenerAdapter iflyTekListenerAdapter) {
	RedisMessageListenerContainer container = new RedisMessageListenerContainer();
	container.setConnectionFactory(redisConnectionFactory);
	container.addMessageListener(commonListenerAdapter, new ChannelTopic(GlobalConstants.REDIS_TOPIC_NAME));
      // 添加绑定channle 名称
	container.addMessageListener(iflyTekListenerAdapter, new ChannelTopic(GlobalConstants.IFLYTEK_TOPIC_NAME));
	return container;
}


@Bean
MessageListenerAdapter commonListenerAdapter(RedisReceiver redisReceiver) {
	MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(redisReceiver, "onMessage");
	messageListenerAdapter.setSerializer(jacksonSerializer());
	return messageListenerAdapter;
}

/**          
 * 讯飞监听处理器     和消息监听容器绑定     发布用会调用RedisReceiver 的  onIflyTekMessage 方法
 * @param redisReceiver
 * @return
 */
@Bean
MessageListenerAdapter iflyTekListenerAdapter(RedisReceiver redisReceiver) {
	MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(redisReceiver, "onIflyTekMessage");
	return messageListenerAdapter;
}
}

3
编写处理订阅消息的类

package org.jeecg.common.modules.redis.listener;

public interface IFlyTekRedisListener {
    void onIFlyTekMessage(String message);
}


package org.jeecg.common.modules.redis.receiver;


import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.jeecg.common.base.BaseMap;
import org.jeecg.common.constant.GlobalConstants;
import org.jeecg.common.modules.redis.listener.IFlyTekRedisListener;
import org.jeecg.common.modules.redis.listener.JeecgRedisListerer;
import org.jeecg.common.util.SpringContextHolder;
import org.springframework.stereotype.Component;

/**
 * @author zyf
 */
@Component
@Data
@Log4j2
public class RedisReceiver {


    /**
     * 接受消息并调用业务逻辑处理器
     *
     * @param params
     */
    public void onMessage(BaseMap params) {
        Object handlerName = params.get(GlobalConstants.HANDLER_NAME);
        JeecgRedisListerer messageListener = SpringContextHolder.getHandler(handlerName.toString(), JeecgRedisListerer.class);
        if (ObjectUtil.isNotEmpty(messageListener)) {
            messageListener.onMessage(params);
        }
    }
    /**
     * 接受语音通道消息逻辑处理器    从applicationcontext中获得类型iFlyTekVoiceHandler bean,调用其onIFlyTekMessage方法,这里是为了解耦
     *
     */
    public void onIflyTekMessage(String message) {
        log.info("message"+message);
        IFlyTekRedisListener iFlyTekVoiceHandler = SpringContextHolder.getHandler("iFlyTekVoiceHandler", IFlyTekRedisListener.class);
        System.out.println("ifly"+iFlyTekVoiceHandler);
        iFlyTekVoiceHandler.onIFlyTekMessage(message);
    }

}

4.修改websocket 主要是onmessage
//将第三方发过来的消息转发至redis 发布订阅通道上

    @OnMessage
    public void onMessage(String message) {
        this.sendMessage(message);
    }

    private void sendMessage(String message) {
        RedisTemplate redisTemplate = (RedisTemplate) SpringContextUtils.getBean("redisTemplate");
        redisTemplate.convertAndSend(IFLYTEK_TOPIC_NAME, message);
    }

    @NotNull
    public IFlytekVoiceVO getiFlytekVoiceVO(String message) {
        String msg= (String)JSON.parse(message);
        System.out.println("msg"+msg);
        IFlytekVoiceDTO iFlytekVoice = JSON.parseObject(msg, IFlytekVoiceDTO.class);
        log.info("【iflytek】收到客户端消息:" + iFlytekVoice.toString());
        LambdaQueryWrapper<SysBaseDict> sysBaseDictLambdaQueryWrapper = new LambdaQueryWrapper<>();
        sysBaseDictLambdaQueryWrapper.eq(SysBaseDict::getDictCode, "iflytek");
        ISysBaseDictService sysBaseDictService = (ISysBaseDictService) SpringContextUtils.getBean("sysBaseDictServiceImpl");
        SysBaseDict sysBaseDict = sysBaseDictService.getOne(sysBaseDictLambdaQueryWrapper);
        LambdaQueryWrapper<SysBaseDict> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysBaseDict::getDictId, sysBaseDict.getId());
        queryWrapper.eq(SysBaseDict::getItemCode, iFlytekVoice.getCmd());
        SysBaseDict sysBaseDict1 = sysBaseDictService.getOne(queryWrapper);
        IFlytekVoiceVO iFlytekVoiceVO;
        if ("-10001".equals(iFlytekVoice.getCmd())) {
            iFlytekVoiceVO = new IFlytekVoiceVO(500, "录音错误");
        } else {
            iFlytekVoiceVO = new IFlytekVoiceVO(200, sysBaseDict1.getItemValue());
        }
        return iFlytekVoiceVO;
    }

}
 iFlyTekVoiceHandler 实现IFlyTekRedisListener ,是的可以被RedisReceiver 调用
package org.jeecg.modules.message.websocket;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.log4j.Log4j2;
import org.jeecg.common.modules.redis.listener.IFlyTekRedisListener;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.modules.system.entity.SysBaseDict;
import org.jeecg.modules.system.service.ISysBaseDictService;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component(value = "iFlyTekVoiceHandler")
@Log4j2
public class IFlyTekVoiceHandler implements IFlyTekRedisListener {
    @Resource
    private IFlytekVoiceSocket iFlytekVoiceSocket;
    @Override
    public void onIFlyTekMessage(String message) {
        try {
            IFlytekVoiceVO iFlytekVoiceVO = iFlytekVoiceSocket.getiFlytekVoiceVO(message);
            IFlytekVoiceSocket.sessionPool.forEach((k, v) -> {
                v.getAsyncRemote().sendText(iFlytekVoiceVO.toString());
            });
        } catch (Exception e) {
            e.printStackTrace();
            IFlytekVoiceSocket.sessionPool.forEach((k, v) -> {
                v.getAsyncRemote().sendText(new IFlytekVoiceVO(500, "服务器错误").toString());
            });
        }
    }


}

其他modle类

package org.jeecg.modules.message.websocket;

public class IFlytekVoiceVO {
    private Integer code;
    private String msg;

    public IFlytekVoiceVO(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                '}';
    }
}




package org.jeecg.modules.message.websocket;

public class IFlytekVoiceDTO {
    private String cmd;
    private String content;

    public String getCmd() {
        return cmd;
    }

    public void setCmd(String cmd) {
        this.cmd = cmd;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "IFlytekVoiceDTO{" +
                "cmd='" + cmd + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值