工作七年,对消息推送使用的一些经验和总结

前言:不管是APP还是WEB端都离不开消息推送,尤其是APP端,push消息,小信箱消息;WEB端的代办消息等。因在项目中多次使用消息推送且也是很多项目必不可少的组成部分,故此总结下供自己参考。

一、什么是消息推送

消息推送(Push)指运营人员通过自己的产品或第三方工具对用户当前网页或移动设备进行的主动消息推送。用户可以在网页上或移动设备锁定屏幕和通知栏看到push消息通知

二、消息推送的种类

从数据模型分:推和拉

从终端分:APP端和WEB端

从实现层面分:短论询、Comet(长轮询)、Flash XMLSocket、SSE、Web-Socket

类型概念优点缺点备注
短轮询客户端通过定期向服务器发送请求来获取最新的消息。服务器在接收到请求后立即响应,无论是否有新消息。如果服务器没有新消息可用,客户端将再次发送请求后端编写简单

高延迟:因客户端定期发起请求,导致消息延迟,尤其是定期时间设置过长时

高网络负载:无新消息时也会频繁发起请求,消耗服务器资源和网络

时效性差:服务器产生了新消息,客户端不能立马感知到,需等到轮询时间到

Comet(长轮询)客户端发起请求,服务器接到请求后hold住连接,直到有新消息(或超时)才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求

减少请求次数:相对于短轮训而言

减少网络负载:没有消息时会保持连接,减少了频繁请求

时效性稍提高:相对于短轮询而言

没有新消息时会保持请求挂起,直到有新消息到达或超时。相比于短轮询,长轮询可以更快地获取新消息,减少了不必要的请求。
Flash XMLSocket在 HTML 页面中内嵌入一个使用了 XMLSocket 类的 Flash 程序。JavaScript 通过调用此 Flash 程序提供的socket接口与服务器端的socket进行通信网络聊天室,网络互动游戏使用较多

SSE(Server-send Events)

服务器主动推送时效性好:SSE使用了持久连接,可以实现比短轮询和长轮询更好的实时性

单向通道:SSE是单向的,只允许服务器向客户端推送消息,客户端无法向服务器发送消息

不适用低版本浏览器:SSE是HTML5的一部分,不支持低版本的浏览器。在使用SSE时,需要确保客户端浏览器的兼容性

Web-SocketWebSocket是一种双向通信协议,允许在单个持久连接上进行全双工通信

时效性最佳:WebSocket 提供了真正的双向通信,可以实现实时的双向数据传输,具有最佳的实时性

低延迟:与轮询和长轮询相比,WebSocket 使用单个持久连接,减少了连接建立和断开的开销,从而降低了延迟

双向通信:WebSocket 允许服务器与客户端之间进行双向通信,服务器可以主动向客户端发送消息,同时客户端也可以向服务器发送消息

较高的网络负载:WebSocket 使用长连接,会占用一定的网络资源。在大规模并发场景下,需要注意服务器的负载情况

浏览器支持:大多数现代浏览器都支持 WebSocket,但需要注意在开发过程中考虑不同浏览器的兼容性

短轮询:客户端定时轮询发起请求

长轮询:客户端发起请求,等待后端响应并再次发起请求

Flash XMLSocket:

原理示意图:

利用Flash XML Socket实现”服务器推”技术前提:
(1)Flash提供了XMLSocket类,服务器利用Socket向Flash发送数据;
(2)JavaScript和Flash的紧密结合JavaScript和Flash可以相互调用。
优点是实现了socket通信,不再利用无状态的http进行伪推送。但是缺点更明显:
1.客户端必须安装 Flash 播放器;
2.因为 XMLSocket 没有 HTTP 隧道功能,XMLSocket 类不能自动穿过防火墙;
3.因为是使用套接口,需要设置一个通信端口,防火墙、代理服务器也可能对非 HTTP 通道端口进行限制。

SSE:当使用Server-Sent Events(SSE)时,客户端(通常是浏览器)与服务器之间建立一种持久的连接,使服务器能够主动向客户端发送数据。这种单向的、服务器主动推送数据的通信模式使得实时更新的数据能够被实时地传送到客户端,而无需客户端进行轮询请求

