Spring 使用SSE(Server-Sent Events)学习
什么是SSE
SSE 即服务器发送事件(Server-Sent Events),是一种服务器推送技术,允许服务器在客户端建立连接后,主动向客户端推送数据。
SSE 基于 HTTP 协议,使用简单,具有轻量级、实时性和断线重连等特点。它在一些需要实时数据更新的场景中非常有用,如股票行情、实时通知等。与传统的轮询方式相比,SSE 可以减少不必要的网络请求,提高数据传输效率。
SSE 的主要优点包括:
实时性:服务器可以实时推送数据到客户端,无需客户端不断轮询。
轻量级:SSE 使用简单的文本协议,数据量小,对网络带宽要求较低。
兼容性好:SSE 基于 HTTP 协议,大多数现代浏览器都支持。
易于实现:服务器端和客户端的实现都相对简单。
然而,SSE 也有一些局限性:
单向通信:SSE 只允许服务器向客户端推送数据,客户端无法直接向服务器发送数据。
支持的浏览器有限:虽然大多数现代浏览器支持 SSE,但一些较旧的浏览器可能不支持。
数据格式受限:SSE 通常只能传输文本数据,对于二进制数据的支持有限。
与 HTTP 相比,SSE 提供了更高效的实时数据推送机制,减少了不必要的请求和响应,降低了服务器负载。但 HTTP 更适合一般性的请求-响应模式的数据传输。
SSE WebSocket 对比
SSE 的优点:
- 简单易用:SSE 使用标准的 HTTP 协议,实现相对简单,不需要复杂的握手和协议转换。
- 单向通信:适合只需从服务器向客户端推送数据的场景,减少了不必要的双向通信开销。
- 低延迟:由于基于 HTTP 协议,数据可以在服务器有新数据时立即推送,延迟较低。
- 兼容性好:大多数现代浏览器都支持 SSE,不需要特殊的插件或扩展。
- 轻量级:相比 WebSocket,SSE 的实现相对较轻量,对服务器资源的消耗较少。
- 自动重连:如果连接中断,SSE 会自动尝试重新连接,确保数据的持续推送。
SSE 的缺点:
- 单向通信限制:SSE 只支持服务器向客户端发送数据,客户端无法向服务器发送数据。
- 数据格式受限:SSE 通常只能发送文本数据,对于二进制数据的支持有限。
- 连接管理:每个 SSE 连接在每次数据推送后都会关闭,然后需要重新建立连接,这可能会导致一些额外的开销。
** WebSocket 的优点:**
- 全双工通信:支持双向通信,客户端和服务器可以随时互相发送数据,适用于实时交互性较高的应用。
- 低延迟:建立连接后,数据可以实时传输,延迟较低。
- 二进制支持:WebSocket 可以发送文本和二进制数据,更适合处理多媒体等二进制数据。
- 较少的 HTTP 开销:由于建立了持久连接,减少了 HTTP 请求头和响应头的开销。
WebSocket 的缺点:
- 协议复杂性:WebSocket 协议相对较复杂,需要更多的代码和服务器资源来处理连接和数据传输。
- 兼容性问题:虽然大多数现代浏览器支持 WebSocket,但在一些旧版本的浏览器或特定环境中可能存在兼容性问题。
- 安全风险:由于 WebSocket 可以实现双向通信,需要注意安全问题,如防止跨站脚本攻击(XSS)和跨站请求伪造(CSRF)。
- 服务器资源消耗:相比 SSE,WebSocket 可能会消耗更多的服务器资源,特别是在处理大量并发连接时。
SSE 适用于简单的单向数据推送场景,如新闻更新、实时通知等,而 WebSocket 更适合需要双向实时通信的场景,如在线聊天、实时游戏等。在选择使用哪种技术时,需要根据具体的应用需求、浏览器兼容性和服务器资源等因素进行综合考虑
效果演示
话不多说。直接上代码
Controller
package cn.ideamake.feishu.web.controller.sse;import cn.hutool.core.thread.ThreadUtil;
import cn.ideamake.common.response.Result;
import cn.ideamake.feishu.pojo.dto.SseMessageDTO;
import cn.ideamake.feishu.service.sse.SseService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import javax.validation.Valid;/*** @author Barcke* @version 1.0* @projectName feishu-application* @className SseController* @date 2024/6/5 10:14* @slogan: 源于生活 高于生活* @description:**/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/sse")
@Validated
public class SseController {private final SseService sseService;/*** 创建sse链接* @param clientId* @return*/@GetMapping("/createConnect")public SseEmitter createConnect(String clientId) {return sseService.createConnect(clientId);}/*** 给所有客户端发送消息* @param msg* @return*/@PostMapping("/broadcast")public Result<Boolean> sendMessageToAllClient(@RequestBody String msg) {ThreadUtil.execute(() -> {sseService.sendMessageToAllClient(msg);});return Result.ok(true);}/*** 给指定端发送消息* @param sseMessageDTO* @return*/@PostMapping("/sendMessage")public Result<Boolean> sendMessageToOneClient(@RequestBody @Valid SseMessageDTO sseMessageDTO) {ThreadUtil.execute(() -> {sseService.sendMessageToOneClient(sseMessageDTO.getClientId(), sseMessageDTO.getData());});return Result.ok(true);}/*** 关闭链接* @param clientId* @return*/@GetMapping("/closeConnect")public Result<Boolean> closeConnect(@RequestParam("clientId") String clientId) {ThreadUtil.execute(() -> {sseService.closeConnect(clientId);});return Result.ok(true);}}
Service
package cn.ideamake.feishu.service.sse;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;/*** @author Barcke* @version 1.0* @projectName feishu-application* @className SseService* @date 2024/6/5 10:18* @slogan: 源于生活 高于生活* @description:**/
public interface SseService {/*** 创建连接** @param clientId 客户端ID*/SseEmitter createConnect(String clientId);/*** 根据客户端id获取SseEmitter对象** @param clientId 客户端ID*/SseEmitter getSseEmitterByClientId(String clientId);/*** 发送消息给所有客户端** @param msg 消息内容*/void sendMessageToAllClient(String msg);/*** 给指定客户端发送消息** @param clientId 客户端ID* @param msg 消息内容*/void sendMessageToOneClient(String clientId, String msg);/*** 关闭连接** @param clientId 客户端ID*/void closeConnect(String clientId);}
ServiceImpl
package cn.ideamake.feishu.service.sse.impl;import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.ideamake.feishu.pojo.dto.SseMessageDTO;
import cn.ideamake.feishu.service.sse.SseService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.io.IOException;
import java.util.Map;
import java.util.function.Consumer;/*** @author Barcke* @version 1.0* @projectName feishu-application* @className SseServiceImpl* @date 2024/6/5 10:18* @slogan: 源于生活 高于生活* @description:**/
@Slf4j
@RequiredArgsConstructor
@Service
public class SseServiceImpl implements SseService {/*** 容器,保存连接,用于输出返回 ;可使用其他方法实现*/private static final Map<String, SseEmitter> SSE_CACHE = MapUtil.newConcurrentHashMap();/*** 重试次数*/private final Integer RESET_COUNT = 3;/*** 重试等待事件 单位 ms*/private final Integer RESET_TIME = 5000;/*** 根据客户端id获取SseEmitter对象** @param clientId 客户端ID*/@Overridepublic SseEmitter getSseEmitterByClientId(String clientId) {return SSE_CACHE.get(clientId);}/*** 创建连接** @param clientId 客户端ID*/@Overridepublic SseEmitter createConnect(String clientId) {// 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutExceptionSseEmitter sseEmitter = new SseEmitter(0L);// 是否需要给客户端推送IDif (StrUtil.isBlank(clientId)) {clientId = IdUtil.simpleUUID();}// 注册回调// 长链接完成后回调接口(即关闭连接时调用)sseEmitter.onCompletion(completionCallBack(clientId));// 连接超时回调sseEmitter.onTimeout(timeoutCallBack(clientId));// 推送消息异常时,回调方法sseEmitter.onError(errorCallBack(clientId));SSE_CACHE.put(clientId, sseEmitter);log.info("创建新的sse连接,当前用户:{} 累计用户:{}", clientId, SSE_CACHE.size());try {// 注册成功返回用户信息sseEmitter.send(SseEmitter.event().id(String.valueOf(HttpStatus.HTTP_CREATED)).data(clientId, MediaType.APPLICATION_JSON));} catch (IOException e) {log.error("创建长链接异常,客户端ID:{} 异常信息:{}", clientId, e.getMessage());}return sseEmitter;}/*** 发送消息给所有客户端** @param msg 消息内容*/@Overridepublic void sendMessageToAllClient(String msg) {if (MapUtil.isEmpty(SSE_CACHE) || StringUtils.isBlank(msg)) {return;}// 判断发送的消息是否为空for (Map.Entry<String, SseEmitter> entry : SSE_CACHE.entrySet()) {SseMessageDTO sseMessageDTO = new SseMessageDTO();sseMessageDTO.setClientId(entry.getKey());sseMessageDTO.setData(msg);sendMsgToClientByClientId(entry.getKey(), sseMessageDTO, entry.getValue());}}/*** 给指定客户端发送消息** @param clientId 客户端ID* @param msg 消息内容*/@Overridepublic void sendMessageToOneClient(String clientId, String msg) {SseMessageDTO sseMessageDTO = new SseMessageDTO(clientId, msg);sendMsgToClientByClientId(clientId, sseMessageDTO, SSE_CACHE.get(clientId));}/*** 关闭连接** @param clientId 客户端ID*/@Overridepublic void closeConnect(String clientId) {SseEmitter sseEmitter = SSE_CACHE.get(clientId);if (sseEmitter != null) {sseEmitter.complete();removeUser(clientId);}}/*** 推送消息到客户端* 此处做了推送失败后,重试推送机制,可根据自己业务进行修改** @param clientId 客户端ID* @param sseMessageDTO 推送信息,此处结合具体业务,定义自己的返回值即可**/private void sendMsgToClientByClientId(String clientId, SseMessageDTO sseMessageDTO, SseEmitter sseEmitter) {if (sseEmitter == null) {log.error("推送消息失败:客户端{}未创建长链接,失败消息:{}", clientId, sseMessageDTO);return;}SseEmitter.SseEventBuilder sendData = SseEmitter.event().id(String.valueOf(HttpStatus.HTTP_OK)).data(sseMessageDTO, MediaType.APPLICATION_JSON);try {sseEmitter.send(sendData);} catch (IOException e) {// 推送消息失败,记录错误日志,进行重推log.error("推送消息失败:{},尝试进行重推", sseMessageDTO);boolean isSuccess = true;// 推送消息失败后,每隔10s推送一次,推送5次for (int i = 0; i < RESET_COUNT; i++) {try {Thread.sleep(RESET_TIME);sseEmitter = SSE_CACHE.get(clientId);if (sseEmitter == null) {log.error("{}的第{}次消息重推失败,未创建长链接", clientId, i + 1);continue;}sseEmitter.send(sendData);} catch (Exception ex) {log.error("{}的第{}次消息重推失败", clientId, i + 1, ex);continue;}log.info("{}的第{}次消息重推成功,{}", clientId, i + 1, sseMessageDTO);return;}}}/*** 长链接完成后回调接口(即关闭连接时调用)** @param clientId 客户端ID**/private Runnable completionCallBack(String clientId) {return () -> {log.info("结束连接:{}", clientId);removeUser(clientId);};}/*** 连接超时时调用** @param clientId 客户端ID**/private Runnable timeoutCallBack(String clientId) {return () -> {log.info("连接超时:{}", clientId);removeUser(clientId);};}/*** 推送消息异常时,回调方法** @param clientId 客户端ID**/private Consumer<Throwable> errorCallBack(String clientId) {return throwable -> {log.error("SseEmitterServiceImpl[errorCallBack]:连接异常,客户端ID:{}", clientId);// 推送消息失败后,每隔10s推送一次,推送5次for (int i = 0; i < RESET_COUNT; i++) {try {Thread.sleep(RESET_TIME);SseEmitter sseEmitter = SSE_CACHE.get(clientId);if (sseEmitter == null) {log.error("SseEmitterServiceImpl[errorCallBack]:第{}次消息重推失败,未获取到 {} 对应的长链接", i + 1, clientId);continue;}sseEmitter.send("失败后重新推送");} catch (Exception e) {log.error("sse推送消息异常", e);}}};}/*** 移除用户连接** @param clientId 客户端ID**/private void removeUser(String clientId) {SSE_CACHE.remove(clientId);log.info("SseEmitterServiceImpl[removeUser]:移除用户:{}", clientId);}
}
DTO
package cn.ideamake.feishu.pojo.dto;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;import javax.validation.constraints.NotNull;/*** @author Barcke* @version 1.0* @projectName feishu-application* @className SseMessageDTO* @date 2024/6/5 10:19* @slogan: 源于生活 高于生活* @description:**/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Accessors(chain = true)
public class SseMessageDTO {/*** 客户端id*/@NotNull(message = "客户端id 不能为空")private String clientId;/*** 传输数据体(json)*/private String data;}
相关文章:
Spring 使用SSE(Server-Sent Events)学习
什么是SSE SSE 即服务器发送事件(Server-Sent Events),是一种服务器推送技术,允许服务器在客户端建立连接后,主动向客户端推送数据。 SSE 基于 HTTP 协议,使用简单,具有轻量级、实时性和断线重…...
词法分析器的设计与实现--编译原理操作步骤,1、你的算法工作流程图; 2、你的函数流程图;3,具体代码
实验原理: 词法分析是编译程序进行编译时第一个要进行的任务,主要是对源程序进行编译预处理之后,对整个源程序进行分解,分解成一个个单词,这些单词有且只有五类,分别时标识符、关键字(保留字&a…...
linux查看磁盘类型命令
在Linux中,有多种方法可以查看磁盘是固态硬盘(SSD)还是机械硬盘(HDD)。以下是一些常用的方法: 查看/sys/block/目录 /sys/block/目录包含了系统中所有块设备的信息。你可以查看这个目录中的设备属性来判断…...
多线程调用同一个不包含可变状态,并且是线程安全的方法时,可同时执行,不必等待排队
多线程调用同一个不包含可变状态,并且是线程安全的方法时,可同时执行,不必等待排队 前言同时执行方法的条件示例并发执行的优势实验验证总结 前言 如果方法不包含可变状态,并且是线程安全的,那么在高并发环境下&#…...
Java文件操作①——XML文件的读取
系列文章目录 文章目录 系列文章目录前言一、邂逅XML二、应用 DOM 方式解析 XML三、应用 SAX 方式解析 XML四、应用 DOM4J 及 JDOM 方式解析 XMLJDOM 方式解析 XMLDOM4J 方式解析 XML前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。…...
【记录】网络|没有路由器没有网线,分别使用手机或Windows电脑共享网络给ARM64开发板,应急连接
事情是这样的,我的开发板明明已经选择了记住热点 WiFi 密码,但是却没有在开机的时候自动连接,我又没有放显示器在身边,又不想为了这点事去找个显示器来,就非常难受。 我手边有的设备是: 笔记本电脑&#…...
一键设置常用纸张和页面边距-Word插件-大珩助手
Word大珩助手是一款功能丰富的Office Word插件,旨在提高用户在处理文档时的效率。它具有多种实用的功能,能够帮助用户轻松修改、优化和管理Word文件,从而打造出专业而精美的文档。 【新功能】常用纸张和常用边距 1、一键设定符合中国人常用…...
在树莓派3B+中下载opencv(遇到的各种问题及解决)
目录 前言 1、删除原版本下新版本 2、python虚拟环境 3、python版本共存换链接——给版本降低 4、烧录之前版本的文件(在清华源中可以找,不用官网的烧录文件就行; 比如:(balenaEtcher)重新烧录有问题…...
精准检测,安全无忧:安全阀检测实践指南
安全阀作为一种重要的安全装置,在各类工业系统和设备中发挥着举足轻重的作用。 它通过自动控制内部压力,有效防止因压力过高而引发的设备损坏和事故风险,因此,对安全阀进行定期检测,确保其性能完好、工作可靠…...
Transformer系列:图文详解KV-Cache,解码器推理加速优化
前言 KV-Cache是一种加速Transformer推理的策略,几乎所有自回归模型都内置了KV-Cache,理解KV-Cache有助于更深刻地认识Transformer中注意力机制的工作方式。 自回归推理过程知识准备 自回归模型采用shift-right的训练方式,用前文预测下一个…...
基础篇03——SQL约束
概述 约束示例 完成以下案例: create table user (id int primary key auto_increment comment 主键,name varchar(10) not null unique comment 姓名,age tinyint unsigned check ( age > 0 and age < 120 ) comment 年龄,status char(1) default 1 commen…...
人工智能--深度神经网络
目录 🍉引言 🍉深度神经网络的基本概念 🍈神经网络的起源 🍍 神经网络的基本结构 🍉深度神经网络的结构 🍈 卷积神经网络(CNN) 🍈循环神经网络(RNN&…...
VOC格式标签各个字段的解释
想了解一下VOC格式数据标签各个字段的含义,搜了一圈没看到,懒得去官网了,直接问了GPT-4o,以下回答字段解析来自GPT-4o,例子我自己写的 VOC (Visual Object Classes) 数据标签格式主要用于目标检测任务。VOC格式的标签…...
2024年端午节放假通知
致尊敬的客户以及全体同仁: 2024年端午节将至,根据国务院办公厅通知精神,结合公司的实际情况,现将放假事宜通知如下: 2024年6月8日(星期六)至6月10日(星期一)ÿ…...
Transformer系列:注意力机制的优化,MQA和GQA原理简述
前言 多查询注意力(MQA)、分组查询注意力(GQA)是Transformer中多头注意力(MHA)的变种,它们大幅提高了解码器的推理效率,在LLaMA-2,ChatGLM2等大模型中有广泛使用,本篇介绍MQA、GQA的原理并分析其源码实现。 使用MQA,G…...
Python知识点11---高阶函数
提前说一点:如果你是专注于Python开发,那么本系列知识点只是带你入个门再详细的开发点就要去看其他资料了,而如果你和作者一样只是操作其他技术的Python API那就足够了。 本篇介绍一下Python的内置函数也叫高阶函数,就是Python自…...
JavaSE——【逻辑控制】(习题)
一、分支结构 2.1 if 语句 【练习】2.1.1 小明,如果这次考到90分以上,给你奖励一个大鸡腿,否则奖你一个大嘴巴子 int score 92;if(score > 90){System.out.println("吃个大鸡腿!!!");}else{System.out.println("挨大嘴…...
自动驾驶仿真:python和carsim联合仿真案例
文章目录 前言一、Carsim官方案例二、Carsim配置1、车辆模型2、procedure配置3、Run Control配置 三、python编写四、运行carsim五、运行python总结 前言 carsim内部有许多相关联合仿真的demo,simulink、labview等等都有涉及,这里简单介绍下python和car…...
Qt报错:libvlc开发的程序,出现Direct3D output全屏窗口
问题描述: 在qt中开发重播模块时,第一次在窗口正常播放,点击重播按钮后会弹出新的Direct3D output窗口播放视频 分析: 因为libvlc_media_player_set_hwnd 这个函数 设置了不存在的窗口句柄,导致vlc视频播放窗口没有嵌…...
yolov5的口罩识别系统+GUI界面 (附代码)
基于YOLOv5模型的口罩识别系统,结合了GUI界面,旨在帮助用户快速、准确地识别图像或视频中佩戴口罩的情况。YOLOv5是一种流行的目标检测模型,具有高效的实时检测能力,而GUI界面则提供了友好的用户交互界面,使得整个系统…...
WPF中Window的外观实现及常用属性
文章目录 1. 概要2. Window的外观2.1 Window的外观组成2.2 Window的实现2.3 Window外观配置2.4 Window 的其他常用属性1. AllowsTransparency 2. WindowStartupLocation3. ShowInTaskbar4. ShowActivated5. SizeToContent6. Topmost7. WindowStyle 1. 概要 和 Android 类似, W…...
(有代码示例)Vue 或 JavaScript中使用全局通信的3种方式
在 Vue 或 JavaScript 应用中,可以使用以下库来实现全局事件通信: Vue.js 中的 EventBus: 在 Vue.js 中,可以使用 EventBus 来实现全局事件通信。EventBus 是一个 Vue 实例,用于在组件之间传递事件。你可以使用 $on、…...
MAB规范(1):概览介绍
前言 MATLAB的MAAB(MathWorks Automotive Advisory Board)建模规范是一套由MathWorks主导的建模指南,旨在提高基于Simulink和Stateflow进行建模的代码质量、可读性、可维护性和可重用性。这些规范最初是由汽车行业的主要厂商共同制定的&…...
基于振弦采集仪的土木工程安全监测技术研究
基于振弦采集仪的土木工程安全监测技术研究 随着土木工程的发展,安全监测成为了非常重要的一部分。土木工程的安全监测旨在及早发现结构的变形、位移、振动等异常情况,以便及时采取措施进行修复或加固,从而保障工程的安全运行。振弦采集仪作…...
这个高考作文满分的极客,想和你聊聊新媒体写作
计育韬 曾为上海市高考作文满分考生 微信官方 SVG AttributeName 开发者 新榜 500 强运营人 复旦大学青年智库讲师 浙江传媒学院客座导师 上海团市委新媒体顾问 上海市金山区青联副主席 文案能力,从来就不是一蹴而就的。今天,来和大家聊聊当年我的…...
AI推介-多模态视觉语言模型VLMs论文速览(arXiv方向):2024.05.25-2024.05.31
文章目录~ 1.Empowering Visual Creativity: A Vision-Language Assistant to Image Editing Recommendations2.Bootstrap3D: Improving 3D Content Creation with Synthetic Data3.Video-MME: The First-Ever Comprehensive Evaluation Benchmark of Multi-modal L…...
如何通过Python SMTP配置示例发附件邮件?
Python SMTP配置的步骤?SMTP服务器的优缺点有哪些? 当我们需要发送包含附件的邮件时,自动化的解决方案显得尤为重要。Python提供了SMTP库,使我们能够轻松配置并发送带有附件的邮件。AokSend将通过一个示例来展示如何操作…...
amd64
MD64,或"x64",是一种64位元的电脑处理器架构。它是基于现有32位元的x86架构,由AMD公司所开发,应用AMD64指令集的自家产品有Athlon(速龙) 64、Athlon 64 FX、Athlon 64 X2、Turion(炫龙) 64、Opteron(皓龙)、Sempron(闪龙…...
2024如何优化SEO?
在2024年的今天,要问我会如何优化seo,我会专注于几个关键的方面。首先,随着AI内容生成技术的发展,我会利用这些工具来帮助创建或优化我的网站内容,但是,随着谷歌3月份的算法更新,纯粹的ai内容可…...
【NoSQL数据库】Redis命令、持久化、主从复制
Redis命令、持久化、主从复制 redis配置 Redis命令、持久化、主从复制Redis数据类型redis数据库常用命令redis多数据库常用命令1、多数据库间切换2、多数据库间移动数据3、清除数据库内数据 key命令1、keys 命令2、判断键值是否存在exists3、删除当前数据库的指定key del4、获取…...
企业建站团队/网上接单平台有哪些
信息发达的今天,作为人们工作与生活的必不可少的工具,邮件技术与相关人才是何其重要。所以按纳不住,抛一块砖,希望能引起大家的共同关注,多多共同研究邮件方面的技术。 邮件服器软件种类繁多,但大都离不开S…...
buddypress搭建的中文社区网站/百度seo排名报价
声明:本篇博客的图来源于: https://blog.csdn.net/qq_35433716/article/details/89710720 一、相关名词说明 度:结点拥有的子树数;(树的度是树内各节点的度的最大值) 叶节点或终端结点:度为0的…...
洛阳网站建设/扬州网络推广哪家好
asp.net操作Excel是B/S架构开发报表中经常遇到的,现对常见操作总结如下:DataGrid输出到Excel并进行格式化处理 用Xml2OleDb将XML文件插入到数据库在C#中利用Excel做高级报表在ASP.NET中将数据直接输出成Excel内容在.NET环境下将报表导出Excel和Word 将DataGrid输出到Excel文件把…...
新疆建设工程信息网官方网站/安卓神级系统优化工具
为了方便大家对博客园的捐助, 现在采用网上支付的方式进行捐款。 网上支付平台:网银在线 网上支付地址:http://www.cnblogs.com/begincontribute.aspx 捐助者签名:http://www.cnblogs.com/dudu/articles/134340.html 详…...
手机投资网站/广东网络seo推广公司
我感觉我是一个比较有发言权的人吧,我在测试行业爬模滚打5年,从点点点的功能测试到现在成为测试开发工程师,工资也翻了几倍; 个人觉得,测试的前景并不差,只要自己肯努力;我刚出来的时候是在鹅厂…...
wordpress博客添加到菜单/小红书新媒体营销案例分析
本题比较困难的地方对于我来说应该是输入,后来参考了别人的代码原来还有stringstream这么好用的东西 还有就是方向的处理上面,lrj的方法比较巧妙,他是把方向全部转化成0-3然后一一对应数组中的元素 #include<iostream>//不需要判断边…...