websocket学习和使用小记

websocket

前言:

最近因为一个项目需要:采用websocket进行数据传输,数字大屏上信息进行实时显示。就最近对websocket的学习和使用做记录。

修行之路艰辛,与君共勉。

1.websocket基础

首先是老生常谈的websocket相关的基础知识。

1.1 基础概念

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。

——百度百科

对于一些需要实时数据刷新的应用场景,采用原始的post/get请求,然后在使用ajax进行轮询是能够实现上诉需求的。但是采用轮询的方式会极大的增加服务器的负载。并且会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上。

所有针对上诉问题,websocket横空出世,几乎完美的解决了上诉问题。

websocket的核心功能:即时通讯,替代轮询

1.2 原理

WebSocket同HTTP一样也是应用层的协议,但是它是一种****双向通信协议****,是建立在TCP之上的。

连接过程 —— 握手过程

  • 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
  • TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(****开始前的HTTP握手****)
  • 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
  • 当收到了连接成功的消息后,通过TCP通道进行传输通信。

2.基础应用

websocket的基础应用:聊天室的实现

技术实现:websocket+Spring Boot

2.1 添加对应的依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2.2 创建websocket的配置文件

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

2.3 创建对应的websocket服务

@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {

    // 统计在线人数
    private static int onlineCount = 0;

    // 用本地线程保存session
    private static ThreadLocal<Session> sessions = new ThreadLocal<Session>();

    // 保存所有连接上的session
    private static Map<String, Session> sessionMap = new ConcurrentHashMap<String, Session>();

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        onlineCount--;
    }

    // 连接
    @OnOpen
    public void onOpen(Session session) {
        sessions.set(session);
        addOnlineCount();
        sessionMap.put(session.getId(), session);
        System.out.println("【" + session.getId() + "】连接上服务器======当前在线人数【" + getOnlineCount() + "】");
    }

    // 关闭连接
    @OnClose
    public void onClose(Session session) {
        subOnlineCount();
        sessionMap.remove(session.getId());
        System.out.println("【" + session.getId() + "】退出了连接======当前在线人数【" + getOnlineCount() + "】");
    }

    // 接收消息 客户端发送过来的消息
    @OnMessage
    public void onMessage(String message, Session session) {

        //message的格式可以包含sessionid, 格式[SID,内容XXX]:1,内容
        System.out.println("【" + session.getId() + "】客户端的发送消息======内容【" + message + "】");
        String[] split = message.split(",");
        String sessionId = split[0];
        Session ss = sessionMap.get(sessionId);
        if (ss != null) {
            String msgTo = "【" + session.getId() + "】发送给【您】的消息:\n【" + split[1] + "】";
            String msgMe = "【我】发送消息给【" + ss.getId() + "】:\n" + split[1];
            sendMsg(ss, msgTo);
            sendMsg(session, msgMe);
        } else {
            for (Session s : sessionMap.values()) {
                if (!s.getId().equals(session.getId())) {
                    sendMsg(s, "【" + session.getId() + "】发送给【您】的广播消息:\n【" + message + "】");
                } else {
                    sendMsg(session, "【我】发送广播消息给大家\n" + message);
                }
            }
        }
    }

