当前位置: 首页 > news >正文

21.添加websocket模块

这里默认读者了解websocket协议,若是还不了解可以看下这篇文章wesocket协议。

websocket主要有三个步骤,1通过HTTP进行握手连接,2进行双向通信,3.协商断开连接

第一步的握手连接需要HTTP,所以还需要使用到上一节讲解的HTTP模块中的部分内容HttpContext类和HttpRequest类。

建立握手连接后,就不再需要使用HTTP了。之后就是通过帧的形式就行数据传输。

那可以给数据帧或者说是数据包封装成一个类WebsocketPacket。

1.WebsocketPacket类

该类一定是有帧头的一些信息,如fin,opcode等等。

enum WSOpcodeType : uint8_t
{WSOpcode_Continue = 0x0,WSOpcode_Text = 0x1,WSOpcode_Binary = 0x2,WSOpcode_Close = 0x8,WSOpcode_Ping = 0x9,WSOpcode_Pong = 0xA,
};class WebsocketPacket
{
public:WebsocketPacket():fin_(1)	//1表示是消息的最后一个分片,表示不分包, rsv1_(0), rsv2_(0), rsv3_(0), opcode_(1)	//默认是发送文本帧, mask_(0), payload_length_(0){memset(masking_key_, 0, sizeof(masking_key_));}~WebsocketPacket(){ }void reset(){fin_ = 1;	//默认是1rsv1_ = 0;rsv2_ = 0;rsv3_ = 0;opcode_ = 1;//默认是发送文本帧mask_ = 0;memset(masking_key_, 0, sizeof(masking_key_));payload_length_ = 0;}void decodeFrame(Buffer* frameBuf, Buffer* output);void encodeFrame(Buffer* output, Buffer* data)const;public:uint8_t fin() const { return fin_; }uint8_t rsv1() const { return rsv1_; }uint8_t rsv2()const { return rsv2_; }//省略部分成员的的获取函数如rsv3()等等,这里就没有显示出来,可查看完整代码//...................void set_fin(uint8_t fin) { fin_ = fin; }void set_rsv1(uint8_t rsv1) { rsv1_ = rsv1; }void set_rsv2(uint8_t rsv2) { rsv2_ = rsv2; }//省略部分成员的设置的函数如set_rsv3(uint8_t rsv3)等等,这里就没有显示出来private:uint8_t fin_;uint8_t rsv1_;uint8_t rsv2_;uint8_t rsv3_;uint8_t opcode_;uint8_t mask_;uint8_t masking_key_[4];uint64_t payload_length_;
};

这里重点就是两个函数decodeFrameencodeFrame。从名字就可以看出来,一个是解帧,即是解析客户端发送过来的帧;另一个是封装成帧,发送给客户端。

1.1decodeFrame函数

按照websocket协议的数据帧进行解析即可。

这里要注意的是,若payloadlength是多字节的话,需要进行转序。

有掩码的操作就是这样,可以不用做过多了解,但想了解多点也可以。

