Java使用WebStocket实现前后端互发消息

Posted 苏柠沅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java使用WebStocket实现前后端互发消息相关的知识,希望对你有一定的参考价值。

记录一下自己使用WebStocket实现服务器主动发消息的过程和踩得雷。

需求:车牌识别系统识别到车牌后,持续向前端推送车牌信息,直到前端回复收到。

测试需求:新增 客户后,持续向前端推送客户信息,直到前端收到消息,并且回复收到。

1.引入WebStocket的依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
	<version>2.7.0</version>
</dependency>

2.创建配置类 WebScoketConfig

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

/**
 * 开启WebSocket支持
 */
@Configuration
public class WebSocketConfig 
    @Bean
    public ServerEndpointExporter serverEndpointExporter() 
        return new ServerEndpointExporter();
    

新增客户的业务层

  这里实现了新增 客户后,持续向前端推送客户信息。

  实现思路:本来是打算 让前端接收到客户信息,回复后端的时候,后端修改数据库中此条客户的 接收状态的字段,然后每次后端往前端发送消息的时候都去数据库查询一次 客户信息的接收状态,如果已经接收到了就不往前端推送。但是好像会造成一边读数据库,一边修改数据库,会出现脏读的问题,而且我在 WebScoketConfigServer 中并不能创建Service层的对象,总是报空指针。

  最后,决定使用 static修饰的静态变量来实现对前端是否接受到消息和是否发送的是同一条重复的消息进行判断。然后根据返回的结果决定是否继续往前端推送消息。

import io.recycle.modules.rest.api.dto.system.CustomerDto;
import io.recycle.modules.rest.api.dto.system.CustomerQueryDto;
import io.recycle.modules.rest.api.dto.weigh.CardDto;
import io.recycle.modules.rest.api.dto.weigh.CustomerParam;
import io.recycle.modules.rest.api.dto.weigh.CustomerWeighDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

import io.recycle.modules.rest.api.dao.RecycleCustomerDao;
import io.recycle.modules.rest.api.entity.RecycleCustomerEntity;
import io.recycle.modules.rest.api.service.RecycleCustomerService;



@Service("recycleCustomerService")
public class RecycleCustomerServiceImpl implements RecycleCustomerService

	private static int count=0;
	@Autowired
	private NoticeWebsocket noticeWebsocket;
	@Autowired
	private RecycleCustomerDao recycleCustomerDao;

	@Override
	public void save(RecycleCustomerEntity recycleCustomer)
		recycleCustomerDao.save(recycleCustomer);
		//测试webstocket,实现新增客户往前端推送消息,直到前端回复
//		//测试webstocket
		boolean flag = false;
		do
			try 
				Thread.sleep(300);    //休息300毫秒
			 catch (InterruptedException e) 
				e.printStackTrace();
				System.out.println("休息时出错000000");
			
			//往前端发送消息
			System.out.println("count===="+count);
			boolean resultFlag = noticeWebsocket.sendMessage("新增了用户:" + recycleCustomer.toString(),count);

			flag = resultFlag;

			if (resultFlag)
				System.out.println("停止往前端发送数据,因为 resultFlag 为: "+resultFlag+"==说明前端已接收的消息");
			else 
				System.out.println("往前端发送数据,因为 resultFlag 为: "+resultFlag+"==说明前端还没接收到消息");
			


		while ( !flag );
		System.out.println("停止往前端发送数据,因为 delFlag 为: "+flag);
		count = count +1;


	

3.创建WebScoketConfigServer

在websocket协议下,后端服务器相当于ws里的客户端,需要用@ServerEndpoint指定访问的路径,并使用@Component注入容器。

这里实现了新增 客户后,持续向前端推送客户信息,直到前端收到消息,并且回复收到。

实现思路:

import com.alibaba.fastjson.JSONObject;
import io.recycle.modules.rest.api.dao.RecycleCustomerDao;
import io.recycle.modules.rest.api.dto.websocket.NoticeWebsocketResp;
import io.recycle.modules.rest.api.entity.RecycleCustomerEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint("/chepaisend")
@Component
@Slf4j
public class NoticeWebsocket 

    //记录连接的客户端
    public static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * userId关联sid(解决同一用户id,在多个web端连接的问题)
     */
    public static Map<String, Set<String>> conns = new ConcurrentHashMap<>();

    private String sid = null;




    //一些记录发送消息状态
    private static int initFlag =0;

    private static int tempFlag =0;
    //区分新旧消息的变量
    private static int sum=0;
    /**
     * 连接成功后调用的方法
     * @param session
     *
     */
    @OnOpen
    public void onOpen(Session session) 
        this.sid = UUID.randomUUID().toString();

        clients.put(this.sid, session);

        log.info(this.sid + "连接开启!");
    

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() 
        log.info(this.sid + "连接断开!");
        clients.remove(this.sid);
    

    /**
     * 判断是否连接的方法
     * @return
     */
    public static boolean isServerClose() 
        if (NoticeWebsocket.clients.values().size() == 0) 
            log.info("已断开");
            return true;
        else 
            log.info("已连接");
            return false;
        
    

    /**
     * 发送给所有用户
     * @param noticeType
     */

    public static boolean sendMessage(String noticeType,int count)
        //判断是否是新的新增客户
        System.out.println("count= "+count+",sum= "+sum+",initFlag= "+initFlag+",tempFlag= "+tempFlag);
        if (sum != count)
            NoticeWebsocketResp noticeWebsocketResp = new NoticeWebsocketResp();
            noticeWebsocketResp.setNoticeType(noticeType);
            sendMessage(noticeWebsocketResp);
            sum = count;
        
        //判断前端是否 回复了 收到消息  相等没收到,不相等 收到
        if (initFlag==tempFlag)
            NoticeWebsocketResp noticeWebsocketResp = new NoticeWebsocketResp();
            noticeWebsocketResp.setNoticeType(noticeType);
            sendMessage(noticeWebsocketResp);
        else 
            //收到消息了,别发同一个消息了
            tempFlag = initFlag;
            return true;
        
        tempFlag = initFlag;
        //没收到消息继续发
        return false;
    


    /**
     * 发送给所有用户
     * @param noticeWebsocketResp
     */
    public static void sendMessage(NoticeWebsocketResp noticeWebsocketResp)
        String message = JSONObject.toJSONString(noticeWebsocketResp);
        for (Session session1 : NoticeWebsocket.clients.values()) 
            try 
                session1.getBasicRemote().sendText(message);
             catch (IOException e) 
                e.printStackTrace();
            
        
    

    /**
     * 根据用户id发送给某一个用户
     * **/
    public static void sendMessageByUserId(String userId, NoticeWebsocketResp noticeWebsocketResp) 
        if (!StringUtils.isEmpty(userId)) 
            String message = JSONObject.toJSONString(noticeWebsocketResp);
            Set<String> clientSet = conns.get(userId);
            if (clientSet != null) 
                Iterator<String> iterator = clientSet.iterator();
                while (iterator.hasNext()) 
                    String sid = iterator.next();
                    Session session = clients.get(sid);
                    if (session != null) 
                        try 
                            session.getBasicRemote().sendText(message);
                         catch (IOException e) 
                            e.printStackTrace();
                        
                    
                
            
        
    


    /**
     * 收到客户端消息后调用的方法
     * @param message
     * @param session
     */

    @OnMessage
    public void onMessage(String message, Session session) 
        log.info("收到来自窗口"+"的信息:"+message);
        if ("已接收到消息".equals(message))
            //收到消息,改变flag的值
            System.out.println("前端已经收到消息,开始改变 initFlag的值,此时initFlag= "+initFlag);
            initFlag = initFlag +1;
            System.out.println("前端已经收到消息,已经改变 initFlag的值,此时initFlag== "+initFlag);
        

    

    /**
     * 发生错误时的回调函数
     * @param error
     */
    @OnError
    public void onError(Throwable error) 
        log.info("错误");
        error.printStackTrace();
    


封装的发送消息的对象

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel("ws通知返回对象")
public class NoticeWebsocketResp<T> 

    @ApiModelProperty(value = "通知类型")
    private String noticeType;

    @ApiModelProperty(value = "通知内容")
    private T noticeInfo;

4.WebSocket调用

用户端调用此接口,主动将消息发送给后端,后端接收到消息后再主动推送给指定/全部用户,可以实现消息的私聊和群发功能。

import io.recycle.common.utils.R;
import io.recycle.modules.rest.api.service.impl.NoticeWebsocket;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController 
	@GetMapping("/test")
    public R test() 
    	NoticeWebsocket.sendMessage("你好,WebSocket",1);
        return R.ok();
    

前端WebSocket连接

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SseEmitter</title>
</head>
<body>
<div id="message"></div>
</body>
<script>
    var limitConnect = 0;
    init();
    function init() 
        //开启webstocket服务的ip地址  ws:// + ip地址 + 访问路径
        var ws = new WebSocket('ws://127.0.0.1:8191/double-win/chepaisend');
// 获取连接状态
        console.log('ws连接状态:' + ws.readyState);
//监听是否连接成功
        ws.onopen = function () 
            console.log('ws连接状态:' + ws.readyState);
            limitConnect = 0;
            //连接成功则发送一个数据
            ws.send('我们建立连接啦');
        
// 接听服务器发回的信息并处理展示
        ws.onmessage = function (data) 
            console.log('接收到来自服务器的消息:');
            console.log(data);
            //接收到 消息后给后端发送的 确认收到消息,后端接收到后 不再重复发消息
            ws.send('已接收到消息');
            //完成通信后关闭WebSocket连接
            // ws.close();
        
// 监听连接关闭事件
        ws.onclose = function () 
            // 监听整个过程中websocket的状态
            console.log('ws连接状态:' + ws.readyState);
            reconnect();

        