// 异常

    @OnError
    public void onError(Session session, Throwable throwable) {
        System.out.println("发生异常!");
        throwable.printStackTrace();
    }

    // 发送消息到指定用户的方法
    public synchronized void sendMsg(Session session, String msg) {
        try {
            session.getBasicRemote().sendText(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //广播发送消息
    public synchronized void sendMsgToAll(String msg) {
        try {
            for (Session s : sessionMap.values()) {
                s.getBasicRemote().sendText(msg);
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

2.4 创建对应的controller,用于数据发送

@Controller
public class MyController {
    @Autowired
    private WebSocketServer webSocketServer;

    @RequestMapping("/send.do")
    public ModelAndView send(){
        ModelAndView modelAndView = new ModelAndView("/send.html");
        webSocketServer.sendMsgToAll("Hello World");
        return modelAndView;
    }
}

2.5 前台采用对应的js代码进行连接

var ws = new WebSocket("你的socket地址");

ws.onopen = function(evt) {  //绑定连接事件
  console.log("Connection open ...");
  ws.send("发送的数据");
};

ws.onmessage = function(evt) {//绑定收到消息事件
  console.log( "Received Message: " + evt.data);
};

ws.onclose = function(evt) { //绑定关闭或断开连接事件
  console.log("Connection closed.");
};

3.需求应用

核心来了。如何使用websocket完成我的需求:数字大屏的数据实时显示。

首先websocket的工作是建立连接,推送数据。但是数据来源就需要我们的另外一模块:定时任务(qrtz_job)

设计思想

在服务端创建一个websocket服务。然后客户端进行连接。

1.客户端发起连接请求,服务端接收到连接请求后,解析session,获取对应的sessionId,token(权限控制),API的具体执行参数。

2.服务端根据解析出来的参数,创建对应的定时任务,并且将session、定时任务、API3者的一对一关系存放在数据表中。

3.定时任务创建完成后,启动定时任务,在定时任务的执行函数中,获取到该定时任务的详细信息。包括API的执行信息,session等。

3.根据API的执行信息(SQL语句,数据源类型等),执行该API,获取对应结果,然后调用socket中的特定推送方法,根据sessionId特定推送给客户端。根据时间间隔,定时执行该方法,实现动态推送数据的效果

注:API的调用执行,放在下一次在详细记录。

4.最后,客户端发起断开连接,服务端接收到断开连接请求,根据对应的sessionId,通过数据表查询到对应的定时任务,停止任务,最后删除任务,并且记录连接/断开信息。

3.2 核心源码

核心的websocket源码

public class ExecSocket implements WebSocketHandler {

    private static final Logger logger;

    @Autowired
    private SysWebsocketService sysWebsocketService;

    private static final Set<WebSocketSession> sessions;
    static {
        sessions = new ConcurrentHashSet<>();
        logger = LoggerFactory.getLogger(ExecSocket.class);
    }

    //建立websocket连接时触发
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("建立连接");
        //创建对应的定时任务,解析session,获取对应的socketId,以及输入参数params
        //解析session,获取参数,调用方法,创建并启动定时器
        String url=session.getUri().toString();
        Map<String, String> map=new HashMap<>();
        map.put("sessionId",session.getId());
        //String[]
        String[] str=url.split("&");
        for(int i=0;i<str.length;i++){
            String[] str1=str[i].split("=");
            if(str1.length>=2){
                map.put(str1[0],str1[1]);
            }
        }
        //该方法就是创建定时任务,并且启动定时任务
        sysWebsocketService.startWebSocket(map);
        //保存定时器和session一一对应的关系。
        sessions.add(session);
    }

    //接收js侧发送来的用户信息
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> socketMessage) throws Exception {
        String message = socketMessage.getPayload().toString();
        System.out.println(message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        if(session.isOpen()){
            session.close();
        }
        logger.debug("websocket connection closed......");
        sessions.remove(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        logger.debug("websocket connection closed......");
        //关闭连接,停止/删除定时任务
        sysWebsocketService.stopWebSocket(session.getId());
        sessions.remove(session);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
    /**
     * 给所用户发送消息
     *
     * @param message
     */
    public void sendMessageToAllUsers( String message) {
        for (WebSocketSession user : sessions) {
            try {
                if (user.isOpen()) {
                    user.sendMessage(new TextMessage(message));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 给特定用户发送消息,并且通过session和定时任务一一对应
     * 根据不同任务,将数据分发给不同的session
     * @param message
     * @param sessionId
     */
    public void sendMessageToSpecificUser(String message,String sessionId){
        WebSocketSession session = null;
        for (WebSocketSession user:sessions){
            if(user.getId().equals(sessionId)){
                session=user;

            }
        }
        try {
            if(session.isOpen()){
                session.sendMessage(new TextMessage(message));
            }
        }catch (IOException e){
            e.printStackTrace();
        }


    }
}

定时任务的核心执行代码

@Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Map<String,Object> data = (Map<String,Object>)jobExecutionContext.getJobDetail().getJobDataMap().get("data");
          System.out.println(data);
        //获取对应API信息,执行,返回结果
        String sysApiId=(String)data.get("apiId");
        SysApi sysApi=sysApiService.queryById(sysApiId);
        DataSource dataSource=dataSourceService.get(sysApi.getDsId());
        String sql=(String)data.get("sql");
        //该方法为核心的API执行方法
        Map<String,Object> ret=apiExecService.doExec(dataSource.getEnName(),sql,sysApi.getType());
        System.out.println(ret);
        String sessionId=(String)data.get("sessionId");
        //调用推送给特定用户的方法
        execSocket.sendMessageToSpecificUser(ret.toString(),sessionId);
        //推送给所有用户
        //execSocket.sendMessageToAllUsers(ret.toString());
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值