【Spring Boot】集成Netty Socket.IO通讯框架
服务端
- @Configuration
- public class NettySocketConfig {
- private static final Logger logger = LoggerFactory.getLogger(NettySocketConfig.class);
- @Bean
- public SocketIOServer socketIOServer() {
- //创建Socket,并设置监听端口
- com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
- // 设置主机名,默认是0.0.0.0
- config.setHostname("192.168.8.107");
- // 设置监听端口
- config.setPort(9096);
- // 协议升级超时时间(毫秒),默认10000。HTTP握手升级为ws协议超时时间
- config.setUpgradeTimeout(10000);
- // Ping消息间隔(毫秒),默认25000。客户端向服务器发送一条心跳消息间隔
- config.setPingInterval(60000);
- // Ping消息超时时间(毫秒),默认60000,这个时间间隔内没有接收到心跳消息就会发送超时事件
- config.setPingTimeout(180000);
- // 这个版本0.9.0不能处理好namespace和query参数的问题。所以为了做认证必须使用全局默认命名空间
- config.setAuthorizationListener(new AuthorizationListener() {
- @Override
- public boolean isAuthorized(HandshakeData data) {
- // 可以使用如下代码获取用户密码信息
- //String username = data.getSingleUrlParam("username");
- //String password = data.getSingleUrlParam("password");
- //logger.info("连接参数:username=" + username + ",password=" + password);
- //ManagerInfo managerInfo = managerInfoService.findByUsername(username);
- //
- //String salt = managerInfo.getSalt();
- //String encodedPassword = ShiroKit.md5(password, username + salt);
- //// 如果认证不通过会返回一个Socket.EVENT_CONNECT_ERROR事件
- //return encodedPassword.equals(managerInfo.getPassword());
- return true;
- }
- });
- final SocketIOServer server = new SocketIOServer(config);
- System.out.println("注入SocketIOServer");
- return server;
- }
- @Bean
- public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
- return new SpringAnnotationScanner(socketServer);
- }
- }
- @Component
- public class MessageEventHandler {
- private static final Logger logger = LoggerFactory.getLogger(MessageEventHandler.class);
- /**
- * 服务器socket对象
- */
- public static SocketIOServer socketIoServer;
- /**
- * 客户端集合
- */
- static ArrayList<UUID> listClient = new ArrayList<>();
- /**
- * 超时时间
- */
- static final int limitSeconds = 60;
- @Autowired
- public LoginService loginService;
- /**
- * 初始化消息事件处理器
- *
- * @param server 服务器socket对象
- */
- @Autowired
- public MessageEventHandler(SocketIOServer server) {
- logger.info("初始化SOCKET消息事件处理器");
- this.socketIoServer = server;
- }
- /**
- * 客户端发起连接时触发
- *
- * @param client 客户端Socket对象信息
- */
- @OnConnect
- public void onConnect(SocketIOClient client) {
- logger.info("客户端{}已连接", client.getSessionId());
- listClient.add(client.getSessionId());
- }
- /**
- * 客户端断开连接时触发
- *
- * @param client 客户端Socket对象信息
- */
- @OnDisconnect
- public void onDisconnect(SocketIOClient client) {
- logger.info("客户端{}断开连接", client.getSessionId());
- if (listClient.contains(client.getSessionId())) {
- listClient.remove(client.getSessionId());
- }
- }
- /**
- * 客户端发送消息时触发
- *
- * @param client 客户端Socket对象信息
- * @param request AckRequest 回调对象
- * @param data 消息信息实体
- */
- @OnEvent(value = SocketConstants.SocketEvent.MESSAGE)
- public void onEvent(SocketIOClient client, AckRequest request, MessageInfo data) {
- System.out.println("发来消息:" + data.getMsgContent());
- socketIoServer.getClient(client.getSessionId()).sendEvent("messageevent", "back data");
- }
- /**
- * 效验连接事件并存储客户端信息
- *
- * @param client 客户端Socket对象信息
- * @param data 客户端数据
- * @param request AckRequest 回调对象
- */
- @OnEvent(value = SocketConstants.SocketEvent.HEALTH_CHECK)
- public void onEventByHealthCheck(SocketIOClient client, String data, AckRequest request) {
- //logger.info("客户端{}效验连接请求", client.getSessionId());
- ////解析请求数据
- //HealthCheckRequest healthCheckRequest = JSON.parseObject(data, HealthCheckRequest.class);
- //if (healthCheckRequest != null) {
- // //存储客户端信息
- // SocketInstance instance = SocketInstance.getSocketInstance();
- // System.out.println(data);
- // instance.insertSocketClient(healthCheckRequest.getEnCode(), client);
- // logger.info("客户端{}效验连接响应:{}", client.getSessionId(), "OK");
- // //响应客户端
- // request.sendAckData("OK");
- //}
- }
- /**
- * 登录事件
- *
- * @param client 客户端Socket对象信息
- * @param data 客户端数据
- * @param request AckRequest 回调对象
- */
- @OnEvent(value = SocketConstants.SocketEvent.LOGIN)
- public void onEventByLogin(SocketIOClient client, String data, AckRequest request) {
- logger.info("客户端{}登录请求:{}", client.getSessionId(), data);
- AppResponseBase appResponseBase = new AppResponseBase(0, "通讯成功");
- //业务响应对象
- LoginResponse loginResponse = null;
- try {
- //解析请求数据
- LoginRequest loginRequest = JSON.parseObject(data, LoginRequest.class);
- if (loginRequest == null) {
- throw new AppException(AppResultCode.LoginAnalysis_Fail);
- }
- //调用登陆接口
- loginResponse = loginService.appLogin(loginRequest);
- if (loginResponse == null) {
- throw new AppException(AppResultCode.LoginCloud_Fail);
- }
- if (EnumResult.Success.equals(loginResponse.getResultCode())) {
- //保存客户端Socket信息
- SocketInstance instance = SocketInstance.getSocketInstance();
- instance.insertSocketClient(loginRequest.getEnCode(), client);
- }
- } catch (AppException ex) {
- loginResponse = new LoginResponse(ex.getAppResultCode().getCode(), ex.getAppResultCode().getMsg());
- } catch (Exception ex) {
- loginResponse = new LoginResponse(AppResultCode.Exceptions.getCode(), AppResultCode.Exceptions.getMsg());
- ex.printStackTrace();
- }
- appResponseBase.setRespData(loginResponse);
- String result = JSON.toJSONString(appResponseBase);
- logger.info("客户端{}登录响应:{}", client.getSessionId(), result);
- //响应客户端
- request.sendAckData(result);
- }
- /**
- * 交易下单事件
- * @param callPayRequest 下单请求信息实体
- * @return
- */
- public static String sendByPayEvent(CallPayRequest callPayRequest) {
- String result = "";
- //获取客户端信息
- SocketInstance instance = SocketInstance.getSocketInstance();
- SocketIOClient client = instance.getClientSocket(callPayRequest.getEnCode());
- if (client != null) {
- //请求报文
- String requestParam = JSON.toJSONString(callPayRequest);
- //请求下单
- client.sendEvent(SocketConstants.SocketEvent.PAY, new AckCallback<String>(String.class) {
- @Override
- public void onSuccess(String s) {
- //响应信息
- System.out.println("ack from client: " + client.getSessionId() + " data: " + s.toString());
- }
- }, requestParam);
- } else {
- //客户端已断开连接
- }
- return result;
- }
- }
- @Component
- @Order(value = 1)
- public class MyCommandLineRunner implements CommandLineRunner {
- private final SocketIOServer server;
- @Autowired
- public MyCommandLineRunner(SocketIOServer server) {
- System.out.println("初始化MyCommandLineRunner");
- this.server = server;
- }
- @Override
- public void run(String... args) {
- try {
- server.start();
- System.out.println("socket.io启动成功!");
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- }
- public class SocketConstants {
- /**
- * Socket事件类
- */
- public class SocketEvent {
- /**
- * 效验连接状况
- */
- public static final String HEALTH_CHECK = "HEALTH_CHECK";
- /**
- * 消息接收事件名称
- */
- public static final String MESSAGE = "message";
- /**
- * 登录事件名称
- */
- public static final String LOGIN = "LOGIN";
- /**
- * 获取交易要素事件名称
- */
- public static final String QUERY_PAY_FIELDS = "QUERY_PAY_FIELDS";
- /**
- * 创建订单事件名称
- */
- public static final String CREATE_ORDER = "CREATE_ORDER";
- /**
- * 监控订单状态事件名称
- */
- public static final String CHECK_ORDER_STATUS = "CHECK_ORDER_STATUS";
- /**
- * 获取订单事件名称
- */
- public static final String QUERY_ORDER = "QUERY_ORDER";
- /**
- * 支付事件名称
- */
- public static final String PAY = "PAY";
- }
- }
- public class SocketInstance {
- /**
- * 客户端Socket连接对象容器
- */
- private static Map<String, SocketIOClient> socketClients = null;
- /**
- * 私有构造
- */
- private SocketInstance() {
- //从缓存中获取socketClients
- socketClients = new HashMap<>();
- }
- /**
- * 定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SocketInstanceHolder的类,只有在SocketInstance.getSocketInstance()中调用,
- * 由于私有的属性,他人无法使用SocketInstanceHolder,不调用SocketInstance.getSocketInstance()就不会创建实例。
- * 优点:达到了lazy loading的效果,即按需创建实例。
- * 无法适用于分布式集群部署
- */
- private static class SocketInstanceHolder {
- /**
- * 创建全局唯一实例
- */
- private final static SocketInstance instance = new SocketInstance();
- }
- /**
- * 获取全局唯一实例
- *
- * @return SocketInstance对象
- */
- public static SocketInstance getSocketInstance() {
- return SocketInstanceHolder.instance;
- }
- /**
- * 新增客户端连接到容器
- *
- * @param encode 设备En号
- * @param socketIOClient 客户端socket对象
- */
- public void insertSocketClient(String encode, SocketIOClient socketIOClient) {
- SocketIOClient oldSocketIOClient = socketClients.get(encode);
- if (oldSocketIOClient != null) {
- try {
- //关闭客户端连接
- oldSocketIOClient.disconnect();
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- socketClients.put(encode, socketIOClient);
- }
- /**
- * 获取客户端Socket对象
- *
- * @param encode 设备encode
- * @return 客户端Socket对象
- */
- public SocketIOClient getClientSocket(String encode) {
- return socketClients.get(encode);
- }
- }
Android客户端
- public class SocketClient {
- /**
- * 最大重连次数
- */
- private int maxReConnectionCount = 5;
- /**
- * 重连次数
- */
- private int reConnectionCount = 0;
- /**
- * 等待框对象
- */
- private static ProgressDialog progressdialog;
- /**
- * 提示框
- */
- private static AlertDialog.Builder dialogExitBuilder;
- /**
- * Toast提示对象
- */
- private static Toast toast;
- /**
- * Socket客户端对象信息
- */
- public static Socket socket;
- /**
- * 主页面对象,每个页面onCreate时必须设置,可在每个页面监控Socket连接状况
- */
- public static Context nowContext;
- /**
- * Socket连接提示handler(等待框)
- */
- Handler dialogMessageHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- Bundle bundle = msg.getData();
- String message = bundle.getString(MessageUtil.MESSAGE);
- setDialogMessage(message);
- }
- };
- /**
- * Socket连接失败退出提示handler(提示框)
- */
- Handler dialogExitHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- Bundle bundle = msg.getData();
- String message = bundle.getString(MessageUtil.MESSAGE);
- dialogExit(message);
- }
- };
- /**
- * Socket连接提示handler(Toast)
- */
- Handler toastMessageHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- Bundle bundle = msg.getData();
- String message = bundle.getString(MessageUtil.MESSAGE);
- showToast(message, Toast.LENGTH_SHORT);
- }
- };
- /**
- * 等待框
- *
- * @param message 提示文字
- */
- private static void setDialogMessage(String message) {
- if (progressdialog == null) {
- progressdialog = new ProgressDialog(nowContext);
- }
- progressdialog.setTitle("学通宝收银");
- progressdialog.setMessage(message);
- progressdialog.setCancelable(false);
- progressdialog.show();
- }
- /**
- * 退出提示框
- *
- * @param message 提示文字
- */
- private void dialogExit(String message) {
- //初始化退出builder
- if (dialogExitBuilder == null) {
- dialogExitBuilder = new AlertDialog.Builder(nowContext);
- }
- dialogExitBuilder.setMessage(message);
- dialogExitBuilder.setTitle("提示");
- dialogExitBuilder.setIcon(R.mipmap.warning);
- dialogExitBuilder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- //参数用作状态码;根据惯例,非 0 的状态码表示异常终止。
- System.exit(0);
- }
- });
- dialogExitBuilder.create().show();
- }
- /**
- * Toast消息提醒
- *
- * @param text 标题
- * @param duration 时长
- */
- public void showToast(String text, int duration) {
- //只创建一次
- if (toast == null) {
- toast = Toast.makeText(nowContext, text, duration);
- } else {
- toast.setText(text);
- toast.setDuration(duration);
- }
- toast.show();
- }
- public void startSocket() throws URISyntaxException {
- //初始化Socket配置
- IO.Options options = new IO.Options();
- options.transports = new String[]{"websocket"};
- options.reconnectionAttempts = maxReConnectionCount; // 设置一个重连的最大尝试次数,超过这个值后Socket.io会使用所有允许的其他连接方式尝试重连,直到最终失败。
- options.reconnectionDelay = 500; //为Socket.io的重连设置一个时间间隔,内部会在多次重连尝试时采用该值的指数值间隔,用来避免性能损耗(500 > 1000 > 2000 > 4000 > 8000)
- options.reconnection = true; //当连接终止后,是否允许Socket.io自动进行重连
- options.timeout = 9000; //连接超时时间(ms)
- options.forceNew = true;
- options.query = "appid=cn.xuetongbao.xtbpay";
- socket = IO.socket("http://192.168.8.107:9096/", options);
- //连接成功
- socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- //重连机制
- if (reConnectionCount > 0) {
- //连接存储客户端信息
- DeviceInfoInstance instance = DeviceInfoInstance.getSocketInstance();
- HealthCheckRequest healthCheckRequest = new HealthCheckRequest();
- healthCheckRequest.setEnCode(instance.getDeviceInfo().getEnCode());
- socket.emit(SocketConstants.SocketEvent.HEALTH_CHECK, RequestUtil.createObject(healthCheckRequest), (Ack) args1 -> {
- System.out.println("args1:" + args1.toString());
- });
- }
- System.out.println("连接成功...");
- toastMessageHandler.sendMessage(MessageUtil.createMessage("服务器连接成功"));
- //关闭等待框
- if (progressdialog != null) {
- progressdialog.dismiss();
- }
- }
- });
- //连接失败事件
- socket.on(Socket.EVENT_CONNECT_ERROR, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- System.out.println("Socket.EVENT_CONNECT_ERROR");
- System.out.println("reConnectionCount:" + reConnectionCount);
- if (reConnectionCount >= maxReConnectionCount) {
- dialogExitHandler.sendMessage(MessageUtil.createMessage("服务器连接失败,请稍后再试"));
- } else {
- dialogMessageHandler.sendMessage(MessageUtil.createMessage("服务器连接失败,正在重新连接..."));
- }
- }
- });
- //连接中事件
- socket.on(Socket.EVENT_RECONNECTING, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- reConnectionCount++;
- System.out.println("Socket.EVENT_RECONNECTING");
- dialogMessageHandler.sendMessage(MessageUtil.createMessage("正在连接服务器..."));
- }
- });
- //连接超时事件
- socket.on(Socket.EVENT_CONNECT_TIMEOUT, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- System.out.println("Socket.EVENT_CONNECT_TIMEOUT");
- if (nowContext != null) {
- dialogMessageHandler.sendMessage(MessageUtil.createMessage("与服务器连接超时,正在重新建立连接..."));
- socket.connect();
- }
- }
- });
- //心跳包
- socket.on(Socket.EVENT_PING, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- System.out.println("Socket.EVENT_PING");
- }
- });
- //心跳包
- socket.on(Socket.EVENT_PONG, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- System.out.println("Socket.EVENT_PONG");
- }
- });
- //消息接收事件
- socket.on(Socket.EVENT_MESSAGE, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- System.out.println("-----------接受到消息啦--------" + Arrays.toString(args));
- }
- });
- //连接断开事件
- socket.on(Socket.EVENT_DISCONNECT, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- reConnectionCount = 0;
- System.out.println("客户端断开连接啦。。。");
- if (nowContext != null) {
- dialogMessageHandler.sendMessage(MessageUtil.createMessage("似乎与服务器断开连接,正在重新建立连接..."));
- socket.connect();
- }
- }
- });
- //交易事件
- socket.on(SocketConstants.SocketEvent.PAY, new Emitter.Listener() {
- @Override
- public void call(Object... args) {
- Object data = args[0];
- Object ackCallBack = args[1];
- System.out.println("接收到服务端交易下单消息" + data);
- CallPayRequest callPayRequest = JSON.parseObject(data.toString(), CallPayRequest.class);
- if (callPayRequest != null) {
- }
- //data
- CallPayResponse callPayResponse = new CallPayResponse();
- callPayResponse.setResultCode(AppResultCode.Success.getCode());
- callPayResponse.setResultMsg(AppResultCode.Success.getMsg());
- //响应服务端
- ((Ack) ackCallBack).call(JSON.toJSONString(callPayResponse));
- }
- });
- System.out.println("准备连接服务器...");
- socket.connect();
- }
- }
注:仅供学习参考
【Spring Boot】集成Netty Socket.IO通讯框架的更多相关文章
- spring boot集成MyBatis 通用Mapper 使用总结
spring boot集成MyBatis 通用Mapper 使用总结 2019年 参考资料: Spring boot集成 MyBatis 通用Mapper SpringBoot框架之通用mapper插 ...
- Spring Boot集成Jasypt安全框架
Jasypt安全框架提供了Spring的集成,主要是实现 PlaceholderConfigurerSupport类或者其子类. 在Sring 3.1之后,则推荐使用PropertySourcesPl ...
- Spring Boot集成Reactor事件处理框架的简单示例
1. Reactor简介 Reactor 是 Spring 社区发布的基于事件驱动的异步框架,不仅解耦了程序之间的强调用关系,而且有效提升了系统的多线程并发处理能力. 2. Spring Boot集成 ...
- Spring Boot 2.X(六):Spring Boot 集成Redis
Redis 简介 什么是 Redis Redis 是目前使用的非常广泛的免费开源内存数据库,是一个高性能的 key-value 数据库. Redis 与其他 key-value 缓存(如 Memcac ...
- Spring boot集成swagger2
一.Swagger2是什么? Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件. Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格 ...
- Spring Boot 集成 Swagger,生成接口文档就这么简单!
之前的文章介绍了<推荐一款接口 API 设计神器!>,今天栈长给大家介绍下如何与优秀的 Spring Boot 框架进行集成,简直不能太简单. 你所需具备的基础 告诉你,Spring Bo ...
- spring boot 集成 zookeeper 搭建微服务架构
PRC原理 RPC 远程过程调用(Remote Procedure Call) 一般用来实现部署在不同机器上的系统之间的方法调用,使得程序能够像访问本地系统资源一样,通过网络传输去访问远程系统资源,R ...
- Quartz与Spring Boot集成使用
上次自己搭建Quartz已经是几年前的事了,这次项目中需要定时任务,需要支持集群部署,想到比较轻量级的定时任务框架就是Quartz,于是来一波. 版本说明 通过搜索引擎很容易找到其官网,来到Docum ...
- Spring Boot集成MyBatis开发Web项目
1.Maven构建Spring Boot 创建Maven Web工程,引入spring-boot-starter-parent依赖 <project xmlns="http://mav ...
随机推荐
- CF1137E. Train Car Selection(可删堆)
题面 三个操作 1.在当前数列最左端加入\(k\)个初始为\(0\)的数 2.在当前数列最右端加入\(k\)个初始为\(0\)的数 3.将当前数列从左到右第\(i\)个数加上\(b+(i-1)k(b& ...
- SqlServer批量插入(SqlBulkCopy、表值参数)
之前做项目需要用到数据库的批量插入,于是就研究了一下,现在做个总结. 创建了一个用来测试的Student表: CREATE TABLE [dbo].[Student]( [ID] [int] PRIM ...
- [POI2007]MEG-Megalopolis 树的dfs序+树状数组维护差分 BZOJ1103
题目描述 Byteotia has been eventually touched by globalisation, and so has Byteasar the Postman, who onc ...
- JS中Math函数的常用方法
Math 是数学函数,但又属于对象数据类型 typeof Math => ‘object’ console.dir(Math) 查看Math的所有函数方法. 1,Math.abs() 获取绝对值 ...
- mysql id_logfile 日志
ib_logfile 文件原理 ib_logfile0 是innodb中事务日志,记录系统的回滚,重做日志,记录的是文件的物理更改,存放位置my.ini 中的 datadir="D:\php ...
- Week 5: Object Oriented Programming 9. Classes and Inheritance Exercise: int set
class intSet(object): """An intSet is a set of integers The value is represented by a ...
- 最小生成树----prim算法的堆优化
题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz 输入输出格式 输入格式: 第一行包含两个整数N.M,表示该图共有N个结点和M条无向边.(N<=5000,M<= ...
- HDU 5938 Kingdom of Obsession(数论 + 二分图匹配)
题意: 给定S,N,把S+1,S+2,...S+N这N个数填到1,2,...,N里,要求X只能填到X的因子的位置.(即X%Y=0,那么X才能放在Y位置) 问是否能够放满. 分析:经过小队的分析得出的结 ...
- (转)数位dp
原博客 https://blog.csdn.net/wust_zzwh/article/details/52100392 建议原博客看到hdu 不要62,然后看我分割线后两道题,然后再回来看原博.-- ...
- C++ GUI Qt4编程(01)-1.1Hello Qt
1. 根据C++ GUI Qt4编程(第二版)整理2. 系统:centos7: Qt版本:5.5.13. 程序:hello.cpp #include <QApplication> #in ...