websocket使用案例

发布时间:2022-02-26 06:49:41
修改时间:2022-02-26 06:49:41
总阅读数:1077
今日阅读数:0
昨日日阅读数:0
字数:12157

websocket使用案例

[TOC]

 

实现效果

http://blog.qianchen.link/clipboard

设备A生成二维码,设备B扫描设备A的二维码后,可将设备A和设备B的输入框内容实时同步

原理

前端向后端获取剪切板标识和二维码(用于其他设备连接到同一剪切板),当输入框内容改变时发送消息到服务器,当收到服务器发送的消息时将内容赋值到输入框

后端将连接以<剪切板标识,<设备标识,连接Session>>的格式保存为静态变量,当收到客户端发送的消息时将消息发送到同一剪切板标识的设备

前端部分(nuxt)

前端部分使用nuxt(vue),其他框架代码也是类似,可根据情况自行修改

<template>
  <div>
      <!-- {{code}},{{baseUrl}} -->
      <div  style="width: 90%;margin: 0 auto;">
            <h1>websocket剪切板</h1>

      <div >
            <Input v-model="text" type="textarea" show-word-limit autofocus :autosize="{minRows: 2}" placeholder="请输入内容..." @on-change="textChange"></Input>
      </div>
     

    <div v-if="img">
        <h3>扫码二维码即可建立同步剪切板</h3>
         <img  :src="img" alt="">
    </div>
       
      </div>
    
      <!-- <Button></Button> -->
  </div>
</template>

<script>
export default {
    data(){
        return{
            text:'',
            socket:null,
             isOpen:false,
             code:null,
             img:null,
            path:'ws://blog.qianchen.link/api/clipboard/',
            baseUrl:null,
        }
    },
    methods:{
          init() {

            if(typeof(WebSocket) === "undefined"){
                alert("您的浏览器不支持socket")
            }else{
                // 实例化socket
                let date=new Date()
                
            //加入时间用于区分不同设备
                this.socket = new WebSocket(this.path+this.code+","+date.getMinutes()+date.getSeconds())

                // this.socket.data
                // 监听socket连接
                this.socket.onopen = this.open
                // 监听socket错误信息
                this.socket.onerror = this.error
                // 监听socket消息
                this.socket.onmessage = this.onmessage
            }
        },
          open: function () {
            console.log("socket连接成功")
            this.isOpen=true;
            this.$Message.success("socket连接成功");
        },
        error: function () {
            console.log("连接错误")
            this.isOpen=false
            this.$Message.error("socket连接失败");
        },
        onmessage: function (msg) {
              console.log(msg)
             this.text=msg.data
                
        },
         send: function () {
              this.socket.send(JSON.stringify({
                code:this.code,
                text:this.text
            }))
         },
           close: function () {
            console.log("socket已经关闭")
             this.$Message.info("socket连接已关闭");
            this.isOpen=false
        },
        textChange(event){
            console.log(event)
            this.send()
        }
    },
    mounted(){
        if (process.client) {


            console.log(document.location.origin+document.location.pathname)
            if (this.$route.query.code) {
                //如果从URL参数中获取到code则代表是扫码连接,直接使用扫描到的参数连接剪切板
                this.code=this.$route.query.code
                this.init()
            }else{
                // console.log(document.location.host)
              //URL中没有参数则向服务器获取code(剪切板标识)和二维码(供其他设备连接到同一剪切板用)
                this.$axios.get('/clipboard/code?url='+document.location.origin+document.location.pathname).then((res=>{
                this.code=res.data.data.code
                this.img=res.data.data.img
                console.log(res)
                console.log(this.code)
                this.init()
            }))
            }
            
        }
    },
     destroyed () {
        // 销毁监听
           
               if ( this.socket) {
                   this.socket.onclose = this.close
                    this.$Message.info("socket连接已关闭");
               }
            
       
    }
}
</script>

<style>

</style>

后端部分(springboot)

添加依赖

<!--        spring的websocket依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>

<!--        生成二维码-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.0</version>
</dependency>

配置类(SpringWebSocketConfig.java)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class SpringWebSocketConfig {

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

}

消息对象类(ClipboardTO.java)

package com.qc.blog.to;

import lombok.Data;

/**
 * @author zzq
 */
@Data
public class ClipboardTO {

    String text;
    String code;

}

websocket处理类(ClipboardSocketServer.java)——核心部分

package com.qc.blog.socket;

import com.alibaba.fastjson.JSON;
import com.qc.blog.to.ClipboardTO;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author zzq
 */
