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.实现的效果: