用WebSocket实现后端消息实时推送到前端

1.前端调用WebSocket server代码:

    //调用WebSocket server
	batchSeting() {
	      this.openStatue = 1;
	      const serve = window.location.hostname;
	      this.websock = new WebSocket(`ws://${serve}:8088/examService`);
	      this.websock.onmessage = this.websocketonmessage;
	      this.websock.onerror = this.websocketonerror;
	      this.websock.onopen = this.websocketonopen;
	      this.websock.onclose = this.websocketclose;
	    },
	    websocketonopen() { // 连接建立之后执行send方法发送数据
	      const data = {
	        pointCode: this.pointCodes.join(','),
	        key: this.openStatue // 0 关 1 开
	      };
	      console.log(data);
	      this.websocketsend(JSON.stringify(data));
	    },
	    websocketonerror() {
	      console.log('WebSocket连接失败');
	    },
	    // 数据接收
	    websocketonmessage(e) { 
	      const data = e.data.length > 0 ? JSON.parse(e.data) : {};
	      this.remindTime = data.remindTime;
	      this.completeNum = data.completeNum;
	      this.pointExam(data.pointCode);
	      if (this.completeNum === this.pointCodes.length) { /** 已经完成体检 */
	        this.dialogVisible = false;
	        this.websock.close();// 关闭websocket
	      }
	    }

2.后端 netty服务类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
@Component
public class ExamTCPServer {

	//@Value(value="${NETTY_PORT}")
	private static int port = 8088;
	
	private static final Logger logger = LoggerFactory.getLogger(ExamTCPServer.class);
	
	private static int maxFrameLength = 1024 * 10;
	
	public void run() throws Exception {
		NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
		//默认核心个数的2倍
		NioEventLoopGroup workLoopGroup = new NioEventLoopGroup();
		
		try {
			ServerBootstrap serverBootstrap = new ServerBootstrap()
				.group(bossLoopGroup, workLoopGroup)
				.channel(NioServerSocketChannel.class)
				.childOption(ChannelOption.TCP_NODELAY, true)
				.option(ChannelOption.SO_BACKLOG, 16)
				.childOption(ChannelOption.SO_KEEPALIVE, true)
				.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ChannelPipeline pipeline = ch.pipeline();
							//基于http协议,使用http的编码和解码器
		                    pipeline.addLast(new HttpServerCodec());
		                    pipeline.addLast(new ChunkedWriteHandler());
		                    pipeline.addLast(new HttpObjectAggregator(7168));
		                    /*
		                     * 将 http协议升级为 ws协议 
		                     */
		                    pipeline.addLast(new WebSocketServerProtocolHandler("/examService"));
		                    pipeline.addLast(new TCPServerHandler());
						}
					});
				
			ChannelFuture future = serverBootstrap.bind(port).sync();
			
			future.channel().closeFuture().sync();
		} finally {
			bossLoopGroup.shutdownGracefully();
			workLoopGroup.shutdownGracefully();
		}
		
	}
	
}

3.tcp服务启动类:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

  @Component
public class StartEvent implements ApplicationListener<ContextRefreshedEvent>{

	private static final Logger logger = LoggerFactory.getLogger(StartEvent.class);
	
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		if (event.getApplicationContext().getParent() == null) {
			logger.info("启动业务程序!");

			// 启动tcp 服务
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						new ExamTCPServer().run();
					} catch (Exception e) {
						logger.error("启动失败");
					}
				}
			}).start();
		}
	}
}

4.后端消息推送:

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

public class ChannelSupervise {

    private   static ChannelGroup GlobalGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    private  static ConcurrentMap<String, ChannelId> ChannelMap=new ConcurrentHashMap();
    public  static void addChannel(Channel channel){
        GlobalGroup.add(channel);
        ChannelMap.put(channel.id().asShortText(),channel.id());
    }
    public static void removeChannel(Channel channel){
        GlobalGroup.remove(channel);
        ChannelMap.remove(channel.id().asShortText());
    }
    public static  Channel findChannel(String id){
        return GlobalGroup.find(ChannelMap.get(id));
    }
    public static void send2All(TextWebSocketFrame tws){
        GlobalGroup.writeAndFlush(tws);
    }
	
}

5.业务处理:

import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TCPServerHandler extends ChannelInboundHandlerAdapter{

/**
 * channelAdded
 * channelRegistered
 * channelActive
 * channelRead
 * channelInactive
 * channelUnregistered
 */

private static Logger LOGGER = LoggerFactory.getLogger(TCPServerHandler.class);

private DateTimeFormatter formatter;
private List<OperationPersonnelVO> operationList;
@Autowired
private OperationPersonnelDao operationPersonnelDao;
//初始化业务处理数据
@PostConstruct
private void initPerson(){
   //...省略
}

  	// -- 连接通道上下文信息
    public static Map<String, SocketMessageVO> channelMap = new ConcurrentHashMap();

	private ExamCheckService examCheckService;
	private NotifyService notifyService;
	
/**
 * 连接建立
 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception{
	notifyService = (NotifyService)SpringContextUtil.getBean("notifyServiceImpl");
	examCheckService = (ExamCheckService)SpringContextUtil.getBean("examCheckService");
	
	SocketMessageVO messageVO = new SocketMessageVO();
	String userId = SessionInfoUtil.getSessionUserId().toString();
	messageVO.setCtx(ctx);
	messageVO.setUserId(userId);
	channelMap.put(userId, messageVO);
	
	String info = String.format("站点:%s  接入连接", getRemoteAddress(ctx));
	LOGGER.info(info);
	ChannelSupervise.addChannel(ctx.channel());
}

/**
 * 连接断开
 */
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception{
	for (Map.Entry<String,SocketMessageVO> map : channelMap.entrySet()) {
		if (map.getValue().getCtx() == ctx) {
			channelMap.remove(map.getKey());
		}
	}
	String info = String.format("站点:%s  连接断开", getRemoteAddress(ctx));
	LOGGER.info(info);
	ChannelSupervise.removeChannel(ctx.channel());
}

/**
 * 新消息自动调用
 * @throws UnsupportedEncodingException 
 * @throws InterruptedException 
 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException, InterruptedException{
	String json = ((TextWebSocketFrame)msg).text();
	//数据转换
	//SocketMessageVO messageVO = JsonUtil.json2Object(json, SocketMessageVO.class);
	if(messageVO.getKey() == 0){
	   //取消业务处理
	   //。。。
	}else{
	   //业务处理
		//。。。
	}
}

/**
 * 异常处理回调
 */
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
	cause.printStackTrace();
	String error = String.format("用户:%s 连接异常 【%s】", getRemoteAddress(ctx), cause.getMessage());
	LOGGER.error(error);
	ctx.close();
}

/**
 * 通过上下文获取channelid
 */
public static String getRemoteAddress(ChannelHandlerContext ctx){
	return ctx.channel().remoteAddress().toString();
}

6.通道上下文信息:

import io.netty.channel.ChannelHandlerContext;
public class SocketMessageVO {
		public SocketMessageVO(){
			super();
		}
	
public SocketMessageVO(String userId, String pointCode, String remindTime, Integer completeNum) {
	super();
	this.userId = userId;
	this.pointCode = pointCode;
	this.remindTime = remindTime;
	this.completeNum = completeNum;
}
/**
 * 消息id
 */
private String id;
/**
 * 聊天消息
 */
private String message;
/**
 * 用户id
 */
private String userId;

private String pointCode;

/**
 * 单站点进度
 */
private Double progress = 0d;
/**
 * 剩余时间
 */
private String remindTime;
/**
 * 已体检完成站点的个数
 */
private Integer completeNum;
/**
 * 通道上下文
 */
private ChannelHandlerContext ctx;

/**
 * 0关闭 1开启
 */
private Integer key;

public String getUserId() {
	return userId;
}
public void setUserId(String userId) {
	this.userId = userId;
}
public String getId() {
	return id;
}
public void setId(String id) {
	this.id = id;
}
public String getMessage() {
	return message;
}
public void setMessage(String message) {
	this.message = message;
}
public String getPointCode() {
	return pointCode;
}
public void setPointCode(String pointCode) {
	this.pointCode = pointCode;
}
public Double getProgress() {
	return progress;
}
public void setProgress(Double progress) {
	this.progress = progress;
}
public String getRemindTime() {
	return remindTime;
}
public void setRemindTime(String remindTime) {
	this.remindTime = remindTime;
}
public ChannelHandlerContext getCtx() {
	return ctx;
}
public void setCtx(ChannelHandlerContext ctx) {
	this.ctx = ctx;
}
public Integer getCompleteNum() {
	return completeNum;
}
public void setCompleteNum(Integer completeNum) {
	this.completeNum = completeNum;
}

public Integer getKey() {
	return key;
}

public void setKey(Integer key) {
	this.key = key;
}}

7.实现的效果:
在这里插入图片描述

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值