【在线五子棋对战】二、websocket 服务器搭建
文章目录
- Ⅰ. WebSocket
- 1、简介
- 2、特点
- 3、原理解析
- 4、报文格式
- Ⅱ. WebSocketpp
- 1、认识
- 2、常用接口
- 3、websocketpp库搭建服务器
- 搭建流程
- 主体框架
- 填充回调函数细节
- 4、编写 makefile 文件
- 5、websocket客户端

Ⅰ. WebSocket
1、简介
WebSocket
是从 HTML5
开始支持的一种网页端和服务端保持长连接的消息推送机制。
- 传统的
web
程序都是属于 “⼀问⼀答” 的形式,即客户端给服务器发送了⼀个HTTP
请求,服务器给客户端返回⼀个HTTP
响应。这种情况下服务器是属于被动的一方,如果客户端不主动发起请求服务器就无法主动给客户端响应 - 像网页即时聊天或者我们做的五子棋游戏这样的程序都是非常依赖 “消息推送” 的, 即需要服务器主动推动消息到客户端。如果只是使用原生的
HTTP
协议,要想实现消息推送⼀般需要通过 “轮询” 的⽅式实现,而轮询的成本比较高并且也不能及时的获取到消息的响应。
基于上述两个问题, 就产生了 WebSocket
协议。WebSocket
更接近于 TCP
这种级别的通信⽅式,⼀旦连接建立完成客户端或者服务器都可以主动的向对方发送数据。
并且要注意,WebSocket
和我们平时说的 Socket
是没有半毛钱关系的,注意区分开来!
2、特点
- 建立在
TCP
协议之上,支持双向通信,实时性更强。 - 与
HTTP
协议有着良好的兼容性,握手阶段采用HTTP
协议,默认端口是80
和443
。 - 数据格式比较轻量,性能开销小、通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是
ws
(如果加密,则为wss
),形式:ws://echo.websocket.org
。 - 支持扩展。
ws
协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)
3、原理解析
WebSocket
协议本质上是⼀个基于 TCP
的协议。为了建立⼀个 WebSocket
连接,客户端浏览器首先要向服务器发起⼀个 HTTP
请求,这个请求和通常的 HTTP
请求不同,包含了⼀些附加头信息,通过这个附加头信息完成握手过程并升级协议的过程。
通过一些抓包来分析一下切换过程中 HTTP
请求和 HTTP
响应的包有何不同:
HTTP
请求:
GET /ws hTTP/1.1 // URL通常会设置为/ws表示这是websocket
Host: localhost:2021
Upgrade: websocket // 希望升级的协议格式
Connection: Upgrade // 希望升级协议
Sec-Websocket-Key: mViTimINUhcF0fBHeX+wqA== // 是客户端发送的一个base64编码的秘文,
Sec-Websocket-Version: 13 // 该websocket的版本
Sec-Websocket-Key
是客户端发送的一个base64
编码的秘文,要求服务端返回一个对应加密的Sec-Websocket-Accept
应答,否则客户端会抛出 “Error during WebSocket handshake” 错误,并关闭连接。Sec-Websocket-Version: 13
表示websocket
的版本,如果服务端不支持该版本,需要返回一个Sec-Websocket-Version
里面包含服务端支持的版本号。
HTTP
响应:
HTTP/1.1 101 Switching Protocols // 表示服务端接受websocket协议的
Connection: Upgrade // 升级协议
Upgrade: websocket // 升级的协议格式
Sec-Websocket-Accept: YLcYR/p/mS8hENqlgMXtFTggdv8=
Sec-Websocket-Accept
是服务端采用与客户端一致的秘钥计算出来后返回客户端。HTTP/1.1 101 Switching Protocols
表示服务端接受WebSocket
协议的客户端连接。
4、报文格式
报文字段比较多,我们重点关注这几个字段:
- FIN:
WebSocket
协议传输数据以消息为概念单位,⼀个消息有可能由⼀个或多个帧组成- 占
1
个比特大小:- 如果是
1
,表示这是消息的最后一个分片 - 如果是
0
,表示这不是消息的最后一个分片
- 如果是
- RSV1、RSV2、RSV3:
- 各占
1
个比特大小:- 保留字段,只在扩展时使用,若未启用扩展则应置
1
,若收到不全为0
的数据帧,且未协商扩展则立即终止连接。
- 保留字段,只在扩展时使用,若未启用扩展则应置
- 各占
- Opcode:
- 占
4
个比特,标志当前数据帧的类型:- 0x0:表示一个延续帧。当
Opcode
为0
时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。 - 0x1:表示这是一个文本帧(frame)
- 0x2:表示这是一个二进制帧(frame)
- 0x3-0x7:保留的操作代码,用于后续定义的非控制帧
- 0x8:表示连接断开
- 0x9:表示这是一个 ping 操作
- 0xA:表示这是一个 pong 操作
- 0xB-0xF:保留的操作代码,用于后续定义的控制帧
- 0x0:表示一个延续帧。当
- 占
- Mask:
- 表示是否要对数据载荷也就是
Payload数据
字段进行掩码操作:- 从客户端向服务端发送数据时,需要对数据进行掩码操作。如果服务端接收到的数据没有进行掩码操作,服务端需要断开连接!
- 从服务端向客户端发送数据时,不需要对数据进行掩码操作。
- 占
1
个比特大小:- 如果为
1
,那么Masking-key
字段中会定义一个掩码键,并用这个掩码键对数据载荷进行反掩码。
- 如果为
- 表示是否要对数据载荷也就是
- Payload length:
- 数据载荷的长度,单位是字节。有可能为
7
位、7+16
位、7+64
位,假设x
为数据载荷的长度,则其表示如下:- x 在[0,126) 内:数据的长度就是 Payload length 表示的大小
- x 为 126:后续
2
个字节代表⼀个16
位的无符号整数,该无符号整数的值为数据的长度 - x 为 127:后续
8
个字节代表⼀个64
位的无符号整数(最高位为0
),该无符号整数的值为数据的长度
- 数据载荷的长度,单位是字节。有可能为
- Masking-key:
- 占
0
或4
字节Mask
为1
,则包含4
字节的Masking-key
Mask
为0
,则不包含Masking-key
- 占
- Payload data:
- 报文携带的载荷数据
- 如果没有协商使用扩展的话,扩展数据为
0
字节。所有的扩展都必须声明扩展数据的长度,扩展如何使用必须在握手阶段就协商好
- 如果没有协商使用扩展的话,扩展数据为
- 报文携带的载荷数据
Ⅱ. WebSocketpp
1、认识
WebSocketpp
是⼀个跨平台的开源(BSD许可证)头部 专用C++库,它实现了 RFC6455
(WebSocket协议)和 RFC7692
(WebSocketCompression Extensions)。它允许将 WebSocket
客户端和服务器功能集成到 C++
程序中。在最常见的配置中,全功能网络 I/O
由 Asio
网络库提供。
WebSocketpp
的主要特性包括:
- 事件驱动的接口
- ⽀持 HTTP/HTTPS、WS/WSS、IPv6
- 灵活的依赖管理,如 Boost 库/ C++11标准库
- 可移植性:Posix/Windows、32/64bit、Intel/ARM
- 线程安全
WebSocketpp
同时支持 HTTP
和 Websocket
两种网络协议,比较适用于我们本次的项目, 所以我们选用该库作为项目的依赖库用来搭建 HTTP
和 WebSocket
服务器。
下面是该项目的⼀些常用网站:
- github
- 官网
- 用户手册
2、常用接口
这里做一下下面接口和类的大概介绍:
- 下面的几个指定事件的回调事件,如
set_open_handler()
函数,它们都是用来 设置 针对不同事件而设置的处理函数,而处理函数是由我们自己来写的,因为WebSocketpp
负责搭建服务器,它给不同的事件分配了不同的处理函数指针,比如open_handler
其实就是握手成功后处理函数的指针,当服务器收到了指定的数据,触发了指定的事件之后,就会通过函数指针去调用我们自己写的那些处理函数! server
类是继承于endpoint
类的,我们后面也是通过创建server
类来完成搭建服务器的操作。connection
这个类也很重要,它提供了一些接口,用于处理连接的事件和状态,而connection_hdl
是一个指向connection
对象的轻量级句柄。它的作用是在WebSocket
库内部,用于管理和操作connection
对象,以及在回调函数中传递连接对象的引用。- 一般来说我们通过
server
类的get_con_from_hdl()
函数,通过connection_hdl
就能获得connection_ptr
从而来操作connection
类的函数!
- 一般来说我们通过
- 通常如
connection
、message_buffer
等类都是用于在回调函数中请求和响应的类!
namespace websocketpp
{typedef lib::weak_ptr<void> connection_hdl;template <typename config>class endpoint : public config::socket_type {typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;typedef typename connection_type::ptr connection_ptr;typedef typename connection_type::message_ptr message_ptr;typedef lib::function<void(connection_hdl)> open_handler;typedef lib::function<void(connection_hdl)> close_handler;typedef lib::function<void(connection_hdl)> http_handler;typedef lib::function<void(connection_hdl, message_ptr)> message_handler;// websocketpp::log::alevel::none 禁止打印所有日志 void set_access_channels(log::level channels); // 设置⽇志打印等级void clear_access_channels(log::level channels); // 清除指定等级的⽇志// 设置指定事件的回调函数void set_open_handler(open_handler h); // websocket握⼿成功回调处理函数void set_close_handler(close_handler h); // websocket连接关闭回调处理函数void set_message_handler(message_handler h); // websocket消息回调处理函数void set_http_handler(http_handler h); // http请求回调处理函数// 发送数据接⼝void send(connection_hdl hdl, std::string& payload, frame::opcode::value op);void send(connection_hdl hdl, void* payload, size_t len, frame::opcode::value op);// 关闭连接接⼝void close(connection_hdl hdl, close::status::value code, std::string& reason);// 获取connection_hdl 对应连接的connection_ptrconnection_ptr get_con_from_hdl(connection_hdl hdl);// websocketpp基于asio框架实现,init_asio⽤于初始化asio框架中的io_service调度器void init_asio();// 设置是否启用地址重⽤void set_reuse_addr(bool value);// 设置endpoint的绑定监听端⼝void listen(uint16_t port);// 对io_service对象的run接⼝封装,⽤于启动服务器std::size_t run();// websocketpp提供的定时器,以毫秒为单位timer_ptr set_timer(long duration, timer_handler callback);// 取消定时器,立刻触发之前设置的callback函数std::size_t cancel();};template <typename config>class server: public endpoint<connection<config>, config> {// 初始化并启动服务端监听连接的accept事件处理void start_accept();};template <typename config>class connection: public config::transport_type::transport_con_type, public config::connection_base{// 发送数据接⼝error_code send(std::string& payload, frame::opcode::value op=frame::opcode::text);// 获取http请求头部中key关键字的std::string const& get_request_header(std::string const& key)// 获取请求正文std::string const& get_request_body();// 设置响应状态码void set_status(http::status_code::value code);// 设置http响应正文void set_body(std::string const& value);// 添加http响应头部字段void append_header(std::string const& key, std::string const& val);// 获取http请求对象request_type const& get_request();// 获取connection_ptr对应的connection_hdl connection_hdl get_handle();};namespace http {namespace parser {class parser {std::string const& get_header(std::string const& key)};class request : public parser {// 获取请求方法std::string const& get_method()// 获取请求uri接口std::string const& get_uri()};}}namespace message_buffer {// 获取websocket请求中的payload数据类型frame::opcode::value get_opcode();// 获取websocket中payload数据std::string const& get_payload();}namespace log {// 日志等级struct alevel {static level const none = 0x0;static level const connect = 0x1;static level const disconnect = 0x2;static level const control = 0x4;static level const frame_header = 0x8;static level const frame_payload = 0x10;static level const message_header = 0x20;static level const message_payload = 0x40;static level const endpoint = 0x80;static level const debug_handshake = 0x100;static level const debug_close = 0x200;static level const devel = 0x400;static level const app = 0x800;static level const http = 0x1000;static level const fail = 0x2000;static level const access_core = 0x00003003;static level const all = 0xffffffff;};}namespace http {// 状态码namespace status_code {enum value {uninitialized = 0,continue_code = 100,switching_protocols = 101,ok = 200,created = 201,accepted = 202,non_authoritative_information = 203,no_content = 204,reset_content = 205,partial_content = 206,multiple_choices = 300,moved_permanently = 301,found = 302,see_other = 303,not_modified = 304,use_proxy = 305,temporary_redirect = 307,bad_request = 400,unauthorized = 401,payment_required = 402,forbidden = 403,not_found = 404,method_not_allowed = 405,not_acceptable = 406,proxy_authentication_required = 407,request_timeout = 408,conflict = 409,gone = 410,length_required = 411,precondition_failed = 412,request_entity_too_large = 413,request_uri_too_long = 414,unsupported_media_type = 415,request_range_not_satisfiable = 416,expectation_failed = 417,im_a_teapot = 418,upgrade_required = 426,precondition_required = 428,too_many_requests = 429,request_header_fields_too_large = 431,internal_server_error = 500,not_implemented = 501,bad_gateway = 502,service_unavailable = 503,gateway_timeout = 504,http_version_not_supported = 505,not_extended = 510,network_authentication_required = 511};}}namespace frame {namespace opcode {enum value {continuation = 0x0,text = 0x1,binary = 0x2,rsv3 = 0x3,rsv4 = 0x4,rsv5 = 0x5,rsv6 = 0x6,rsv7 = 0x7,close = 0x8,ping = 0x9,pong = 0xA,control_rsvb = 0xB,control_rsvc = 0xC,control_rsvd = 0xD,control_rsve = 0xE,control_rsvf = 0xF,};}}
}
3、websocketpp库搭建服务器
搭建流程
一般我们搭建服务器都是统一形式的,大概流程如下:
- 实例化
server
对象- 实例化的时候,因为命名空间一般我们不展开,所以要引用命名空间
websocketpp
里面的server
,注意不要落了模板,一般模板里面的config
这里都是使用websocketpp::config::asio
- 实例化的时候,因为命名空间一般我们不展开,所以要引用命名空间
- 设置日志等级
- 初始化
asio
调度器和启用地址重用 - 设置回调函数
- 设置回调函数的时候,
connection_hdl
类型是要通过命名空间websocketpp
来引入的,就是websocketpp::connection_hdl
。而对于set_http_handler()
函数来说,它还多了一个函数指针message_ptr
,因为它是属于endpoint
类,或者它的子类server
的,所以通过我们typedef
的server
类型wsserver_t
来取出这个函数指针,就是wsserver_t::message_ptr
。
- 设置回调函数的时候,
- 设置监听窗口
- 获取新连接
- 启动服务器
主体框架
我们先将主体框架搭起来,其中头文件我们要使用的是 server.hpp
和 asio_no_tls.hpp
,因为 websocketpp
库是放在 /usr/include/
中的,所以要使用这些头文件的话还得进入它们对应的路径下面引入。
#include <iostream>
#include <string>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>using wsserver_t = websocketpp::server<websocketpp::config::asio>;void open_callback(websocketpp::connection_hdl hdl)
{}
void close_callback(websocketpp::connection_hdl hdl)
{}
void http_callback(websocketpp::connection_hdl hdl)
{}
void message_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msgptr)
{}
int main()
{// 1.实例化server对象wsserver_t server;// 2.设置日志等级server.set_access_channels(websocketpp::log::alevel::none);// 3.初始化asio调度器和地址重用server.init_asio();server.set_reuse_addr(true);// 4.设置回调函数server.set_open_handler(open_callback);server.set_close_handler(close_callback);server.set_message_handler(message_callback);server.set_http_handler(http_callback);// 5.设置监听窗口server.listen(8080);// 6.开始获取新连接server.start_accept();// 7.启动服务器server.run();return 0;
}
但是一般我们在回调函数中还需要用到 server
对象的 指针 或者 引用,所以要多传一个参数,但是我们不希望影响到这些函数的参数列表,因为比如这里的 typedef lib::function<void(connection_hdl)> open_handler
等函数已经要求参数中只能有一个参数,那要是我们想多传一个参数又不想影响包装器,怎么办呢❓❓❓
此时就需要使用 std::bind
绑定器,终于有用武之地了哈哈!
绑定器的作用就是可以将一些参数直接绑定到形参,相当于让计算机帮我们自动填我们预先设置好的参数,就仿佛这个参数不存在一样,有点像缺省参数,但不同的是,绑定器绑定的函数可以生成一个新的函数指针进行返回!
下面我们就给每个回调函数多传一个 server
对象的指针,然后在设置回调函数那里进行参数绑定:
void open_callback(wsserver_t* server, websocketpp::connection_hdl hdl)
{}
void close_callback(wsserver_t* server, websocketpp::connection_hdl hdl)
{}
void http_callback(wsserver_t* server, websocketpp::connection_hdl hdl)
{}
void message_callback(wsserver_t* server, websocketpp::connection_hdl hdl, wsserver_t::message_ptr msgptr)
{}
int main()
{......// 4.设置回调函数server.set_open_handler(std::bind(open_callback, &server, std::placeholders::_1));server.set_close_handler(std::bind(close_callback, &server, std::placeholders::_1));server.set_message_handler(std::bind(message_callback, &server, std::placeholders::_1, std::placeholders::_2));server.set_http_handler(std::bind(http_callback, &server, std::placeholders::_1)); ......
}
填充回调函数细节
-
http_callback() 函数
// 任务:打印请求内容,并且设置响应内容 void http_callback(wsserver_t* server, websocketpp::connection_hdl hdl) {// 首先将一些请求内容打印到终端wsserver_t::connection_ptr conn_ptr = server->get_con_from_hdl(hdl); // 获取connection_ptrstd::cout << "body: " << conn_ptr->get_request_body() << std::endl; // 打印请求正文websocketpp::http::parser::request req = conn_ptr->get_request(); // 获取http请求对象std::cout << "method: " << req.get_method() << std::endl; // 打印请求方法std::cout << "uri: " << req.get_uri() << std::endl; // 打印请求路径资源std::cout << "version: " << req.get_version() << std::endl; // 打印请求版本// 然后填充响应资源std::string body = "<html><body><h1>Hello Liren!</h1></body></html>"; // 写一个简单的html页面格式conn_ptr->set_body(body); // 设置响应正文conn_ptr->append_header("Content-Type", "text/html"); // 设置响应头部conn_ptr->set_status(websocketpp::http::status_code::ok); // 设置响应状态码 }
-
open_callback 函数
void open_callback(wsserver_t* server, websocketpp::connection_hdl hdl) {std::cout << "websocket握手成功!" << std::endl; }
-
close_callback 函数
void close_callback(wsserver_t* server, websocketpp::connection_hdl hdl) {std::cout << "websocket断开连接!" << std::endl; }
-
message_callback 函数
// 任务:收到一个消息进行打印,然后进行响应 void message_callback(wsserver_t* server, websocketpp::connection_hdl hdl, wsserver_t::message_ptr msgptr) {// 打印获取的消息wsserver_t::connection_ptr conn_ptr = server->get_con_from_hdl(hdl); // 获取connection_ptrstd::cout << "message: " << msgptr->get_payload() << std::endl; // 打印消息// 响应std::string callback = "server say: client say " + msgptr->get_payload(); // 创建响应内容conn_ptr->send(callback, websocketpp::frame::opcode::text); // 发送响应 }
4、编写 makefile 文件
因为 websocketpp
用到了 pthread
和 boost_system
,所以要在编译的时候指定:
server:wsserver.cppg++ -o $@ $^ -std=c++11 -lboost_system -lpthread.PHONY:clean
clean:rm -f server
5、websocket客户端
如果使用的是 http
客户端,也就是直接用浏览器输入 ip + 端口号,那么就能直接访问了。其连接等时候 触发的是 http_callback()
函数。
而这里我们用自己写一个 html
页面来充当 websocket
客户端,来测试一下服务器是否能正确的收发信息,其 触发的是 message_callback()
函数!
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Test Websocket</title></head><body><input type="text" id="message"><button id="submit">提交</button><script>// 创建 websocket 实例// ws://192.168.51.100:8888// 类比http:// ws表示websocket协议// 192.168.51.100 表示服务器地址// 8888表⽰服务器绑定的端⼝let websocket = new WebSocket("ws://81.71.97.127:8080/");// 处理连接打开的回调函数websocket.onopen = function() {alert("连接建⽴");}// 处理收到消息的回调函数// 控制台打印消息websocket.onmessage = function(e) {alert("收到消息: " + e.data);}// 处理连接异常的回调函数websocket.onerror = function() {alert("连接异常");}// 处理连接关闭的回调函数websocket.onclose = function() {alert("连接关闭");}// 实现点击按钮后, 通过 websocket实例 向服务器发送请求let input = document.querySelector('#message');let button = document.querySelector('#submit');button.onclick = function() {console.log("发送消息: " + input.value);websocket.send(input.value);}</script></body>
</html>
然后我们在桌面可以创建一个 html
文件,将其复制进去之后,打开这个文件,就能测试了!
相关文章:

【在线五子棋对战】二、websocket 服务器搭建
文章目录 Ⅰ. WebSocket1、简介2、特点3、原理解析4、报文格式 Ⅱ. WebSocketpp1、认识2、常用接口3、websocketpp库搭建服务器搭建流程主体框架填充回调函数细节 4、编写 makefile 文件5、websocket客户端 Ⅰ. WebSocket 1、简介 WebSocket 是从 HTML5 开始支持的一种网页端…...

C++课设:从零开始打造影院订票系统
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、项目背景与需求分析二、系统架构设计…...

【计算机网络】数据链路层-滑动窗口协议
数据链路层滑动窗口协议 1. 三种协议对比表 特性停止-等待协议GBN协议SR协议窗口大小发送 1,接收 1发送 W (1<W≤2ⁿ-1),接收 1发送 C,接收 R确认方式单个确认累积确认选择性确认重传策略超时重传回退N帧重传选择性重传接收缓冲区…...

在linux系统上,如何安装Elasticsearch?
1.问题描述 当尝试连接时报错,报错内容为: elastic_transport.ConnectionError: Connection error caused by: ConnectionError(Connection error caused by: NewConnectionError(<urllib3.connection.HTTPConnection object at 0x7fd808b179d0>:…...

wpf Behaviors库实现支持多选操作进行后台绑定数据的ListView
<ListView ItemsSource"{Binding SchemeItems}" SelectionMode"Extended" VerticalAlignment"Stretch" HorizontalAlignment"Stretch"><ListView.ContextMenu><ContextMenu><MenuItem Header"删除" …...
【HarmonyOS 5】拍摄美化开发实践介绍以及详细案例
以下是 HarmonyOS 5 拍摄美化功能的简洁介绍,整合核心能力与技术亮点: 一、AI 影像创新 AI 魔法移图 系统级图像分层技术实现人物/物体自由拖拽、缩放与复制,突破传统构图限制。自动分离主体与背景,一键生成错位创意照&…...

《Vuejs设计与实现》第 8 章(挂载与更新)
目录 8.1 挂载子节点与属性 8.2 HTML Attributes 与 DOM Properties 8.3 设置元素属性的正确方式 8.4 处理 class 属性 8.5 卸载操作 8.6 区分 vnode 类型 8.7 事件处理优化 8.8 事件冒泡与更新时机问题 8.9 子节点的更新 8.10 文本节点和注释节点 8.11 片段…...

Ubuntu20.04中 Redis 的安装和配置
Ubuntu20.04 中 Redis 的安装和配置 Ubuntu 安装 MySQL 及其配置 1. Redis 的安装 更新系统包列表并安装 Redis : # 更新包管理工具 sudo apt update# -y:自动确认所有提示(非交互式安装) sudo apt install -y redis-server测…...
从游戏到自动驾驶:互联网时代强化学习如何让机器学会自主决策?
一、为什么机器需要“试错学习”?——强化学习的核心秘密 你有没有玩过《超级马里奥》?当你操控马里奥躲避乌龟、跳过悬崖时,其实就在用一种“试错”的方法学习最优路径。强化学习(Reinforcement Learning, RL)就是让…...

实验四:图像灰度处理
实验四 图像处理实验报告 目录 实验目的实验内容 原理描述Verilog HDL设计源代码Testbench仿真代码及仿真结果XDC文件配置下板测试 实验体会实验照片 实验目的 在实验三的基础上,将图片显示在显示器上,并进行灰度处理。 实验内容 原理描述 1. 图片的…...
asp.net mvc如何简化控制器逻辑
在ASP.NET MVC中,可以通过以下方法简化控制器逻辑: ASP.NET——MVC编程_aspnet mvc-CSDN博客 .NET/ASP.NET MVC Controller 控制器(IController控制器的创建过程) https://cloud.tencent.com/developer/article/1015115 【转载…...

解析“与此站点的连接不安全”警告:成因与应对策略
一、技术本质:SSL/TLS协议的信任链断裂 现代浏览器通过SSL/TLS协议建立加密通信,其核心在于证书颁发机构(CA)构建的信任链。当用户访问网站时,浏览器会验证服务器证书的有效性,包括: 证书链完…...
PyCharm和VS Code哪个更适合初学者
对于 Python 初学者来说,选择 VS Code 还是 PyCharm 取决于你的具体需求和使用场景。以下是两者的详细对比和推荐建议: VS Code 优点: 轻量级:启动速度快,占用资源少,适合在低端设备上运行。高度可定制&am…...

⚡️ Linux Docker 基本命令参数详解
🐳 Linux Docker 基本命令参数详解 📘 1. Docker 简介 Docker 是一个开源的容器化平台,它通过将应用及其依赖打包到一个轻量级、可移植的容器中,从而实现跨平台运行。Docker 采用 C/S 架构,服务端称为 Docker Daemon&a…...

做题笔记(ctfshow)
一。ctfshow web13 文件扫描 存在upload.php.bak <?php header("content-type:text/html;charsetutf-8");$filename $_FILES[file][name];$temp_name $_FILES[file][tmp_name];$size $_FILES[file][size];$error $_FILES[file][error];$arr pathinfo($fi…...
Linux 用户层 和 内核层锁的实现
目录 一、系统调用futex介绍1. 核心机制2. 常见操作3. 工作流程示例(互斥锁)4. 优势5. 注意事项6. 典型应用 二、Linux中用户态的锁和内核的锁不是同一个实现吗?2.1 本质区别2.2 用户态锁如何工作(以 pthread_mutex 为例ÿ…...
Android第十五次面试总结(第三方组件和adb命令)
Android 第三方组件转为系统组件核心流程 这通常是在进行 Android 系统定制(如 ROM 开发、固件制作)时完成,目的是让第三方应用拥有更高的权限和系统身份。主要过程如下: 核心准备:签名!赋予系统身份 …...

Agent短期记忆的几种持久化存储方式
今天给大家讲一下关于Agent长期对话的几种持久化存储方式,之前的文章给大家说过短期记忆和长期记忆,短期记忆基于InMemorySaver做checkpointer(检查点),短期记忆 (线程级持久性) 使代理能够跟踪…...

Git 常见操作
目录 1.git stash 2.合并多个commit 3. git commit -amend (后悔药) 4.版本回退 5.merge和rebase 6.cherry pick 7.分支 8.alias 1.git stash git-stash操作_git stash 怎么增加更改内容-CSDN博客 2.合并多个commit 通过git bash工具交互式操作。 1.查询commit的c…...

从 ClickHouse、Druid、Kylin 到 Doris:网易云音乐 PB 级实时分析平台降本增效
网易云音乐基于 Apache Doris 替换了早期架构中 Kylin、Druid、Clickhouse、Elasticsearch、HBase 等引擎,统一了实时分析架构,并广泛应用于广告实时数仓、日志平台和会员报表分析等典型场景中,带来导入性能提升 3~30 倍ÿ…...
隐函数 因变量确定标准
涉及多元隐函数求导法的逻辑本质:当我们对隐函数关系 F ( x , y , z ) 0 F(x, y, z) 0 F(x,y,z)0 使用偏导法求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z时,为什么「偏导」能确定谁是因变量?为什么只有当对 z z z 的偏导 F z…...

Facebook接入说明
Facebook 原生 Messenger 聊天消息接入到一洽对话中 1、创建 Facebook 主页 进入 https://www.facebook.com/pages/create 页面根据提示创建主页(如果已经有待用主页,可跳过) 2、授权对话权限 1、向您的一洽负责人获取 Facebook 授权链接 2、…...

Grafana 地图本土化方案:使用高德地图API平替GeoMap地图指南
[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] 📢 大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(De…...
Python爬虫实战:研究demiurge框架相关技术
1. 引言 在当今数字化时代,互联网上蕴含着海量的有价值信息。爬虫技术作为获取这些信息的重要手段,被广泛应用于学术研究、商业分析、舆情监测等多个领域。然而,构建一个高效、稳定且可维护的爬虫系统面临诸多挑战,如网页结构复杂多变、反爬机制日益严格、数据处理流程繁琐…...

3 个优质的终端 GitHub 开源工具
1、Oh My Zsh Oh My Zsh 是一个帮助你管理和美化 zsh 终端的开源工具。它让你的终端更炫酷、更高效。安装后,你可以快速使用各种插件和主题,比如常见的 git 命令简化、支持多种编程语言工具等,每次打开终端都会有惊喜。无论你是开发者还是普…...
F(x, y, z) = 0 隐函数微分 确定自变量
多元隐函数偏导的通用公式: 设一个隐函数由三元函数定义: F ( x , y , z ) 0 F(x, y, z) 0 F(x,y,z)0 且假设 z z ( x , y ) z z(x, y) zz(x,y),即 z z z 是 x , y x, y x,y 的函数,满足这个等式恒成立。则有以下公式&am…...

亚马逊AWS云服务器高效使用指南:最大限度降低成本的实战策略
对于初次接触云计算的企业或个人开发者而言,亚马逊云服务器(Amazon EC2)的配置与成本控制往往面临双重挑战:既要理解数百种实例规格的技术参数,又要避免因配置不当导致的资源浪费。本文将深入剖析AWS EC2的核心使用场景…...

Android设备推送traceroute命令进行网络诊断
文章目录 工作原理下载traceroute for android推送到安卓设备执行traceroutetraceroute www.baidu.com Traceroute(追踪路由) 是一个用于网络诊断的工具,主要用于追踪数据包从源主机到目标主机所经过的路由路径,以及每一跳&#x…...

github开源协议选择
文章目录 怎么选协议宽松型协议 Permissive Licenses传染型协议 怎么选协议 希望代码被广泛使用,允许闭源 MIT、Apache 2.0、BSD需要专利保护 Apache 2.0强制开源衍生作品 GPL、AGPL开发库,允许闭源调用 LGPL云服务项目,防止白嫖 AGPL企业级…...

详解Jenkins Pipeline 中git 命令的使用方法
在 Jenkins Pipeline 中,git 命令是用于从版本控制系统(如 Git)拉取代码的核心步骤。其用法灵活,支持多种配置参数,但需要遵循 Jenkins 流水线语法规范。 一、基础语法 1. 声明式流水线(Declarative Pipe…...