// 监听并处理error事件
        ws.onerror = function (error) 
            console.log(error);
        
    
    function reconnect() 
        limitConnect ++;
        console.log("重连第" + limitConnect + "次");
        setTimeout(function()
            init();
        ,2000);

    
</script>
</html>

项目启动后,打开写好的前端页面后控制台打印连接信息

 新增客户后,前端接收到,并回复收到了

  新增客户后,前端接收到,并回复收到了,后端停止推送

前端接收到,但是骗后端没收到,或者说后端不知道 前端已经接收到消息。 

后端展示,后端一直往前端推送

 

Python3 & 基于TCP的方式实现客户端与服务器端互发消息

在这里插入图片描述

客户端

'''
1、导⼊ socket 模块
2、创建socket 套接字
3、建⽴tcp连接(和服务端建⽴连接)
4、开始发送数据(到服务端)
5、接收服务端发送的信息
6、关闭套接字
'''

# 1、导⼊ socket 模块
from socket import *


# 2、创建socket 套接字
tcp_socket_client = socket(AF_INET,SOCK_STREAM)
# 3、建⽴tcp连接(和服务端建⽴连接)
tcp_socket_client.connect(("192.168.100.117",8787))

while True:
   #键盘输入要发送的内容
   msg = input("请输入要发送的内容:").strip()
   #判断发送的内容是否为空,为空则跳过
   if len(msg)==0:
       continue
    # 判断发送的内容是否为EXIT,是则退出客户端
   elif msg == 'exit':
       break
   else:
       # 4、发送数据(到服务端)
       tcp_socket_client.sendall(msg.encode("gbk"))
       # 5、接收服务端发送的信息
       recv_data = tcp_socket_client.recv(1024).decode("gbk")
       print("收到服务端回复:%s" % recv_data)


# 6、关闭套接字
tcp_socket_client.close()

服务器:

'''
1. 导⼊ socket 模块
2 socket创建⼀个套接字
3. bind绑定ip和port
4. listen使套接字变为可以被动链接
5. accept等待客户端的链接
6. recv/send接收发送数据
7. 发送数据到客户端
8. 中止与当前客户端的连接
9. 关闭服务端套接字
'''

# 1. 导⼊ socket 模块
from socket import *

# 2. socket创建⼀个套接字
tcp_socket_server = socket(AF_INET,SOCK_STREAM)
# 3. bind绑定ip和port
tcp_socket_server.bind(('',8787))
# 4. listen使套接字变为可以被动链接
tcp_socket_server.listen(128)

# 循环目的:调用多次accept,从而为多个客户端服务
while True:
   # 5. accept等待客户端的链接
   socket_client,ip_port = tcp_socket_server.accept()
   print("新的客户端来了:", ip_port)
   
   # 循环目的: 为同一个客户端 服务多次
   while True:
       # 6. recv接收客户端发送的数据
       recv_data = socket_client.recv(1024)
       #判断接收到客户端的信息是否为空,为空则退出
       if not recv_data:
           print("客户端连接断开")
           break
       else:
           print("收到客户端信息:%s" % recv_data.decode("gbk"))

       #从键盘输入回复给客户端的内容
       msg = input("请输入回复内容:").strip()
       # 判断发送的内容是否为空,为空则跳过
       if len(msg)==0:
           continue
       # 7. 发送数据到客户端
       socket_client.sendall(msg.encode("gbk"))

   # 8. 中止与当前客户端的连接
   socket_client.close()
# 9. 关闭服务端套接字
tcp_socket_server.close()

在这里插入图片描述
在这里插入图片描述

看完这篇内容后,相信以下两件事,也会对你的个人提升有所帮助:==

1、 点赞,让更多人能看到这篇文章,同时你的认可也会鼓励我创作更多优质内容。

2、 让自己变得更强:想一想,如果你想在测试这个行业一直做下去,你的经验和测试技术是远远不够的,你需要进阶,你需要丰富你的技术栈!还等什么!

最后:【可能给予你助力的教程】


这些资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。

关注我的微信公众号:【伤心的辣条】免费获取~

我的学习交流群:902061117 群里有技术大牛一起交流分享~

码字不易,硬核码字更难,希望大家不要吝啬自己的鼓励,给我 : “点赞” “评论” “收藏” 一键三连哦!

好文推荐:

35岁之后软件测试工程师靠什么养家?我能继续做测试!

App公共测试用例梳理

Python简单?先来40道基础面试题测试下

阿里二黑叹息:越来越多的年轻人从职场撤退了?

从一名开发人员转做测试的一些感悟

以上是关于Java使用WebStocket实现前后端互发消息的主要内容,如果未能解决你的问题,请参考以下文章

前后端互相传值

通过GUI制作一个简单的消息对话框互发消息

Java 服务端和客户端的通信

0. Restful前后端通讯规范参考实现

Java企业工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

RTX51 Tiny