@ServerEndpoint("/clipboard/{code}")
@Component
public class ClipboardSocketServer {

    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static AtomicInteger onlineNum = new AtomicInteger();


    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
     * <剪切板标识,<设备标识,连接Session>>
     */
    private static ConcurrentHashMap<String, Map<String,Session>> sessionPools = new ConcurrentHashMap<>();

    /**
     * 建立连接成功调用
     * @param session
     * @param code
     */
    @OnOpen
    public void onOpen(Session session,@PathParam(value = "code") String code){
        String[] fields = code.split(",");
        Map<String, Session> stringSessionMap = sessionPools.get(fields[0]);
        if (stringSessionMap == null) {
//            如果此标识的剪切板不存在,则初始化剪切板并将表示加入map中
            stringSessionMap=new HashMap<>();
        }
//       将自己加入对应的剪切板
        stringSessionMap.put(fields[1],session);
        sessionPools.put(fields[0],stringSessionMap);
        System.out.println(code + "加入webSocket!当前人数为" + onlineNum);

    }

    /**
     * 关闭连接时调用
     * @param code
     */
    @OnClose
    public void onClose(@PathParam(value = "code") String code){
        String[] fields = code.split(",");
        Map<String, Session> stringSessionMap = sessionPools.get(fields[0]);
        stringSessionMap.remove(fields[1]);
        if (stringSessionMap.size()==0){
//            如果自己是最后一个连接,则将剪切板删除
            sessionPools.remove(fields[0]);
        }
        System.out.println(code + "断开webSocket连接!当前人数为" + onlineNum);
    }


    /**
     * 收到客户端消息时调用
     * @param message
     * @throws IOException
     */
    @OnMessage
    public void onMessage(String message) throws IOException{
        System.out.println("server get" + message);
        ClipboardTO clipboardTO=JSON.parseObject(message, ClipboardTO.class);

        sendText(clipboardTO.getCode(), clipboardTO.getText());
    }

    /**
     * 获取剪切板标识,然后将消息发送到该剪切板的全部设备
     * @param code
     * @param text
     * @throws IOException
     */
    private void sendText(String code, String text) throws IOException {
        String[] fields = code.split(",");
        Map<String, Session> sessionMap = sessionPools.get(fields[0]);
        if (sessionMap != null) {
            sessionMap.values().forEach(session -> {
                try {
//                    发送消息到客户端
                    session.getBasicRemote().sendText(text);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }

    }


    /**
     * 错误时调用
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable){
        System.out.println("发生错误");
        throwable.printStackTrace();
    }


}

获取连接初始化信息对象(ClipboardVo.java)

package com.qc.blog.vo;

import lombok.Data;

/**
 * @author zzq
 */
@Data
public class ClipboardVo {

    /**
     * 剪切板标识(区分不同用户使用的剪切板)
     */
    private String code;
    /**
     * 手机端连接时的二维码
     */
    private String img;
}

ClipboardController(ClipboardController.java)

IdGeneratorSnowflake为UUID生成类,可自行更换(如java自带UUID)

package com.qc.blog.controller;

import com.qc.blog.utils.IdGeneratorSnowflake;
import com.qc.blog.utils.QrCodeUtils;
import com.qc.blog.utils.Res;
import com.qc.blog.vo.ClipboardVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author zzq
 */
@RestController
@RequestMapping("clipboard")
@CrossOrigin
public class ClipboardController {

    
    @Autowired
    private IdGeneratorSnowflake idGeneratorSnowflake;

    @GetMapping("code")
    public Res getCode(String url){
        ClipboardVo clipboardVo = new ClipboardVo();
        long code = idGeneratorSnowflake.snowflakeId();
        clipboardVo.setCode(String.valueOf(code));
        String img = QrCodeUtils.createCode(url + "?code=" + code);
        clipboardVo.setImg(img);
        return Res.data(clipboardVo);
    }


}

二维码生成工具类(QrCodeUtils.java)

package com.qc.blog.utils;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.springframework.util.Base64Utils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;

/**
 * @author zzq
 */
public class QrCodeUtils {

	public QrCodeUtils() {

	}

	public static String createCode(String content){
		int width=300;
		int height=300;
		String format="png";

		HashMap hashMap=new HashMap();

		hashMap.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hashMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
		hashMap.put(EncodeHintType.MARGIN, 2);
		BitMatrix bitMatrix=null;

		try {
			bitMatrix= new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE , width, height,hashMap);


		} catch (WriterException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
			}
		}


		ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
		try {
			ImageIO.write(image, "png", byteArrayOutputStream);
		} catch (IOException e) {
			e.printStackTrace();
		}

		String img="data:image/png;base64,"+ Base64Utils.encodeToString(byteArrayOutputStream.toByteArray());

		return img;
	}

	
	
	
}