SSE的工作原理如下: 

  1. 建立连接:客户端通过使用EventSource对象在浏览器中创建一个与服务器的连接。客户端向服务器发送一个HTTP请求,请求的头部包含Accept: text/event-stream,以表明客户端希望接收SSE数据。服务器响应这个请求,并建立一个持久的HTTP连接。

  2. 保持连接:服务器保持与客户端的连接打开状态,不断发送数据。这个连接是单向的,只允许服务器向客户端发送数据,客户端不能向服务器发送数据。

  3. 服务器发送事件:服务器使用Content-Type: text/event-stream标头来指示响应是SSE数据流。服务器将数据封装在特定的SSE格式中,每个事件都以data:开头,后面是实际的数据内容,以及可选的其他字段,如event:id:。服务器发送的数据可以是任何文本格式,通常是JSON。

  4. 客户端接收事件:客户端通过EventSource对象监听服务器发送的事件。当服务器发送事件时,EventSource对象会触发相应的事件处理程序,开发人员可以在处理程序中获取到事件数据并进行相应的操作。常见的事件是message事件,表示接收到新的消息。

  5. 断开连接:当客户端不再需要接收服务器的事件时,可以关闭连接。客户端可以调用EventSource对象的close()方法来显式关闭连接,或者浏览器在页面卸载时会自动关闭连接。

在Spring Boot中,可以使用SseEmitter类来实现SSE:

@RestController
public class SSEController {private SseEmitter sseEmitter;@GetMapping("/subscribe")public SseEmitter subscribe() {sseEmitter = new SseEmitter();return sseEmitter;}@PostMapping("/send-message")public void sendMessage(@RequestBody String message) {try {if (sseEmitter != null) {sseEmitter.send(SseEmitter.event().data(message));}} catch (IOException e) {e.printStackTrace();}}
}
<script>// 创建一个EventSource对象,指定SSE的服务端端点var eventSource = new EventSource('/subscribe');console.log("eventSource=", eventSource)// 监听message事件,接收从服务端发送的消息eventSource.addEventListener('message', function(event) {var message = event.data;console.log("message=", message)var messageContainer = document.getElementById('message-container');messageContainer.innerHTML += '<p>' + message + '</p>';});
</script>

上述过程:客户端可以通过访问/subscribe接口来订阅SSE事件,服务器会返回一个SseEmitter对象。当有新消息到达时,调用SseEmitter对象的send()方法发送消息。

Web-Socket:

HTML代码: 

<script>// 创建WebSocket对象,并指定服务器的URLvar socket = new WebSocket('ws://localhost:8080/上下文路径/channel/message/');// 监听WebSocket的连接事件socket.onopen = function(event) {console.log('WebSocket connected');};// 监听WebSocket的消息事件socket.onmessage = function(event) {var message = event.data;var messageContainer = document.getElementById('message-container');messageContainer.innerHTML += '<p>' + message + '</p>';};// 监听WebSocket的关闭事件socket.onclose = function(event) {console.log('WebSocket closed');};// 发送消息到服务器function sendMessage() {var messageInput = document.getElementById('message-input');var message = messageInput.value;socket.send(message);messageInput.value = '';}
</script>

三、项目中使用的消息推送

例子1:Web-Socket
1.引入websocket依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
2.websocket配置
/*** @描述 开启WebSocket支持的配置类* 自动注册使用@ServerEndpoint*/
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
3.websocket服务器端代码

说明:@ ServerEndpoint 注解是一个类层次的注解,主要是将当前类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端

/*** 消息推送**/
@ServerEndpoint("/channel/message/{user-id}")
@Slf4j
@Component
@RequiredArgsConstructor
public class TodoChannel implements ApplicationListener<FlowMessageEvent> {private static final Map<String, Set<Session>> SESSION_MAP = new ConcurrentHashMap<>();private Session session;private String userId;@OnMessagepublic void onMessage(String message) {log.info("websocket消息(id={}): {}", this.session.getId(), message);}@OnOpenpublic void onOpen(Session session, @PathParam("user-id") String userId) {this.session = session;this.userId = userId;val sessionSet = SESSION_MAP.getOrDefault(this.userId, new CopyOnWriteArraySet<>());sessionSet.add(session);SESSION_MAP.put(this.userId, sessionSet);log.info("websocket连接: id={}", this.session.getId());val message = new MessageModel();//往todoModel放业务数据session.getAsyncRemote().sendText(JSON.toJSONString(message));}@OnClosepublic void onClose(CloseReason closeReason) {val sessionSet = SESSION_MAP.get(this.userId);if (sessionSet != null) {sessionSet.remove(session);}log.info("websocket断开: id={} {}", this.session.getId(), closeReason);}@OnErrorpublic void onError(Throwable throwable) {log.warn("websocket异常: id={} throwable:", this.session.getId(), throwable);val sessionSet = SESSION_MAP.get(this.userId);if (sessionSet != null) {sessionSet.remove(this.session);}try {this.session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, throwable.getMessage()));} catch (IOException e) {log.error("websocket关闭失败", e);}}@Overridepublic void onApplicationEvent(@NotNull FlowMessageEvent event) {Set<String> userIds = CastUtils.cast(event.getSource());userIds.forEach(id -> {val sessionSet = SESSION_MAP.get(id);if (sessionSet == null) {return;}//业务处理todosessionSet.forEach(s -> s.getAsyncRemote().sendObject(JSON.toJSON(message)));});}@Scheduled(fixedRate = 24 * 60 * 60 * 1000L)public void sessionCleaner() {log.info("websocket message channel session清理");val keyToClean = new HashSet<String>();SESSION_MAP.forEach((k, v) -> {val sessionToClean = new HashSet<Session>();v.forEach(s -> {if (!s.isOpen()) {sessionToClean.add(s);}});v.removeAll(sessionToClean);if (v.isEmpty()) {keyToClean.add(k);}});keyToClean.forEach(SESSION_MAP::remove);}@Dataprivate static class MessageModel implements Serializable {private static final long serialVersionUID = 1L;private List<Info> list;private Integer size;@AllArgsConstructor@Valueprivate static class Info implements Serializable {private static final long serialVersionUID = 1L;String type;String name;}}
}
 4.事件类代码
/*** message事件**/
public class FlowMessageEvent extends ApplicationEvent {public FlowMessageEvent(Object source) {super(source);}
}
5.使用事件推送消息

applicationEventPublisher.publishEvent(new FlowMessageEvent(user));

例子2:RabbitMq:
1.引入rabbitmq依赖
2.编写rabbitmq配置类
/*** @author wux* @version 1.0.0*/
@SpringBootConfiguration
@Slf4j
public class RabbitMqConfig {@Value("${spring.rabbitmq.host:10.128.30.xxx}")private String host;@Value("${spring.rabbitmq.port:5672}")private int port;@Value("${spring.rabbitmq.username:guest}")private String username;@Value("${spring.rabbitmq.password:guest}")private String password;@Beanpublic ConnectionFactory connectionFactory() {CachingConnectionFactory factory = new CachingConnectionFactory(host, port);factory.setUsername(username);factory.setPassword(password);//连接工厂开启消息确认和消息返回机制
//        factory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
//        factory.setPublisherReturns(true);return factory;}@Beanpublic RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);//使用json 序列化和反序列化factory.setMessageConverter(new Jackson2JsonMessageConverter());//factory.setAcknowledgeMode(AcknowledgeMode.AUTO);return factory;}@Bean@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public RabbitTemplate rabbitTemplate() {RabbitTemplate template = new RabbitTemplate();template.setConnectionFactory(connectionFactory());template.setMessageConverter(new Jackson2JsonMessageConverter());return template;}@Beanpublic Queue testDirectQueue() {return new Queue(RabbitMqConsts.TEST_QUE_PHM_WARN_INFO, true, false, false);}@Beanpublic DirectExchange testDirectExchange() {return new DirectExchange(RabbitMqConsts.TEST_EXC_PHM_WARN_INFO, true,false);}@Beanpublic Binding TestBinding() {return BindingBuilder.bind(testDirectQueue()).to(testDirectExchange()).with(RabbitMqConsts.TEST_KEY_PHM_WARN_INFO);}
}
3.编写rabbitmq工具类
/*** @author wux* @version 1.0.0* @description rabbit工具类*/
@Slf4j
public class RabbitMqUtil {private static AmqpAdmin getAmqpAdmin() {return SpringContextUtils.getBean("amqpAdmin",AmqpAdmin.class);}/**通过amqpAdmin动态创建队列、交换机和绑定关系ttlFlag :设置消息过期时间*/public static void createQueueAndExchangeIfNeed(String businessName, ExchangeEnum typeEnum, Integer ttl) {String exchangeName = "exc_" + businessName;String queueName = "que_" + businessName;String routingKey = "key_" + businessName;if (CheckUtil.isNotEmpty(getQueueInfo(queueName))) {return;}//创建队列Queue queue = createAndBindQueue(queueName, ttl);//创建交换机Exchange exchange = createAndBindExchange(exchangeName, typeEnum);//绑定队列和交换机switch (typeEnum){case DIRECT:binding(queueName, exchangeName, routingKey, typeEnum);break;case FANOUT:fanoutBinding(queue, exchange);break;default:binding(queueName, exchangeName, routingKey, typeEnum);}}private static Queue createAndBindQueue(String queueName, Integer ttl) {Map<String, Object> arguments = new HashMap<>();//设置过期时间,单位是毫秒if (CheckUtil.isNotEmpty(ttl)) {arguments.put("x-message-ttl", ttl);}if (CheckUtil.isEmpty(queueName)) {log.error("队列名称为空!queueName=" + queueName);throw new BusinessException("队列名称为空!");}Queue queue = new Queue(queueName, true, false, false, arguments);getAmqpAdmin().declareQueue(queue);return queue;}public static QueueInformation getQueueInfo (String queueName) {if (CheckUtil.isEmpty(queueName)) {return null;}return getAmqpAdmin().getQueueInfo(queueName);}private static Exchange createAndBindExchange(String exchangeName, ExchangeEnum typeEnum){AbstractExchange exchange = null;switch (typeEnum){case DIRECT:exchange = new DirectExchange(exchangeName, true, false);break;case TOPIC:exchange = new TopicExchange(exchangeName, true, false);break;case FANOUT:exchange = new FanoutExchange(exchangeName, true, false);break;case HEADERS:exchange = new HeadersExchange(exchangeName, true, false);break;default:exchange = new DirectExchange(exchangeName, true, false);}getAmqpAdmin().declareExchange(exchange);return exchange;}private static void binding(String queueName, String exchangeName, String routingKey, ExchangeEnum typeEnum) {//绑定队列和交换机Binding binding = new Binding(queueName, Binding.DestinationType.QUEUE, exchangeName, routingKey, null);getAmqpAdmin().declareBinding(binding);}private static void fanoutBinding(Queue queue, Exchange exchange) {BindingBuilder.bind(queue).to(exchange);}
}
4.监听和推送消息
/*** @author wux* @version 1.0.0* @description 监听phm设备状态消息*/
@Component
@Slf4j
public class MotePhmDeviceStatesListener {private static final String CLASS_NAME = "MotePhmDeviceStatesListener";@Autowiredprivate Map<String, AssembleDeviceStatesStrategy> map = new ConcurrentHashMap<String, AssembleDeviceStatesStrategy>();@Resourceprivate MoteMessageService moteMessageService;@Autowiredprotected BeanMapper beanMapper;@RabbitHandler@RabbitListener(bindings = @QueueBinding(value=@Queue("que_phm_device_states"),exchange = @Exchange("exc_phm_device_states"),key = "key_phm_device_states"))public void process(@Payload BaseMessage req, Message msg, Channel channel) {final String METHOD_NAME ="process";log.info(CLASS_NAME + "-" + METHOD_NAME + "-start,req={},msg={}", req, msg);long deliverTag = msg.getMessageProperties().getDeliveryTag();if (!MessageTypeEnum.DEVICE_STATES.getKey().equals(req.getType())) {log.warn(CLASS_NAME + "-" + METHOD_NAME + "-message=消息类型不匹配!");//拒绝,重新回到队列//channel.clearReturnListeners();//channel.basicReject(deliverTag, true);return;}try {String service = MessageSubTypeEnum.getService(req.getSubType());AssembleDeviceStatesStrategy strategy = map.get(service);BaseMessage reqMessage = beanMapper.map(req, BaseMessage.class);BaseMessage retMessage = strategy.assembleDeviceStates(reqMessage);strategy.sendMessage(retMessage, reqMessage, channel);} catch (Exception e) {log.error(CLASS_NAME + "-" + METHOD_NAME + "-异常, e={}", e);return;//拒绝,重新回到队列//channel.basicNack(deliverTag, false,true);}}
}
 public void sendMessage(BaseMessage retMessage, BaseMessage req, Channel channel) {retMessage.setDate(new Date());retMessage.setDateStr(DateUtils.format(retMessage.getDate(), DateUtils.DATE_TIME_SECOND));if (CheckUtil.isEmpty(req.getQueueName())) {log.warn("AssembleDeviceStatesStrategy" + "队列名称为空,req={}", req);return;}QueueInformation queueInfo = RabbitMqUtil.getQueueInfo(req.getQueueName());if (CheckUtil.isEmpty(queueInfo) || CheckUtil.isEmpty(queueInfo.getName())) {log.warn("AssembleDeviceStatesStrategy" + "-" + "队列不存在!" + ",req={}", req);return;}
//        try {
//            long count = channel.messageCount(req.getQueueName());
//            if (count >= 5000) {
//                channel.queueDelete(req.getQueueName());
//            }
//
//        } catch (IOException e) {
//            log.error("AssembleDeviceStatesStrategy" + "-" + "清除队列消息失败" + ",retMessage={}", retMessage, e);
//        }rabbitTemplate.convertAndSend(req.getQueueName(), retMessage);log.info("AssembleDeviceStatesStrategy" + "-" + "sendMessage推给前端信息" + ",retMessage={},req={}", retMessage, req);}
例子3:Kafka:

使用@KafkaListene(topics="xxx", groupId="xxx")  接受消息

四、消息中间件:RabbitMQ、RocketMQ、Kafka

高并发情况下,或者规模较大,推荐使用消息中间件,搭建一个公共平台,统一管理消息推送,项目层面进行隔离即可。

RabbitMQ可看:https://blog.csdn.net/baidu_35160588/article/details/89027810

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/659755.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最短路径 ( floyd) 算法

Floyd算法又称为插点法&#xff0c;是一种用于寻找给定的加权图中多源点之间最短路径的算法。 算法思想: https://upimg.baike.so.com/doc/5450540-5688910.html 图演示: 代码实现: public void floyd() {int[] vertex graph.getVertex();int[][] edges graph.getEdges()…

<网络安全>《12 数据库安全审计系统》

1 概念 数据库安全审计系统通过对用户访问数据库行为的记录、分析和汇报&#xff0c;来帮助用户事后生成合规报告、事故追根溯源&#xff0c;同时通过大数据搜索技术提供高效查询审计报告&#xff0c;定位事件原因&#xff0c;以便日后查询、分析、过滤&#xff0c;实现加强内…

【算法】拦截导弹(线性DP)

题目 某国为了防御敌国的导弹袭击&#xff0c;发展出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷&#xff1a;虽然它的第一发炮弹能够到达任意的高度&#xff0c;但是以后每一发炮弹都不能高于前一发的高度。 某天&#xff0c;雷达捕捉到敌国的导弹来袭。 由于该系…

VUE3+elementPlus 之 Form表单校验器 之 字符长度校验

需求&#xff1a;校验字符长度&#xff0c;超过后仍可输入&#xff0c;error提示录入字符数与限制字符数 校验字符长度&#xff1a; /*** 检验文字输入区的长度* param {*} rule 输入框的rule 对象&#xff0c;field&#xff1a;字段名称* param {*} value …

【百度Apollo】本地调试仿真:加速自动驾驶系统开发的利器

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

Unity | 资源热更(YooAsset AB)

目录 一、AssetBundle 1. 插件AssetBundle Browser 打AB包 &#xff08;1&#xff09;Unity&#xff08;我用的版本是2020.3.8&#xff09;导入AssetBundle Browser &#xff08;2&#xff09;设置Prefab &#xff08;3&#xff09;AssetBundleBrowser面板 2. 代码打AB包…

nodejs历史版本下载

Node.js — Previous Releases下载地址 .msi 是安装包&#xff08;windows&#xff09;&#xff0c;下载安装包即可

关于最小系统板PCB设计后的一些反思

简介 趁着刚刚画完板子寄回来&#xff0c;在这里做一些记录。 板子状况 这里打烊了5块PCB&#xff0c;但是没有进行SMT贴片&#xff0c;后续如果有芯片可以焊接上去进行后续验证。 封装问题 这里可以看到&#xff0c;我这里两侧的排针都是焊盘&#xff0c;不是通孔&#…

Unity_Timeline使用说明

Unity_Timeline使用说明 首先要找到工具吧&#xff1f;Unity2023.1.19f1c1打开如下&#xff1a; &#xff08;团结引擎没找见哪儿打开&#xff0c;可能是引擎问题吧&#xff1f;有知道的同学可以告诉我在哪儿打开&#xff09; Timelime使用流程&#xff1a; 打开之后会提示您…

ClickHouse为什么这么快(二)SSE指令优化

上一篇 ClickHouse为什么这么快&#xff08;一&#xff09;减少数据扫描范围 我们说到了ClickHouse中使用列存储&#xff0c;每个列都单独存储为一个文件&#xff0c;每个文件都是由一个或多个数据块组成&#xff0c;也就是说&#xff1a;每个文件由一个或多个数组组成&#xf…

3分钟阅读100篇文献?GPT可以做到!

摘要和背景 PPMAN-AI 01 在开始深入阅读之前&#xff0c;了解文献的主题和背景是非常重要的。这可以帮助你快速判断该文献是否符合你的研究需求。 prompt&#xff1a; 请简述文献[文献标题]的摘要。 解释文献[文献标题]中提到的研究背景。 文献[文献标题]的主要研究目的是什…

WordPress块编辑器(Gutenberg古腾堡)中如何添加脚注?

WordPress默认自带的块编辑器​&#xff08;Gutenberg古腾堡编辑器&#xff09;本身就自带添加脚注功能&#xff0c;不过经典编辑器不行。如果想要在WordPress中添加更加专业的脚注&#xff0c;建议使用Modern Footnotes插件&#xff0c;具体介绍及使用请参考『WordPress站点如…

【计算机图形】几何(Geometry)和拓扑(Topology)

目录 参考文献三维实体建模内核CSG/BREPParasolid简介Parasolid接口函数Parasolid类的结构 Parasolid数据分类&#xff1a;几何(Geometry)和拓扑(Topology)拓扑(Topology)什么是“拓扑”呢&#xff1f;Principle Geometry- Topology - Construction Geometry案例&#xff1a;拓…

ElementUI Form:Switch 开关

ElementUI安装与使用指南 Switch 开关 点击下载learnelementuispringboot项目源码 效果图 el-switch.vue 页面效果图 项目里el-switch.vue代码 <script> export default {name: el_switch,data() {return {value: true,value1: true,value2: true,value3: 100,value…

MFC串行化的应用实例

之前写过一篇MFC串行化的博文;下面看一个具体例子; 新建一个单文档应用程序;在最后一步,把View类的基类改为CFormView; 然后在资源面板编辑自己的字段; 然后到doc类的头文件添加对应变量, public:CString name;int age;CString sex;CString dept;CString zhiwu;CStrin…

蓝桥杯2024/1/31----第十届省赛题笔记

题目要求&#xff1a; 1、 基本要求 1.1 使用大赛组委会提供的国信长天单片机竞赛实训平台&#xff0c;完成本试题的程序设计 与调试。 1.2 选手在程序设计与调试过程中&#xff0c;可参考组委会提供的“资源数据包”。 1.3 请注意&#xff1a; 程序编写、调试完成后选手…

JAVA Web 学习(二)ServLet

二、动态web 资源开发技术——Servlet Servlet&#xff08;小服务程序&#xff09;是一个与协议无关的、跨平台的Web组件&#xff0c;由Servlet容器所管理。运行在服务器端&#xff0c;可以动态地扩展服务器的功能&#xff0c;并采用“请求一响应”模式提供Web服务。 Servlet的…

将java对象转换为json字符串的几种常用方法

目录 1.关于json 2.实现方式 1.Gson 2.jackson 3.fastjson 3.与前端的联系 1.关于json JSON是一种轻量级的数据交换格式。它由Douglas Crockford在2001年创造。JSON的全称是JavaScript Object Notation&#xff0c;它是一种文本格式&#xff0c;可以轻松地在各种平台之间传…

C#学习笔记_类(Class)

类的定义 类的定义是以关键字 class 开始&#xff0c;后跟类的名称。类的主体&#xff0c;包含在一对花括号内。 语法格式如下&#xff1a; 访问标识符 class 类名 {//变量定义访问标识符 数据类型 变量名;访问标识符 数据类型 变量名;访问标识符 数据类型 变量名;......//方…

正则表达式与文本三剑客

目录 一、正则表达式 1. 定义 2. 字符匹配 3. 重复限定符 4. 位置锚点 5. 分组和引用 6. 扩展正则表达式 二、文本三剑客 1. grep 1.1 定义 1.2 语法 1.3 选项 1.4 示例 2. sed 2.1 定义 2.2 通式 2.3 选项 2.4 脚本格式&#xff08;脚本语法&#xff09; 2.…