void WebsocketPacket::decodeFrame(Buffer* frameBuf,Buffer* output)
{const char* msg = frameBuf->peek();int pos = 0;//获取fin_fin_=((unsigned char)msg[pos] >> 7);//获取opcode_opcode_ = msg[pos] & 0x0f;pos++;//获取mask_mask_ = (unsigned char)msg[pos] >> 7;//获取payload_length_payload_length_ = msg[pos] & 0x7f;pos++;if (payload_length_ == 126) {uint16_t length = 0;memcpy(&length, msg + pos, 2);pos += 2;payload_length_ = ntohs(length);}else if (payload_length_ == 127) {uint64_t length = 0;memcpy(&length, msg + pos, 8);pos += 8;payload_length_ = ntohl(length);}//获取masking_key_if (mask_ == 1) {for (int i = 0; i < 4; i++)masking_key_[i] = msg[pos + i];pos += 4;}if (mask_ != 1) {output->append(msg + pos, payload_length_);}else {for (uint64 i = 0; i < payload_length_; i++) {output->append(msg[pos + i] ^ masking_key_[i % 4], payload_length_);}}}

1.2encodeFrame

也是按照websocket协议的数据帧进行封装帧即可。

注意是若payloadlength是多字节的话,需要进行转序。

还有服务器端发送的是没有掩码的。

void WebsocketPacket::encodeFrame(Buffer* output,Buffer* data)const
{uint8_t onebyte = 0;onebyte |= (fin_ << 7);onebyte |= (rsv1_ << 6);onebyte |= (rsv2_ << 5);onebyte |= (rsv3_ << 4);onebyte |= (opcode_ & 0x0F);output->append((char*)&onebyte, 1);onebyte = 0;//set mask flagonebyte = onebyte | (mask_ << 7);int length = data->readableBytes();if (length < 126){onebyte |= length;output->append((char*)&onebyte, 1);}else if (length == 126){onebyte |= length;output->append((char*)&onebyte, 1);auto len = htons(length);output->append((char*)&len, 2);}else if (length == 127){onebyte |= length;output->append((char*)&onebyte, 1);// also can use htonll if you have itonebyte = (payload_length_ >> 56) & 0xFF;output->append((char*)&onebyte, 1);onebyte = (payload_length_ >> 48) & 0xFF;output->append((char*)&onebyte, 1);onebyte = (payload_length_ >> 40) & 0xFF;output->append((char*)&onebyte, 1);onebyte = (payload_length_ >> 32) & 0xFF;output->append((char*)&onebyte, 1);onebyte = (payload_length_ >> 24) & 0xFF;output->append((char*)&onebyte, 1);onebyte = (payload_length_ >> 16) & 0xFF;output->append((char*)&onebyte, 1);onebyte = (payload_length_ >> 8) & 0xFF;output->append((char*)&onebyte, 1);onebyte = payload_length_ & 0XFF;output->append((char*)&onebyte, 1);}if (mask_ == 1)	//服务器发送给客户端的,是不带mask_key的,所以这个是没有用到的{output->append((char*)masking_key_, 4);	// save masking keychar value = 0;for (uint64_t i = 0; i < payload_length_; ++i) {value = *(char*)(data->peek());data->retrieve(1);value = value ^ masking_key_[i % 4];output->append(&value, 1);}}else {output->append(data->peek(), data->readableBytes());}
}

数据帧解析和封装说完了,那就到握手连接和双向通信的了。可以封装个类WebsocketContext。

2.WebsocketContext类

该类有点类似上一节的HttpContext类,解包和封包的操作已有WebsocketPacket去处理。那这个类需要处理握手连接等问题。

WebsocketContext会拥有WebsocketPacket类型的请求包requestPacket_。其中函数parseData就是调用requestPacket_的decodeFrame

websocketStatus_表示是否已握手连接的,构造函数是默认kUnconnect的。

class WebsocketContext {
public:enum class WebsocketSTATUS { kUnconnect, kHandsharked };WebsocketContext();~WebsocketContext();void handleShared(Buffer* buf, const std::string& server_key);void parseData(Buffer* buf, Buffer* output);void reset() { requestPacket_.reset(); }void setwebsocketHandshared() { websocketStatus_ = WebsocketSTATUS::kHandsharked; }WebsocketSTATUS getWebsocketSTATUS()const { return websocketStatus_; }uint8_t getRequestOpcode()const { return requestPacket_.opcode(); }private:WebsocketSTATUS websocketStatus_;WebsocketPacket requestPacket_;
};

那么接下来看看如何握手连接的

2.1handleShared

源代码里会有base64和sha1的代码。

这里就主要是按照给定的服务器端回复的握手格式进行回复。

#include "base64.h"
#include "sha1.h"#define MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"void WebsocketContext::handleShared(Buffer* buf, const std::string& serverKey)
{buf->append("HTTP/1.1 101 Switching Protocols\r\n");buf->append("Connection: upgrade\r\n");buf->append("Sec-WebSocket-Accept: ");std::string server_key = serverKey;server_key += MAGIC_KEY;SHA1 sha;unsigned int message_digest[5];sha.Reset();sha << server_key.c_str();sha.Result(message_digest);for (int i = 0; i < 5; i++) {message_digest[i] = htonl(message_digest[i]);}server_key = base64_encode(reinterpret_cast<const unsigned char*>(message_digest), 20);server_key += "\r\n";buf->append(server_key);buf->append("Upgrade: websocket\r\n\r\n");
}

3.websocketServer

接着封装一个websocketServer类方便使用。这个类和HttpServer是很相似的,流程和HttpServer也是差不多的。

class websocketServer
{
public:using WebsocketCallback = std::function<void(const Buffer*, Buffer*, WebsocketPacket& respondPacket)>;websocketServer(EventLoop* loop, const InetAddr& listenAddr);void setHttpCallback(const WebsocketCallback& cb) { websocketCallback_ = cb; }void start(int numThreads);private:void onConnetion(const ConnectionPtr& conn);	//连接到来的回调函数void onMessage(const ConnectionPtr& conn, Buffer* buf);	//消息到来的回调函数void handleData(const ConnectionPtr& conn, WebsocketContext* websocket, Buffer* buf);Server server_;WebsocketCallback websocketCallback_;
};

setHttpCallback是设置用户的业务函数。

3.1onConnetion函数

连接到来时候会执行该函数

void websocketServer::onConnetion(const ConnectionPtr& conn)
{if (conn->connected()) {//conn->setContext(HttpContext());   //这是之前HttpServer的conn->setContext(WebsocketContext());//测试使用,用来测试绑定不符合的类型//int a = 10;  conn->setContext(a);}
}

3.2onMessage

消息到来的时候会执行该函数。

该函数就先获取该conn的getMutableContext,得到该WebsocketContext类对象。

之后就两种情况,一种是还没进行握手的,一种是已进行握手的,进行通信的。

需要握手的 ,先通过解析http请求,获取请求头中的特定字段 ,发送特殊的HTTP响应头进行握手确认。

void websocketServer::onMessage(const ConnectionPtr& conn, Buffer* buf)
{auto context = std::any_cast<WebsocketContext>(conn->getMutableContext());	//c++117if (!context) {printf("context kong...\n");LOG_ERROR << "context is bad\n";return;}if (context->getWebsocketSTATUS() == WebsocketContext::WebsocketSTATUS::kUnconnect) {HttpContext http;if (!http.parseRequest(buf)) {conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");conn->shutdown();}if (http.gotAll()) {auto httpRequese = http.request();if (httpRequese.getHeader("Upgrade") != "websocket" ||httpRequese.getHeader("Connection") != "Upgrade" ||httpRequese.getHeader("Sec-WebSocket-Version") != "13" ||httpRequese.getHeader("Sec-WebSocket-Key") == "") {conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");conn->shutdown();return;		//表明不是websocket连接}Buffer handsharedbuf;context->handleShared(&handsharedbuf, http.request().getHeader("Sec-WebSocket-Key"));conn->send(&handsharedbuf);context->setwebsocketHandshared();//设置建立握手}}else {handleData(conn, context, buf);}
}

另一种情况,可以进行通信的,调用函数handleData

主要流程:

  1. 先调用websocketContext的解析帧的函数parseData。之后得到fin,opcode等信息并把传输过来的数据写入到DataBuf中去。
  2. 之后再根据情况进行设置opcode。
  3. 之后再调用用户设置的回调函数来进行用户的业务处理。
  4. 再进行封装帧操作,发送给客户端。

这里需要注意的是:当收到客户主动发送过来的opcode是0x8(即是关闭),需要服务器端也返回ox8给客户端。因为websocket关闭是双方协商的。之后客户端收到0x8后就会关闭连接了。

void websocketServer::handleData(const ConnectionPtr& conn, WebsocketContext* websocket, Buffer* buf)
{Buffer DataBuf;websocket->parseData(buf, &DataBuf);WebsocketPacket respondPacket;int opcode = websocket->getRequestOpcode();switch (opcode){case WSOpcodeType::WSOpcode_Continue:respondPacket.set_opcode(WSOpcodeType::WSOpcode_Continue);break;case WSOpcodeType::WSOpcode_Text:respondPacket.set_opcode(WSOpcodeType::WSOpcode_Text);break;case WSOpcodeType::WSOpcode_Binary:respondPacket.set_opcode(WSOpcodeType::WSOpcode_Binary);break;case WSOpcodeType::WSOpcode_Close:respondPacket.set_opcode(WSOpcodeType::WSOpcode_Close);break;case WSOpcodeType::WSOpcode_Ping:respondPacket.set_opcode(WSOpcodeType::WSOpcode_Pong);	//进行心跳响应break;case WSOpcodeType::WSOpcode_Pong:		//表示这是一个心跳响应(pong),那就不用回复了return;default:LOG_INFO << "WebSocketEndpoint - recv an unknown opcode.\n";return;}Buffer sendbuf;if(opcode != WSOpcodeType::WSOpcode_Close && opcode != WSOpcode_Ping && opcode != WSOpcode_Pong)websocketCallback_(&DataBuf, &sendbuf, respondPacket);Buffer frameBuf;respondPacket.encodeFrame(&frameBuf, &sendbuf);conn->send(&frameBuf);websocket->reset();
}

4.websocket的使用例子

用户主要就是写自己的业务函数,之后调用setHttpCallback设置自己的业务函数。

//用户的业务函数
void onRequest(const Buffer* input, Buffer* output){//进行echo回复output->append(input->peek(),input->readableBytes());
}int main(int argc, char* argv[])
{int numThreads = 0;if (argc > 1) {Logger::setLogLevel(Logger::LogLevel::WARN);numThreads = atoi(argv[1]);}EventLoop loop;websocketServer server(&loop, InetAddr(9999));server.setHttpCallback(onRequest);   //设置自己的业务函数server.start(numThreads);loop.loop();return 0;
}

websocket的服务器基本就是这样了。

5.修复问题,Connection::handleRead()中的问题

这是在测试websocket的时候发现的问题。

在有新消息到来的时刻,是会调用Connection::handleRead()函数

那么在该函数中需要添加inputBuffer_.retrieve(inputBuffer_.readableBytes());这句代码。

void Connection::handleRead()
{int savedErrno = 0;auto n = inputBuffer_.readFd(fd(), &savedErrno);if (n > 0) {//这个是用户设置好的函数messageCallback_(shared_from_this(), &inputBuffer_);//新添加的,没有这句代码的话,那readindex可能就没有变化,那读取的数据就会包含上一次的inputBuffer_.retrieve(inputBuffer_.readableBytes());//messageCallback_中处理好读取的数据后,更新readerIndex位置}else if (n == 0) {//表示客户端关闭了连接handleClose();}//....省略了对错误的处理
}

不然每次inputBuffer_的readerIndex就不会改变,那么每次input中获取到的数据都会包含上一次的数据

也可以不添加,让用户在写业务函数的时候手动添加去更新readerIndex,但这样就不方便了,用户不应该去处理这些问题的。

在server_v10代码中,加不加这句代码是没有影响的,是因为用户的业务函数使用了Buffer::retrieveAllAsString()函数,该函数是会更新buf的readerIndex的,所以才会没有问题的。

//在代码server_v10中用户的业务函数
void onMessage(const ConnectionPtr& conn, Buffer* buf) {std::string msg(buf->retrieveAllAsString());printf("onMessage() %ld bytes reveived:%s\n", msg.size(), msg.c_str());conn->send(msg);
}int main(){//..............
}

但不是每个用户编写自己的业务函数时候都一定使用这个函数的。所以需要在这添加这句代码inputBuffer_.retrieve(inputBuffer_.readableBytes());

可以试试不添加这句代码和添加了这句代码的websocket服务器的效果。

完整源代码:https://github.com/liwook/CPPServer/tree/main/code/server_v21

相关文章:

21.添加websocket模块

这里默认读者了解websocket协议&#xff0c;若是还不了解可以看下这篇文章wesocket协议。 websocket主要有三个步骤&#xff0c;1通过HTTP进行握手连接&#xff0c;2进行双向通信&#xff0c;3.协商断开连接 第一步的握手连接需要HTTP&#xff0c;所以还需要使用到上一节讲解…...

Linux UDP编程流程

文章目录 UDP编程流程UDP协议无连接的特点UDP协议数据报的特点 UDP编程流程 UDP 提供的是无连接、不可靠的、数据报服务。服务器端和客户端没有什么本质上的区别。编程流程如下&#xff1a; socket()用来创建套接字&#xff0c;使用 udp 协议时&#xff0c;选择数据报服务 SOC…...

【opencv】多版本安装

安装opencv3.2.0以及对应的付费模块 一、安装多版本OpenCV如何切换 按照如下步骤安装的OpenCV&#xff0c;在CMakeLists.txt文件中&#xff0c;直接指定opencv的版本就可以找到相应版本的OpenCV&#xff0c;为了验证可以在CMakeLists.txt文件中使用如下指令输出版本验证&…...

webpack打包常用配置项

webpack打包配置项 参考链接 文件结构&#xff1a;最基础版 先安装 npm i webpack webpack-cli --dev 运行命令&#xff1a;npx webpack 进行打包 1. 配置webpack.config.js文件&#xff1a; const path require(path); module.exports {mode: development, // 开发环境 …...

回归预测 | MATLAB实现MPA-BiGRU海洋捕食者算法优化双向门控循环单元多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现MPA-BiGRU海洋捕食者算法优化双向门控循环单元多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现MPA-BiGRU海洋捕食者算法优化双向门控循环单元多输入单输出回归预测&#xff08;多指标&#xff0c;多图&a…...

selenium_webdriver自动化测试指南

目录 1 引言 4 1.1 目的.. 4 1.2 背景.. 4 1.3 参考资料.. 4 2 安装并引用Selenium2. 5...

红米Note12Turbo解锁BL刷入PixelExperience原生ROM系统详细教程

红米Note12Turbo的兄弟是国外POCO F5 机型&#xff0c;并且该机性价比非常高&#xff0c;国内外销量也还可以&#xff0c;自然不缺第三方ROM适配。目前大家心心念念的原生PixelExperience已成功发布&#xff0c;并且相对来说&#xff0c;适配程度较高&#xff0c;已经达到日用的…...

NoSQL之Redis配置与优化(一)

关系数据库与非关系型数据库 &#xff1a; ●关系型数据库&#xff1a; 关系型数据库是一个结构化的数据库&#xff0c;创建在关系模型&#xff08;二维表格模型&#xff09;基础上&#xff0c;一般面向于记录。 SQL 语句&#xff08;标准数据查询语言&#xff09;就是一种基于…...

Boost搜索引擎

项目背景 先说一下什么是搜索引擎,很简单,就是我们平常使用的百度,我们把自己想要所有的内容输入进去,百度给我们返回相关的内容.百度一般给我们返回哪些内容呢?这里很简单,我们先来看一下. 搜索引擎基本原理 这里我们简单的说一下我们的搜索引擎的基本原理. 我们给服务器发…...

侧边栏的文章分类、热门文章和热门文章的展示(Go 搭建 qiucode.cn 之九)

早就有言,秋码记录 虽早已不是原来的面貌,但这终究是不防碍我们使用golang来搭建它。 而又为什么是使用golang呢?并非是其他编程语言呢?想必 时候回答【我为什么要学习 Go 语言(golang)】这个问题了 已经给出了答案! 当然,当初学习golang时,不单单是为了搭建一个博客应…...

LeetCode——贪心篇(二)

刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 134. 加油站 在一条环路上有 n 个加油站&#xff0c;其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&#xff0c;从第 i 个加油站开往第 i1 个加油站需要消耗…...

Linux find

1.find介绍 linux查找命令find是linux运维中很重要、很常用的命令之一&#xff0c;find用于根据指定条件的匹配参数来搜索和查找文件和目录列表&#xff0c;我们可以通过权限、用户、用户组、文件类型、日期、大小等条件来查找文件。 2.find语法 find语法 find [查找路径] …...

UE4实现断线重连功能

断线重连的整体逻辑是 设备离线后&#xff0c;根据需要决定是否保留pawn&#xff0c;还是设备重连后再重新生成一个&#xff0c;然后是断线重连时的验证方式&#xff0c;最后是playerstate重连后的属性保留 重载Playercontroller的PawnLeavingGame,这里是设备断线后&#xff0…...

nginx笔记

1. nginx 简介 nginx性能比apache强&#xff0c;体现 在io模型方面 76 Pv&#xff1a; UV : 不同浏览器是不同的UV GET 获取 POST 上传 HEAT 只看头 访问网站的流程 1.dns解析 2.cdn 3.tcp 4.web服务器 处理 建立连接 接收请求 处理请求 GET POST等 获取资源 构…...

动态库的制作和使用

动态库和静态库的工作原理 配置环境变量 方式1&#xff1a; 坏处&#xff1a;环境变量是临时的 方式2&#xff1a; 1 用户级别的配置&#xff1a; 进入到/home&#xff0c;找到.bashrc&#xff0c;进入 先去找到库的路径 然后再到.bashrc最后一行输入路径 使其生效 2 系统…...

AWS Glue Pyspark+Athena基础学习汇总

Pyspark 基础学习汇总篇🍎 一、AWS 架构 ① AWS Glue:工作平台,包括脚本的编写以及管理脚本的运行状态以及调度等(主要:数据库配置、ETL和数据转换脚本编写、调度) ② Amazon S3 数据湖(数仓):数据的存储 ③ Athena:(雅典娜)SQL直接编写查询工作台(会产生费用) ④ Q…...

智能合约安全新范式,超越 `require`和`assert`

智能合约安全新范式&#xff0c;超越 require_assert 摘要 不要只为特定的函数写 require 语句&#xff1b;为你的协议写 require 语句。函数遵循检查(requirements)-生效(Effects)-交互(INteractions)协议不变性(Invariants)或 FREI-PI 模式可以帮助你的合约更加安全&#x…...

【ESP-S3-BOX-Lite花屏问题】:Github下载源码(出厂源码factory_demo)编译调试到ESP-S3-BOX-Lite中出现花屏现象

项目场景&#xff1a; 最近拿到了一块乐鑫的 ESP-S3-BOX-Lite &#xff08;esp-box: ESP-BOX 是乐鑫信息科技&#xff09; 详细资料&#xff08;esp32_s3_box_lite&#xff09; 版本信息 ESP-BOX依赖的 ESP-IDF分支信息支持状态master> release/v5.1 commit id: 22cfbf3…...

Redis集群3.2.11离线安装详细版本(使用Ruby)

1.安装软件准备 1.Redis版本下载 Index of /releases/http://download.redis.io/releases/ 1.2gcc环境准备 GCC(GNU Compiler Collection,GNU编译器套件)是一套用于编译程序代码的开源编译器工具集。它的主要用途是将高级编程语言(如C、C++、Fortran等)编写的源代码转换…...

Ansible自动化运维

目录 前言 一、概述 常见的开源自动化运维工具比较 二、ansible环境搭建 三、ansible模块 &#xff08;一&#xff09;、hostname模块 &#xff08;二&#xff09;、file模块 &#xff08;三&#xff09;、copy模块 &#xff08;四&#xff09;、fetch模块 &#xff…...

MSTP + Eth-Trunk配置实验 华为实验手册

1.1 实验介绍 1.1.1 关于本实验 以太网是当今现有局域网LAN&#xff08;Local Area Network&#xff09;采用的最通用的通信协议标准&#xff0c;以太网作为一种原理简单、便于实现同时又价格低廉的局域网技术已经成为业界的主流。 本实验主要介绍了LAN网络中的Eth-Trunk技术…...

滚动菜单 flutter

想实现这个功能&#xff1a; 下面的代码可以实现&#xff1a; import package:flutter/material.dart;void main() > runApp(MyApp());class MyApp extends StatelessWidget {static const String _title Flutter Code Sample;overrideWidget build(BuildContext context)…...

javaee springMVC数字类型转换之通过注解的方式

po 在属性上增加注解 NumberFormat(pattern “#,#.#”) package com.test.pojo;import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.NumberFormat;import java.util.Date;public class Users {private int uid;pr…...

SQL中CASE的用法

在SQL中&#xff0c;CASE语句是一种条件表达式&#xff0c;用于根据条件执行不同的操作。它有两种形式&#xff1a;简单CASE表达式和搜索CASE表达式。 简单CASE表达式的语法如下&#xff1a; CASE expressionWHEN value1 THEN result1WHEN value2 THEN result2...ELSE result …...

自己的碎碎念集合

自己的碎碎念集合 2023-09-07 c++叠加三目运算符闰年计算法2023-08-13 一个小题目 A+B problem一、问题及解答关碍总结2023-07-26 C的2至36进制转换函数一、itoa()函数的示例代码总结2023-07-19 平面坐标下判断三角形以及输出周长和面积一. 基本知识总结2023-06-25 达芬奇去除白…...

暂定名「码道功成:Coder启示录」

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…...

Apache HTTPD (CVE-2017-15715)换行解析漏洞复现

Apache HTTPD 换行解析漏洞 CVE-2017-15715漏洞简介 组件版本漏洞名称 Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09; 漏洞描述 ​ Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞&…...

Spring Boot集成JasperReport生成文档

由于工作需要&#xff0c;要实现后端根据模板动态填充数据生成PDF文档&#xff0c;通过技术选型&#xff0c;使用Ireport5.6来设计模板&#xff0c;结合JasperReports5.6工具库来调用渲染生成PDF文档。 一、使用Ireport designer 5.6设计模板 ireport的使用由于时间关系不便多…...

02-Tomcat打破双亲委派机制

上一篇&#xff1a;01-从JDK源码级别剖析JVM类加载机制 Tomcat 如果使用默认的双亲委派类加载机制行不行&#xff1f; 我们思考一下&#xff1a;Tomcat是个web容器&#xff0c; 那么它要解决什么问题&#xff1a; 一个web容器可能需要部署两个应用程序&#xff0c;不同的应用…...

怎么理解flink的异步检查点机制

背景 flink的checkpoint监控页面那里有两个指标Sync Duration 和Async Duration&#xff0c;一个是开始进行同步checkpoint所需的时间&#xff0c;一个是异步checkpoint过程所需的时间&#xff0c;你是否也有过疑惑&#xff0c;是否只是同步过程中的时间才会阻塞正常的数据处理…...

烟台高端网站建设公司/新媒体运营需要哪些技能

这里select classno 班级, avg(score) 平均分 into avgScore就出现了问题&#xff0c;改为 create or replace procedure myproc(classno1 i执行insert的时候出现这个错误。insert into 表1 values (123,2423,12);表1的结构有4个column&#xff0c;显然插入的值只有三个&#x…...

男男sm怎么做视频网站/网级移动营销app下载

转自&#xff1a;http://developer.51cto.com/art/201507/483448.htm GitHub是一个免费托管开源代码的Git服务器&#xff0c;如果我们不想公开项目的源代码&#xff0c;又不想付费使用&#xff0c;那么我们可以自己搭建一台Git服务器。 下面我们就看看&#xff0c;如何在Ubuntu…...

电商网站商品表设计方案/关键词搜索引擎

通过System.out.printf("%%"); 利用%转义一下即可...

如何利用服务器做网站/windows优化大师是官方的吗

需要用到的命令 #查看挂载df -h#磁盘状态查看fdisk -l#磁盘分区fdisk /dev/adb#格式化磁盘或磁盘分区mkfs.ext4 /dev/adb#挂载磁盘mount /dev/adb /data直接说操作 fdisk -l 查看可挂载磁盘 df -h 查看已经挂在的磁盘 如果对磁盘做分区 fdisk /dev/vdb 输入m&#xff0c;可以查…...

常平做网站公司/上海搜索推广

thinkphp6.0的安装运行 6.0 版本开始&#xff0c;必须通过 Composer 方式安装和更新&#xff0c;所以你无法通过 Git 下载安装。PHP运行环境需要 > 7.1.0 一、安装composer 第一步&#xff1a;双击下载好的composer运行程序第二步&#xff1a;选择要安装的盘符第三步&…...

微网站和微信公共平台的区别/广告优化师的工作内容

Linux是什么&#xff1f;linux是一个开源操作系统&#xff0c;是用于与计算机硬件打交道的中间层。操作系统管理系统资源&#xff0c;提供最基本的计算功能&#xff0c;如管理及配置内存&#xff0c;同时还提供一些基本的服务程序&#xff0c;如&#xff1a;文件系统、设备驱动…...