【在线五子棋对战】二、websocket 服务器搭建

news2025/6/9 12:23:53

文章目录

  • Ⅰ. 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 协议,默认端口是 80443
  • 数据格式比较轻量,性能开销小、通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。
  • 协议标识符是 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,表示这不是消息的最后一个分片
  • RSV1RSV2RSV3
    • 各占 1 个比特大小:
      • 保留字段,只在扩展时使用,若未启用扩展则应置 1,若收到不全为 0 的数据帧,且未协商扩展则立即终止连接。
  • Opcode
    • 4 个比特,标志当前数据帧的类型:
      • 0x0:表示一个延续帧。当 Opcode0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
      • 0x1:表示这是一个文本帧(frame)
      • 0x2:表示这是一个二进制帧(frame)
      • 0x3-0x7:保留的操作代码,用于后续定义的非控制帧
      • 0x8:表示连接断开
      • 0x9:表示这是一个 ping 操作
      • 0xA:表示这是一个 pong 操作
      • 0xB-0xF:保留的操作代码,用于后续定义的控制帧
  • 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
    • 04 字节
      • Mask1,则包含 4 字节的 Masking-key
      • Mask0,则不包含 Masking-key
  • Payload data
    • 报文携带的载荷数据
      • 如果没有协商使用扩展的话,扩展数据为 0 字节。所有的扩展都必须声明扩展数据的长度,扩展如何使用必须在握手阶段就协商好

Ⅱ. WebSocketpp

1、认识

WebSocketpp 是⼀个跨平台的开源(BSD许可证)头部 专用C++库,它实现了 RFC6455(WebSocket协议)和 RFC7692(WebSocketCompression Extensions)。它允许将 WebSocket 客户端和服务器功能集成到 C++ 程序中。在最常见的配置中,全功能网络 I/OAsio 网络库提供。

WebSocketpp 的主要特性包括:

  • 事件驱动的接口
  • ⽀持 HTTP/HTTPS、WS/WSS、IPv6
  • 灵活的依赖管理,如 Boost 库/ C++11标准库
  • 可移植性:Posix/Windows、32/64bit、Intel/ARM
  • 线程安全

WebSocketpp 同时支持 HTTPWebsocket 两种网络协议,比较适用于我们本次的项目, 所以我们选用该库作为项目的依赖库用来搭建 HTTPWebSocket 服务器。

​ 下面是该项目的⼀些常用网站:

  • 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 类的函数!
  • 通常如 connectionmessage_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_ptr
    	connection_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库搭建服务器

搭建流程

一般我们搭建服务器都是统一形式的,大概流程如下:

  1. 实例化 server 对象
    • 实例化的时候,因为命名空间一般我们不展开,所以要引用命名空间 websocketpp 里面的 server,注意不要落了模板,一般模板里面的 config 这里都是使用 websocketpp::config::asio
  2. 设置日志等级
  3. 初始化 asio 调度器和启用地址重用
  4. 设置回调函数
    • 设置回调函数的时候,connection_hdl 类型是要通过命名空间 websocketpp 来引入的,就是 websocketpp::connection_hdl。而对于 set_http_handler() 函数来说,它还多了一个函数指针 message_ptr,因为它是属于 endpoint 类,或者它的子类 server 的,所以通过我们 typedefserver 类型 wsserver_t 来取出这个函数指针,就是 wsserver_t::message_ptr
  5. 设置监听窗口
  6. 获取新连接
  7. 启动服务器

主体框架

​ 我们先将主体框架搭起来,其中头文件我们要使用的是 server.hppasio_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_ptr
        std::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_ptr
        std::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 用到了 pthreadboost_system,所以要在编译的时候指定:

server:wsserver.cpp
	g++ -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 文件,将其复制进去之后,打开这个文件,就能测试了!

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

C++课设:从零开始打造影院订票系统

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 专栏介绍&#xff1a;《编程项目实战》 目录 一、项目背景与需求分析二、系统架构设计…

【计算机网络】数据链路层-滑动窗口协议

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

在linux系统上,如何安装Elasticsearch?

1.问题描述 当尝试连接时报错&#xff0c;报错内容为&#xff1a; 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"删除" …

《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 片段&#xf…

Ubuntu20.04中 Redis 的安装和配置

Ubuntu20.04 中 Redis 的安装和配置 Ubuntu 安装 MySQL 及其配置 1. Redis 的安装 更新系统包列表并安装 Redis &#xff1a; # 更新包管理工具 sudo apt update# -y&#xff1a;自动确认所有提示&#xff08;非交互式安装&#xff09; sudo apt install -y redis-server测…

实验四:图像灰度处理

实验四 图像处理实验报告 目录 实验目的实验内容 原理描述Verilog HDL设计源代码Testbench仿真代码及仿真结果XDC文件配置下板测试 实验体会实验照片 实验目的 在实验三的基础上&#xff0c;将图片显示在显示器上&#xff0c;并进行灰度处理。 实验内容 原理描述 1. 图片的…

解析“与此站点的连接不安全”警告:成因与应对策略

一、技术本质&#xff1a;SSL/TLS协议的信任链断裂 现代浏览器通过SSL/TLS协议建立加密通信&#xff0c;其核心在于证书颁发机构&#xff08;CA&#xff09;构建的信任链。当用户访问网站时&#xff0c;浏览器会验证服务器证书的有效性&#xff0c;包括&#xff1a; 证书链完…

⚡️ Linux Docker 基本命令参数详解

&#x1f433; Linux Docker 基本命令参数详解 &#x1f4d8; 1. Docker 简介 Docker 是一个开源的容器化平台&#xff0c;它通过将应用及其依赖打包到一个轻量级、可移植的容器中&#xff0c;从而实现跨平台运行。Docker 采用 C/S 架构&#xff0c;服务端称为 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…

Agent短期记忆的几种持久化存储方式

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

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 等引擎&#xff0c;统一了实时分析架构&#xff0c;并广泛应用于广告实时数仓、日志平台和会员报表分析等典型场景中&#xff0c;带来导入性能提升 3&#xff5e;30 倍&#xff…

Facebook接入说明

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

Grafana 地图本土化方案:使用高德地图API平替GeoMap地图指南

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] &#x1f4e2; 大家好&#xff0c;我是 WeiyiGeek&#xff0c;一名深耕安全运维开发&#xff08;SecOpsDev&#xff09;领域的技术从业者&#xff0c;致力于探索DevOps与安全的融合&#xff08;De…

3 个优质的终端 GitHub 开源工具

1、Oh My Zsh Oh My Zsh 是一个帮助你管理和美化 zsh 终端的开源工具。它让你的终端更炫酷、更高效。安装后&#xff0c;你可以快速使用各种插件和主题&#xff0c;比如常见的 git 命令简化、支持多种编程语言工具等&#xff0c;每次打开终端都会有惊喜。无论你是开发者还是普…

亚马逊AWS云服务器高效使用指南:最大限度降低成本的实战策略

对于初次接触云计算的企业或个人开发者而言&#xff0c;亚马逊云服务器&#xff08;Amazon EC2&#xff09;的配置与成本控制往往面临双重挑战&#xff1a;既要理解数百种实例规格的技术参数&#xff0c;又要避免因配置不当导致的资源浪费。本文将深入剖析AWS EC2的核心使用场景…

Android设备推送traceroute命令进行网络诊断

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

github开源协议选择

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

详解Jenkins Pipeline 中git 命令的使用方法

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