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

【HTTP】:应用层协议HTTP(1)

1.HTTP协议

虽然我们说,应用层协议是我们程序猿自己定的.但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用.HTTP(超文本传输协议)就是其中之一。

在互联网世界中,HTTP(HyperTextTransfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。

HTTP协议是客户端与服务器之间通信的基础。客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。

2. HTTP 协议的工作过程

当我们在浏览器输入一个网址,此时浏览器就会给对应的服务器发送一个 HTTP 请求,对应的服务器收到这个请求之后,经过计算处理,就会返回一个 HTTP 响应。并且当我们访问一个网站时,可能涉及不止一次的 HTTP 请求和响应的交互过程。

基础术语:

  1. 客户端: 主动发起网络请求的一端
  2. 服务器: 被动接收网络请求的一端
  3. 请求: 客户端给服务器发送的数据
  4. 响应: 服务器给客户端返回的数据

HTTP 协议的重要特点: 一发一收,一问一答
在这里插入图片描述
注意: 网络编程中,除了一发一收之外,还有其它的模式

多发一收:例如上传大文件
一发多收:例如看直播时,搜索一个词条可以得到多个视频源
多发多收:例如串流(steam link、moonlight 等等)

3. Fiddler 抓包工具介绍

3.1 抓包工具的使用
当我们访问一个网站时,可能涉及不止一次的 HTTP 请求和响应的交互,为此为了更加清楚的了解我们访问一个网站时 HTTP 请求/协议是怎么交互的,由于 HTTP 是一个文本格式的协议,就可以通过以下两种方式:

方式一: 通过 F12 打开浏览器的开发者工具,点击 Network 标签页,然后刷新页面就行。显示的每一条记录都是一次 HTTP 请求/响应
方式二(推荐): 抓包工具,这里以 Fiddler 为例,它能够直接读取你电脑上网卡的信息,网卡上有什么数据流动,它都能感知到并且显示出来
Fiddler 使用方式:

打开 Fiddler,然后打开你要访问的网站,访问该网站的 HTTP 请求和响应就会显示在 Fiddler 上

urlencode 和urldecode
像/?:等这样的字符,已经被url当做特殊意义理解了.因此这些字符不能随意出现.
比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位
做一位,前面加上%,编码成%XY格式

在这里插入图片描述
当我们选择好我们具体要查看的 HTTP/HTTPS 请求和响应时,右上栏就会显示具体的请求报文内容,右下角就会显示具体的响应报文内容(需要点击 Raw 标签来查看详细的数据格式)

请求和响应的详细数据,可以通过右下角的 view in Notepad 键通过记事本打开

为了方便我们抓取我们自己想查看的抓包结果,可以通过 ctrl + a 全选左侧的抓包结果,ctrl + x 删除选中的所有抓包结果,再进行网页的刷新就行

响应的正文往往是被显示在浏览器上,最常见的响应格式就是 html,很多网站返回的 html 是压缩过的(因为压缩之后,网络传输的数据量就变少了,即节省了网络带宽),所以需要进行解压缩 decode
在这里插入图片描述
在这里插入图片描述

4.HTTP协议请求与响应格式

HTTP请求
首行:[方法]+[url]+[版本]
• Header:请求的属性,冒号分割的键值对;每组属性之间使用\r\n分隔;遇到空行
表示Header部分结束
• Body:空行后面的内容都是Body.Body允许为空字符串.如果Body存在,则在Header 中会有一个Content-Length属性来标识Body的长度;
在这里插入图片描述

在这里插入图片描述

5.认识“方法”(method)

HTTP 中的方法,就是 HTTP 请求报文中的首行的第一个部分。

原本 HTTP 的设计者,是希望通过不同的方法,来表达不同的语义。但是至今,其实也没有被实现,以下具体的方法具体起到了什么作用,完全看程序员在代码中是如何实现的。

虽然 HTTP 中的方法很多,但是最常用的就两个 GET 和 POST。以下主要介绍这两个方法
在这里插入图片描述
GET 方法
基本介绍:

GET 是最常用的 HTTP 方法,常用于获取服务器上的某个资源。以下几种方式都会触发 GET 方法的请求

在浏览器中直接输入 URL 回车或点击浏览器收藏夹中的链接,此时浏览器就会发送出一个 GET 请求。
HTML 中的 link、img、script 等标签的属性中放的 URL,浏览器也会构造出 HTTP GET 请求
使用 Javascript 重点 ajax,也能构造出 HTTP GET 请求
各种编程语言(只要能够访问网络),就都能够构造出 HTTP GER 请求
GET 请求的特点:

首行里面的第一个部分就是 GET
URL 里面的 query string 可以为空,也可以不为空
GET 请求的 header 有若干个键值对结构
GET 请求的 body 一般是空的
POST 方法
基本介绍:

POST 方法也是一种常见的方法,多用于提交用户输入的数据给服务器(如登录页面)。以下几种方法都会触发 POST 方法的请求

通过 HTML 中的 form 标签可以构造 POST 请求
使用 JavaScript 的 ajax 可以构造 POST 请求
POST 请求的特点:

首行第一个部分就是 POST
URL 里面的 query string 一般是空的
POST 请求的 header 里面有若干个键值对
POST 请求的 body 一般不为空(body 的具体数据格式,由 header 中的 Content-Type 来描述;body 的具体数据长度,由 header 中的 Content-Length 来描述

GET 和 POST 的区别
其实 GET 和 POST 的区别是一个经典的面试题,以下为大家介绍如何在面试中回答上述问题

答题模板:

GET 和 POST 其实没有本质区别,使用 GET 的场景完全可以使用 POST 代替,使用 POST 的场景一样可以使用 GET 代替。但是在具体的使用上,还是存在一些细节的区别

GET 习惯上会把客户端的数据通过 query string 来传输(body 部分是空的);POST 习惯上会把客户端的数据通过 body 来传输(query string 部分是空的)
GET 习惯上用于从服务器获取数据;POST 习惯上是客户端给服务器提交数据
一般情况,程序员会把 GET 请求的处理,实现成“幂等”的;对于 POST 请求的处理,不要求实现成“幂等”
GET 请求可以被缓存,可以被浏览器保存到收藏夹中;POST 请求不能被缓存

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <fstream>
#include <memory>
#include "Log.hpp"
const std::string Separator = "\r\n";                                                      // 常量定义,表示回车换行符
static const std::string sep = "\r\n";                                                     // 静态常量定义,表示回车换行符
static const std::string header_sep = ": ";                                                // 静态常量定义,表示冒号和空格
static const std::string wwwroot = "wwwroot";                                              // 静态常量定义,表示wwwroot目录
static const std::string homepage = "index.html";                                          // 静态常量定义,表示首页文件名
static const std::string httpversion = "HTTP/1.0";                                         // 静态常量定义,表示HTTP版本
static const std::string httpversion = "HTTP/1.0";                                         // 静态常量定义,表示HTTP版本
static const std::string space = " ";                                                      // 静态常量定义,表示空格
static const std::string filesuffixsep = ".";                                              // 静态常量定义,表示文件后缀分隔符
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>; // 定义回调函数类型
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>;
using func_t = std::function<std::shared_ptr<HttpResponse>(std::shared_ptr<HttpRequest>)>; // 定义回调函数类型
class HttpRequest                                                                          // 定义一个HTTP请求类
{
private:std::string GetOneLine(std::string &str) // 读取一行数据{if (str.empty()){return std::string(); // 如果字符串为空,则返回空字符串}auto pos = str.find(Separator); // 查找回车换行符if (pos == std::string::npos)   // 如果没有找到回车换行符,则返回空字符串{return std::string();}std::string line = str.substr(0, pos);  // 截取一行数据str.erase(0, pos + Separator.length()); // 删除已读取的字符串return line.empty() ? Separator : line; // 如果一行数据为空,则返回回车换行符,否则返回一行数据}bool PraseHeaderHelper(const std::string &line, std::string *key, std::string *value) // 解析请求头{auto pos = line.find(':'); // 查找冒号if (pos == std::string::npos){return false; // 如果没有找到冒号,则返回false}*key = line.substr(0, pos);    // 截取键*value = line.substr(pos + 1); // 截取值}bool PraseRequestLine(){if (_req_line.empty()){return false; // 如果请求行为空,则返回false}std::stringstream ss(_req_line);      // 字符串流ss >> _method >> _url >> _version;    // 解析请求行_path += _url;                        // 路径if (_path[_path.length() - 1] == '/') // 如果URL以/结尾,则添加index.html{// TODO: 这里需要添加index.html的处理逻辑_path += homepage; // 这里需要添加index.html的处理逻辑}auto pos = _path.rfind(filesuffixsep); // rfind表示从右边开始查找,找到最后一个.的位置if (pos == std::string::npos){_suffix = ".html"; // 如果没有后缀名,则默认为.html}else{_suffix = _path.substr(pos); // 截取后缀名}LOG(INFO, "client wang get %s\n", _path.c_str()); // 打印日志return true;                                      // 解析成功}bool PraseHeader() // 解析请求头{for (const auto &line : _headers) // 遍历请求头{std::string key, value; // 定义键值对if (PraseHeaderHelper(line, &key, &value)){_header.insert(std::make_pair(key, value)); // 插入键值对}}}void Print() // 打印HTTP请求{std::cout << "===" << _req_line << "===" << std::endl; // 打印请求行for (const auto &header : _headers){std::cout << "***" << header << "***" << std::endl; // 打印请求头}std::cout << _Null_String << std::endl;                     // 打印请求正文;std::cout << "===" << _resq_text << "===" << std::endl;     // 打印响应std::cout << "method ###" << _method << "###" << std::endl; // 打印请求方法std::cout << "url ###" << _url << "###" << std::endl;       // 打印URLstd::cout << "path ###" << _path << "###" << std::endl;     // 打印路径std::cout << "args ###" << _args << "###" << std::endl;     // 打印参数}public:HttpRequest(){}void Serialization() // 序列化HTTP请求{}void DeSerialization(std::string &request) // 反序列化HTTP请求{_req_line = GetOneLine(request); // 读取请求行while (true){std::string line = GetOneLine(request); // 读取请求头if (line.empty()){break; // 读取到空行,则表示请求头结束}else if (line == Separator){_resq_text = request; // 读取请求正文;break;                // 读取到空行,则表示请求头结束}else{_headers.push_back(line); // 读取请求头}}}std::string Path() // 获取文件路径{return _path; // 为什么要获取文件路径?->为了找到文件并返回给客户端}std::string Suffix() // 获取文件后缀名{return _suffix;}std::string Method() // 获取请求方法{return _method;}std::string Args() // 获取参数{return _args;}std::string Text(){return _resq_text; // 读取请求正文;}~HttpRequest(){}private:std::string _req_line;                                // 请求行std::vector<std::string> _headers;                    // 请求头std::string _Null_String;                             // 空行std::string _resq_text;                               // 请求正文;std::unordered_map<std::string, std::string> _header; // 请求头std::string _method;  // 请求方法std::string _url;     // URLstd::string _args;    // 参数std::string _path;    // 路径std::string _suffix;  // 后缀名std::string _version; // HTTP版本
};class HttpResponse // 定义一个HTTP响应类
{
public:HttpResponse(): _version(httpversion),_code(200),_Null_String(Separator){}void AddStatusLine(int code, std::string message) // 添加状态行{_code = code;_message = message; // TODO: 这里需要添加响应信息_desc = "OK";       // TODO: 这里需要添加描述信息}void AddHeader(const std::string &key, const std::string &value) // 添加响应头{_header.insert(std::make_pair(key, value)); // 插入响应头_header[key] = value;                       // 插入响应头}void AddText(const std::string &text) // 添加响应内容{_resq_text = text; // 响应内容}std::string Serialize() // 序列化HTTP响应{std::string _status_line = _version + space + std::to_string(_code) + space + _desc + Separator; // 状态行for (const auto &header : _header){_headers.push_back(header.first + header_sep + header.second + Separator); // 响应头}// 序列化;std::string respstr = _status_line; // 状态行for (const auto &header : _headers){respstr += header; // 响应头}respstr += _Null_String; // 空行respstr += _resq_text;   // 响应内容return respstr;          // 序列化;}~HttpResponse(){}private:int _code;                                            // 响应代码std::string _desc;                                    // 响应描述std::string _message;                                 // 响应信息std::string _content;                                 // 响应内容std::string _version;                                 // HTTP版本std::unordered_map<std::string, std::string> _header; // 响应头std::string _req_line;                                // 请求行std::vector<std::string> _headers;                    // 请求头std::string _Null_String;                             // 空行std::string _resq_text;                               // 请求正文;
};class Factory // 定义一个工厂类
{
public:// 静态成员函数// 1.所有对象共享同一个函数// 2.静态成员函数只能访问静态成员变量// 3.静态成员函数也有访问权限 写在private里,类外是访问不到的static std::shared_ptr<HttpRequest> BuildHttpRequest() // 静态工厂函数,用于创建HTTP请求对象{return std::make_shared<HttpRequest>(); // 实例化一个HTTP请求对象并返回}static std::shared_ptr<HttpResponse> BuildHttpReponse() // 静态工厂函数,用于创建HTTP响应对象{return std::make_shared<HttpResponse>(); // 实例化一个HTTP响应对象并返回}
};class HttpServer // 定义一个HTTP服务器类
{
public:HttpServer(){_mime_type.insert(std::make_pair(".html", "text/html"));              // 定义文件类型_mime_type.insert(std::make_pair(".css", "text/css"));                // 定义文件类型_mime_type.insert(std::make_pair(".js", "application/x-javascript")); // 定义文件类型_mime_type.insert(std::make_pair(".png", "image/png"));               // 定义文件类型_mime_type.insert(std::make_pair(".jpg", "image/jpeg"));              // 定义文件类型_mime_type.insert(std::make_pair(".unknown", "text/html"));           // 定义文件类型_code_to_desc.insert(std::make_pair(100, "Continue"));              // 定义响应描述_code_to_desc.insert(std::make_pair(200, "OK"));                    // 定义响应描述_code_to_desc.insert(std::make_pair(301, "Moved Permanently"));     // 定义响应描述_code_to_desc.insert(std::make_pair(302, "Found"));                 // 定义响应描述_code_to_desc.insert(std::make_pair(404, "Not Found"));             // 定义响应描述_code_to_desc.insert(std::make_pair(500, "Internal Server Error")); // 定义响应描述}std::string ReadFileContent(const std::string path, int *Size) // 读取文件内容{std::ifstream ifs(path, std::ios::binary); // 打开文件if (!ifs.is_open()){return std::string(); // 打开失败,返回空字符串}ifs.seekg(0, ifs.end);      // 移动文件指针到文件末尾int filesize = ifs.tellg(); // 获取文件大小ifs.seekg(0, ifs.beg);      // 移动文件指针到文件开头std::string content;content.resize(filesize); // 预分配内存ifs.read((char *)content.c_str(), filesize);*Size = filesize; // 保存文件大小ifs.close();      // 关闭文件}std::string HandlerHttpReuqest(std::string request) // 处理HTTP请求的函数{
#ifdef TESTstd::cout << "HTTP Request: " << request << std::endl;       // 打印HTTP请求std::string response = "HTTP/1.0 200 OK\r\n";                // 200 OKresponse += "\r\n";                                          // 空行response += "<html><boby><h1>Hello C++!</h1></body></html>"; // 响应内容return response;
#else// return "HTTP/1.0 404 Not Found\r\n\r\n<html><body><h1>404 Not Found</h1></body></html>";std::shared_ptr<HttpRequest> req = Factory::BuildHttpRequest(); // 创建HTTP请求对象req->DeSerialization(request);                                  // 反序列化HTTP请求int content_size = 0;std::string content = ReadFileContent(req->Path(), &content_size); // 读取文件内容std::string suffix = req->Suffix();                                // 获取文件后缀名std::shared_ptr<HttpResponse> res = Factory::BuildHttpReponse();   // 创建HTTP响应对象res->AddStatusLine(200, "OK");                                     // 添加状态行res->AddHeader("Content-Type", _mime_type[suffix]);                // 添加响应头res->AddHeader("Content-Length", std::to_string(content_size));    // 添加响应头res->AddHeader("Location", "https://www.baidu.com");               // 添加响应头,重定向res->AddText(content);                                             // 添加响应内容return res->Serialize();                                           // 序列化HTTP响应int code = 0;                                                      // 响应代码if (req->Path() == "wwwroot/index.html"){code = 301;                                          // 重定向res->AddHeader("Location", "https://www.baidu.com"); // 添加响应头,重定向res->AddHeader("Content-Type", "text/html");         // 添加响应头}else{code = 200;                                                     // 200 OKint content_size = 0;                                           // 响应内容大小std::string text = ReadFileContent(req->Path(), &content_size); // 读取文件内容res->AddHeader("Content_length", std::to_string(content_size)); // 添加响应头res->AddText(text);if (text.empty()) // 如果文件内容为空,则返回404//文件内容为空表示文件不存在{code = 404;res->AddStatusLine(code, _code_to_desc[code]);                            //     添加状态行std::string text404 = ReadFileContent("wwwroot/404.html", &content_size); // 读取404页面内容res->AddHeader("Content-Length", std::to_string(content_size));           // 添加响应头res->AddHeader("Content-Type", _mime_type[".html"]);                      // 添加响应头res->AddText(text404);                                                    // 添加响应内容}else{std::string suffix = req->Suffix();                             // 获取文件后缀名res->AddStatusLine(code, _code_to_desc[code]);                  // 添加状态行res->AddHeader("Content-Length", std::to_string(content_size)); // 添加响应头res->AddText(text);                                             // 添加响应内容res->AddHeader("Content-Type", _mime_type[suffix]);             // 添加响应头}return res->Serialize(); // 序列化HTTP响应}
#endif}~HttpServer(){}private:int _code;                                               // 响应代码std::string _message;                                    // 响应信息std::string _content;                                    // 响应内容std::string _version;                                    // HTTP版本std::unordered_map<std::string, std::string> _header;    // 响应头std::string _req_line;                                   // 请求行std::vector<std::string> _headers;                       // 请求头std::string _Null_String;                                // 空行std::string _resq_text;                                  // 请求正文;std::unordered_map<std::string, std::string> _mime_type; // _mime_type表示文件类型,为什么要有文件类型->因为HTTP协议是基于文本的协议,所以需要知道文件类型才能知道如何解析std::unordered_map<int, std::string> _code_to_desc;      // _code_to_desc表示响应描述
};
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t *port){*port = ntohs(_addr.sin_port);*ip = inet_ntoa(_addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port){_addr.sin_family = AF_INET;_addr.sin_port = htons(_port);_addr.sin_addr.s_addr = inet_addr(_ip.c_str());}InetAddr(){}std::string Ip(){return _ip;}bool operator==(const InetAddr &addr){// if(_ip == addr._ip)if (_ip == addr._ip && _port == addr._port) // 方便测试{return true;}return false;}// bool operator = (const struct sockaddr_in &addr)// {//     _addr = addr;// }struct sockaddr_in Addr(){return _addr;}uint16_t Port(){return _port;}~InetAddr(){}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};
#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__#include <iostream>
#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex) : _mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t *_mutex;
};#endif
#pragma once#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"bool gIsSave = false;
const std::string logname = "log.txt";// 1. 日志是由等级的
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message;out.close();
}std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}std::string GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
// 2. 日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfid = getpid();char buffer[1024];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;LockGuard lockguard(&lock);// pthread_mutex_lock(&lock);if (!issave){std::cout << message;}else{SaveFile(logname, message);}// pthread_mutex_lock(&lock); // bug??// std::cout << levelstr << " : " << timestr << " : " << filename << " : " << line << ":" << buffer << std::endl;
}// C99新特性__VA_ARGS__
#define LOG(level, format, ...)                                                \do                                                                         \{                                                                          \LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__); \} while (0)#define EnableFile()    \do                  \{                   \gIsSave = true; \} while (0)
#define EnableScreen()   \do                   \{                    \gIsSave = false; \} while (0)// 默认传递进来的参数都是整数
// void Test(int num, ...)
// {
//     va_list arg;
//     va_start(arg, num);//     while(num)
//     {
//         int data = va_arg(arg, int);
//         std::cout << "data: " << data << std::endl;
//         num--;
//     }//     va_end(arg); // arg = NULL
// }
#include "TcpServer.hpp"
#include "Http.hpp"void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n"<< std::endl;
}
std::shared_ptr<HttpResponse> Login(std::shared_ptr<HttpRequest> req)
{LOG(DEBUG, "=========================\n");std::string userdata;if (req->Method() == "GET"){userdata = req->Args();}else if (req->Method() == "POST"){userdata = req->Text(); // 获取请求体}else{}// 1. 进程间通信, 比如 pipe! 还有环境变量!// 2. fork();// 3. exec(); python / php / java / C++// 处理数据了LOG(DEBUG, "enter data handler, data is : %s\n", userdata.c_str());auto response = Factory::BuildHttpReponse();response->AddStatusLine(200, "OK");response->AddHeader("Content-Type", "text/html");response->AddText("<html><h1>handler data done</h1></html>");LOG(DEBUG, "=========================\n");return response;
}
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return 1;}uint16_t port = atoi(argv[1]);                                                                                // 获取端口号HttpServer http_service;                                                                                      // 实例化HTTP服务器类TcpServer tcp_server(port, std::bind(&HttpServer::HandlerHttpReuqest, &http_service, std::placeholders::_1)); // 实例化TCP服务器类,绑定HTTP请求处理函数return 0;
}
#pragma once#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <memory>
#include "InetAddr.hpp"
#include "Log.hpp"// 模版方法模式
namespace socket_ns
{class Socket;const static int gbacklog = 8;using socket_sptr = std::shared_ptr<Socket>;enum{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR};// std::unique_ptr<Socket> listensock = std::make_unique<TcpSocket>();// listensock->BuildListenSocket();// std::unique_ptr<Socket> clientsock = std::make_unique<TcpSocket>();// clientsock->BuildClientSocket();// clientsock->send();// clientsock->Recv();class Socket{public:virtual void CreateSocketOrDie() = 0;             // 创建套接字virtual void BindSocketOrDie(InetAddr &addr) = 0; // 绑定本地地址virtual void ListenSocketOrDie() = 0;             // 监听套接字virtual socket_sptr Accepter(InetAddr *addr) = 0; // 接收客户端连接virtual bool Connetcor(InetAddr &addr) = 0;       // 连接服务器virtual void SetSocketAddrReuse() = 0;            // 设置套接字地址复用virtual int SockFd() = 0;                         // 获取套接字描述符virtual void SetSocketAddrReuse() = 0;            // 设置套接字地址复用->为什么要设置套接字地址复用?因为服务器程序在运行时,可能会多次启动,而端口号是临时端口,所以需要设置套接字地址复用,使得端口号可以被重复使用。virtual int Recv(std::string *out) = 0;           // 接收数据virtual int Send(const std::string &in) = 0;      // 发送数据virtual void Close() = 0;// virtual void Recv() = 0;// virtual void Send() = 0;// virtual void other() = 0;public:void BuildListenSocket(InetAddr &addr){CreateSocketOrDie();SetSocketAddrReuse();BindSocketOrDie(addr);ListenSocketOrDie();}bool BuildClientSocket(InetAddr &addr){CreateSocketOrDie();return Connetcor(addr);}// void BuildUdpSocket()// {//     CreateSocketOrDie();//     BindSocketOrDie();// }};class TcpSocket : public Socket{public:TcpSocket(int fd = -1) : _sockfd(fd){}void CreateSocketOrDie() override{// 1. 创建流式套接字_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success, sockfd is : %d\n", _sockfd);}void BindSocketOrDie(InetAddr &addr) override // 绑定本地地址{// 2. bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(addr.Port());local.sin_addr.s_addr = inet_addr(addr.Ip().c_str());int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); //_sockfd表示套接字描述符,(struct sockaddr *)&local表示要绑定的地址,sizeof(local)表示地址长度if (n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(DEBUG, "bind success, sockfd is : %d\n", _sockfd);}void ListenSocketOrDie() override{int n = ::listen(_sockfd, gbacklog);if (n < 0){LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success, sockfd is : %d\n", _sockfd);}socket_sptr Accepter(InetAddr *addr) override{struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);if (sockfd < 0){LOG(WARNING, "accept error\n");return nullptr;}*addr = peer;socket_sptr sock = std::make_shared<TcpSocket>(sockfd);return sock;}bool Connetcor(InetAddr &addr) override{// tcp client 要bind,不要显示的bind.struct sockaddr_in server;// 构建目标主机的socket信息memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(addr.Port());server.sin_addr.s_addr = inet_addr(addr.Ip().c_str());int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect error" << std::endl;return false;}return true;}void SetSocketAddrReuse() override{int opt = 1;::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));}int Recv(std::string *out) override{char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (n > 0){inbuffer[n] = 0;*out = inbuffer; // ??? +=}return n;}void SetSocketAddrReuse() override // 设置套接字地址复用->为什么要设置套接字地址复用?因为服务器程序在运行时,可能会多次启动,而端口号是临时端口,所以需要设置套接字地址复用,使得端口号可以被重复使用。{int opt = 1;                                                                       // 1表示开启地址复用::setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 设置套接字地址复用}int Send(const std::string &in) override{int n = ::send(_sockfd, in.c_str(), in.size(), 0);return n;}int SockFd() override{return _sockfd;}void Close() override{if (_sockfd > -1)::close(_sockfd);}private:int _sockfd;};// class SocketFactor// {// public://     void Build// }
} // namespace socket_ns
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <memory>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Socket.hpp"using namespace socket_ns;class TcpServer;using http_t = std::function<std::string(std::string reqeust)>;class ThreadData
{
public:ThreadData(socket_sptr fd, InetAddr addr, TcpServer *s) : sockfd(fd), clientaddr(addr), self(s){}public:socket_sptr sockfd;InetAddr clientaddr;TcpServer *self;
};
class TcpServer
{
public:TcpServer(int port, http_t service): _localaddr("0", port), // 本地地址为什么要设置为0?因为0.0.0.0可以表示任何地址,而0.0.0.0:8080表示本机的8080端口_listensock(std::make_unique<TcpSocket>()),_http_service(service),_isrunning(false){_listensock->BuildListenSocket(_localaddr);}static void *HandlerSock(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);std::string request, response;// 有较大的概率,读到的就是一个完整的http requestssize_t n = td->sockfd->Recv(&request); // 读取请求if (n > 0){response = td->self->_http_service(request); // self表示TcpServer对象td->sockfd->Send(response);                  // 发送应答}td->sockfd->Close();delete td;return nullptr;}void Loop(){_isrunning = true;while (_isrunning){InetAddr peeraddr;                                         // 客户端地址socket_sptr normalsock = _listensock->Accepter(&peeraddr); // 接受连接if (normalsock == nullptr){continue;}pthread_t t;                                                 // 线程ThreadData *td = new ThreadData(normalsock, peeraddr, this); // 创建线程数据}}void Loop() // 启动服务{_isrunning = true;// 4. 不能直接接受数据,先获取连接while (_isrunning){InetAddr peeraddr;socket_sptr normalsock = _listensock->Accepter(&peeraddr);if (normalsock == nullptr)continue;pthread_t t;ThreadData *td = new ThreadData(normalsock, peeraddr, this);pthread_create(&t, nullptr, HandlerSock, td); // 将线程分离}_isrunning = false;}~TcpServer(){}private:InetAddr _localaddr;std::unique_ptr<Socket> _listensock;bool _isrunning;http_t _http_service;
};

相关文章:

【HTTP】:应用层协议HTTP(1)

1.HTTP协议 虽然我们说,应用层协议是我们程序猿自己定的.但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用.HTTP(超文本传输协议)就是其中之一。 在互联网世界中&#xff0c;HTTP&#xff08;HyperTextTransfer Protocol&#xff0c;超文本…...

Boost Graph Library (BGL) 介绍与使用示例

Boost Graph Library (BGL) 介绍与使用示例 Boost Graph Library (BGL) 是 Boost 库中用于图论计算的模块&#xff0c;提供了处理图数据结构的通用接口和多种图算法实现。 BGL 主要特性 提供多种图表示方式&#xff1a;邻接表、邻接矩阵等包含常用图算法&#xff1a;DFS、BF…...

数据结构--线性表

单链表的基本操作 1.清空单链表 链表仍然存在&#xff0c;但链表中无元素&#xff0c;成为空链表&#xff08;头指针和头链表仍存在&#xff09;算法思路&#xff1a;依次释放所有结点&#xff0c;并将头结点指针设置为空 2.返回表长 3.取值–取单链表中第i个元素 因为存储…...

电商用户购物行为分析:基于K-Means聚类与分类验证的完整流程

随着电商行业的快速发展,用户行为分析成为企业优化营销策略、提升用户体验的重要手段。通过分析用户的购物行为数据,企业可以挖掘出用户群体的消费特征和行为模式,从而制定更加精准的营销策略。本文将详细介绍一个基于Python实现的电商用户购物行为分析系统,涵盖数据预处理…...

《车辆人机工程-汽车驾驶显示装置》实验报告

汽思考题 汽车显示装置有哪些&#xff1f; 汽车显示装置是车辆与驾驶员、乘客交互的重要界面&#xff0c;主要用于信息展示、功能控制和安全辅助。以下是常见的汽车显示装置分类及具体类型&#xff1a; 一、驾驶舱核心显示装置 1. 仪表盘&#xff08;Instrument Cluster&am…...

三维点云投影二维图像的原理及实现

转自个人博客&#xff1a;三维点云投影二维图像的原理及实现 1. 概述 1.1 原理概述 三维点云模型是由深度相机采集深度信息和RGB信息进行生成的&#xff0c;深度相机能直接获取到深度图和二维RGB图像&#xff0c;也就是说利用相机原本的关系就可以把深度信息投影回二维图像&a…...

使用Golang打包jar应用

文章目录 背景Go 的 go:embed 功能介绍与打包 JAR 文件示例1. go:embed 基础介绍基本特性基本语法 2. 嵌入 JAR 文件示例项目结构代码实现 3. 高级用法&#xff1a;嵌入多个文件或目录4. 使用注意事项5. 实际应用场景6. 完整示例&#xff1a;运行嵌入的JAR 背景 想把自己的一个…...

MySQL数据过滤、转换与标准化

数据处理是数据库操作的重要组成部分&#xff0c;尤其是在大量数据中查找、转换和规范化目标信息的过程中。为了确保数据的有效性与一致性&#xff0c;MySQL提供了一系列数据过滤、转换与标准化的功能。 本教程将深入探讨数据过滤和转换的基本方法及应用&#xff0c;内容涵盖数…...

Linux中安装sentinel

拉取镜像 #我默认拉取最新的 sentinel 镜像 docker pull bladex/sentinel-dashboard 创建容器 docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard 检查是否成功 docker ps 浏览器访问 默认账号密码是 sentinel/sentinel 成功了 开放sentinel端口或者关…...

大模型压缩训练(知识蒸馏)

AI的计算结果不是一个数值&#xff0c;而是一个趋势 一、模型压缩简介 1、深度学习&#xff08;Deep Learning&#xff09;因其计算复杂度或参数冗余&#xff0c;在一些场景和设备上限制了相应的模型部署&#xff0c;需要借助模型压缩、优化加速、异构计算等方法突破瓶颈。 …...

Matlab绘制函数方程图形

Matlab绘制函数方程图形&#xff1a; 多项式计算: polyval 函数 Values of Polynomials: polyval ( ) 绘制方程式图形&#xff1a; 代码如下&#xff1a; >> a[9,-5,3,7]; x-2:0.01:5; fpolyval(a,x); plot(x,f,LineWidth,2); xlabel(x); ylabel(f(x))…...

dify windos,linux下载安装部署,提供百度云盘地址

dify下载安装 dify1.0.1 windos安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.zip 链接: 百度网盘 请输入提取码 提取码: 1234 dify安装包 linux安装包百度云盘地址 通过网盘分享的文件&#xff1a;dify-1.0.1.tar.gz 链接: 百度网盘 请输入提取码 提取码…...

优化方法介绍(一)

优化方法介绍(一) 本博客是一个系列博客,主要是介绍各种优化方法,使用 matlab 实现,包括方法介绍,公式推导和优化过程可视化 1 失败案例介绍 本文在编写最速下降法的时候使用了经典的求解函数框架,并使用了自适应步长(alpha)机制,即加入参数flag,当出现梯度下降的情…...

Centos7.9 升级内核,安装RTX5880驱动

系统镜像下载 https://vault.centos.org/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso 系统安装步骤省略 开始安装显卡驱动 远程登录查看内核 [root192 ~]# uname -a Linux 192.168.119.166 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x8…...

计算轴承|滚动轴承故障频率

一、轴承故障频率概述 在旋转机械故障诊断中&#xff0c;轴承故障频率&#xff08;BPFO、BPFI、BSF、FTF&#xff09;是重要的分析依据。通过计算这些特征频率&#xff0c;可以帮助工程师&#xff1a; 识别轴承故障类型&#xff08;内圈/外圈/滚动体故障&#xff09;制定振动…...

Python 数据分析01 环境搭建教程

Python 数据分析01 环境搭建教程 一、安装 Python 环境 访问 Python 官方网站 Python 官网&#xff0c;选择适合你操作系统的 Python 版本进行下载。下载完成后&#xff0c;运行安装程序。在安装过程中&#xff0c;建议选择“Add Python to PATH”选项&#xff0c;这样可以在…...

程序化广告行业(80/89):近年发展动态与技术标准演进

程序化广告行业&#xff08;80/89&#xff09;&#xff1a;近年发展动态与技术标准演进 大家好&#xff01;在技术领域探索的过程中&#xff0c;我深刻认识到知识分享的力量&#xff0c;它能让我们在学习的道路上加速前行。写这篇博客&#xff0c;就是希望能和大家一起深入剖析…...

Node.js cluster模块详解

Node.js cluster 模块详解 cluster 模块允许你轻松创建共享同一服务器端口的子进程&#xff08;worker&#xff09;&#xff0c;充分利用多核 CPU 的性能。它是 Node.js 实现高并发的重要工具。 核心概念 主进程&#xff08;Master&#xff09;&#xff1a;负责管理工作进程…...

2025年认证杯数学建模C题完整分析论文(共39页)(含模型、可运行代码)

2025年认证杯数学建模竞赛C题完整分析论文 目录 摘要 一、问题重述 二、问题分析 三、模型假设 四、 模型建立与求解 4.1问题1 4.1.1问题1解析 4.1.2问题1模型建立 4.1.3问题1求解代码 4.1.4问题1求解结果 4.2问题2 4.2.1问题2解析 4.2.2问题2模型建…...

PostgreSQL 的 COPY 命令

PostgreSQL 的 COPY 命令 PostgreSQL 的 COPY 命令是高效数据导入导出的核心工具&#xff0c;性能远超常规 INSERT 语句。以下是 COPY 命令的深度解析&#xff1a; 一 COPY 命令基础 1.1 基本语法对比 命令类型语法示例执行位置文件访问权限服务器端COPYCOPY table FROM /p…...

MySQL进阶-存储引擎索引

目录 一&#xff1a;存储引擎 MySQL体系结构 存储引擎介绍 存储引擎特点 InnoDB MyISAM Memory 区别及特点 存储引擎选择 索引 索引概述 介绍 演示 特点 索引结构 概述 二叉树 B-Tree BTree Hash 索引分类 索引分类 聚集索引&二级索引 一&#xff1…...

为什么需要Refresh Token?

后端服务性能 一种方案是在服务器端保存 Token 状态&#xff0c;用户每次操作都会自动刷新&#xff08;推迟&#xff09; Token 的过期时间——Session 就是采用这种策略来保持用户登录状态的。然而仍然存在这样一个问题&#xff0c;在前后端分离、单页 App 这些情况下&#x…...

基于3A4000及CentOS的银河麒麟V10离线源码编译安装VLC

碰到过的一个具体问题&#xff1a; 源码安装vlc-3.0.x版本&#xff0c;需要注意的是&#xff0c;不要安装ffmpeg-5及以上的版本&#xff0c;即只支持ffmpeg-4的版本&#xff0c;因此&#xff0c;要安装vlc-3.0版本&#xff0c;一个重要的依赖时就会ffmpeg-4。报错没有revision…...

Windows for Redis 后台服务运行

下载 redis 安装包 地址&#xff1a;https://github.com/tporadowski/redis/releases 解压zip压缩包&#xff0c;执行 redis-server.exe 即可以窗口模式运行&#xff08;窗口关闭则服务关闭&#xff09; 运行窗口可以看到&#xff0c;端口是 6379 我这里使用 nvaicat 客服端测…...

前端工程化-包管理NPM-package.json 和 package-lock.json 详解

package.json 和 package-lock.json 详解 1.package.json 基本概念 package.json 是 Node.js 项目的核心配置文件&#xff0c;它定义了项目的基本信息、依赖项、脚本命令等。 主要字段 基本信息字段 name: 项目名称&#xff08;必填&#xff09; version: 项目版本&#xf…...

如何在 Linux 中彻底终止被 `Ctrl+Z` 挂起的进程?

问题场景 在 Linux 终端操作时&#xff0c;你是否曾遇到过这样的情况&#xff1f; 当运行一个命令&#xff08;如 ping www.baidu.com&#xff09;时&#xff0c;不小心按下了 CtrlZ&#xff0c;屏幕上显示类似以下内容&#xff1a; ^Z [2] 已停止 ping www.b…...

人工智能100问☞第3问:深度学习的核心原理是什么?

目录 一、通俗解释 二、专业解析 三、权威参考 深度学习的核心原理是​​通过构建多层神经网络结构,逐层自动提取并组合数据特征,利用反向传播算法优化参数,从而实现对复杂数据的高层次抽象和精准预测​​。 一、通俗解释 ​​深度学习的核心原理,就像是教计算机像婴儿…...

基于若依和elementui实现文件上传(导入Excel表)

基于若依和elementui实现文件上传&#xff08;导入Excel表&#xff09; 前端部分&#xff1a; 若依封装了Apache的poi功能&#xff0c;实现文件的上传和下载 若依使用的是JS语法&#xff0c;需要改造为JS语法才能使用 若依如何解决跨域的问题&#xff1a; 在前端的配置文件中…...

2025年第十六届蓝桥杯省赛真题解析 Java B组(简单经验分享)

之前一年拿了国二后&#xff0c;基本就没刷过题了&#xff0c;实力掉了好多&#xff0c;这次参赛只是为了学校的加分水水而已&#xff0c;希望能拿个省三吧 >_< 目录 1. 逃离高塔思路代码 2. 消失的蓝宝思路代码 3. 电池分组思路代码 4. 魔法科考试思路代码 5. 爆破思路…...

OpenHarmony人才认证证书

OpenHarmony人才认证体系目前支持初级工程师认证&#xff0c;要求了解OpenHarmony开源项目、生态进展及系统移植等基础知识&#xff0c;熟练掌握OpenHarmony的ArkUI、分布式软总线、分布式硬件、分布式数据管理等基础能力使用&#xff0c;具备基础的开发能力。 考试流程可参考O…...

Docker--利用dockerfile搭建mysql主从集群和redis集群

Docker镜像制作的命令 链接 Docker 镜像制作的注意事项 链接 搭建mysql主从集群 mysql主从同步的原理 MySQL主从同步&#xff08;Replication&#xff09;是一种实现数据冗余和高可用性的技术&#xff0c;通过将主数据库&#xff08;Master&#xff09;的变更操作同步到一个…...

LLaMA-Factory双卡4090微调DeepSeek-R1-Distill-Qwen-14B医学领域

unsloth单卡4090微调DeepSeek-R1-Distill-Qwen-14B医学领域后&#xff0c;跑通一下多卡微调。 1&#xff0c;准备2卡RTX 4090 2&#xff0c;准备数据集 医学领域 pip install -U huggingface_hub export HF_ENDPOINThttps://hf-mirror.com huggingface-cli download --resum…...

使用ZSH美化Windows系统Git Bash

此前&#xff0c;我们讲解了一种借助 Windows Subsystem for Linux&#xff08;WSL&#xff09;让用户在 Windows 操作系统中运用 Linux Shell 命令&#xff0c;进而高效地实现文件访问、编译等开发工作。 Windows系统命令行的最佳实践 | 听到微笑的博客 这种借助 Windows Su…...

如何使用PyCharm自动化测试

如何使用PyCharm自动化测试 1.打开PyCharm右击文件&#xff0c;点击新建项目 按照如图配置&#xff0c;然后点击创建 2.创建好后&#xff0c;点击文件&#xff0c;然后点击设置 按照如图步骤&#xff0c;查看selenium和webdriver-manager是否存在 3.以上都完成后按照如图创…...

56.评论日记

2025年4月12日22:06:08 小米事故下的众生相_哔哩哔哩_bilibili...

EMI滤波器和ESD保护等效参数汇总

EMI 共模抑制与ESD设计参考用,特别是工业和机器人&#xff0c;伺服器类产品&#xff0c;特别关注&#xff0c;提高产品稳定性 基带接口 通道数 线性小信号等效参数 数字端口时钟频率 备注 Rline Cline 电池反接 1 — 240Pf — 过压和电池反接保护 …...

java -jar与java -cp的区别

java -jar与java -cp 1、情景描述2、情景分析3、两者区别 通常情况下&#xff0c;我们会看到以下两种命令启动的Java程序&#xff1a; java -jar xxx.jar [args] java -cp xxx.jar mainclass [args]这两种用法有什么区别呢&#xff1f; 1、情景描述 1&#xff09;Java打包单个…...

蓝桥杯嵌入式十五届模拟三(串口、双ADC)

一.LED 先配置LED的八个引脚为GPIO_OutPut&#xff0c;锁存器PD2也是&#xff0c;然后都设置为起始高电平&#xff0c;生成代码时还要去解决引脚冲突问题 二.按键 按键配置&#xff0c;由原理图按键所对引脚要GPIO_Input 生成代码&#xff0c;在文件夹中添加code文件夹&#…...

04-算法打卡-数组-二分查找-leetcode(69)-第四天

1 题目地址 69. x 的平方根 - 力扣&#xff08;LeetCode&#xff09;69. x 的平方根 - 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。注意&#xff1a;不允许使用任何内…...

SpringBoot项目:部门管理系统

文章目录 1、工程搭建1.1 创建项目1.2 创建数据库1.3 准备基础代码1.4 准备mapper接口1.5 准备service层1.6 准备controller层2、接口开发2.1 查询部门2.1.1 接口开发1、工程搭建 1.1 创建项目 主要内容: 创建Springboot工程引入web开发起步依赖、mybatis、mysql驱动、lombok…...

MyBatis-Plus 扩展功能

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 逻辑删除一、配置逻辑删除字段方式一&#xff1a;全局配置&#xff08;推荐&#xff09;方式二&#xff1a;实体类注解配置 二、逻辑删除流程三、完整代码示例1. 实…...

service和endpoints是如何关联的?

在Kubernetes中&#xff0c;Service 和 Endpoints 是两个密切关联的对象&#xff0c;它们共同实现了服务发现和负载均衡的功能。以下是它们之间的关联和工作原理&#xff1a; 1. Service 的定义 Service 是一种抽象&#xff0c;定义了一组逻辑上相关的 Pod&#xff0c;以及用…...

MyBatis-plus 快速入门

提示&#xff1a;MyBatis-Plus&#xff08;MP&#xff09;是一个 MyBatis的增强版 文章目录 前言使用MybatisPlus的基本步骤1、引入MybatisPlus依赖代替Mybatis依赖2、定义Mapper接口并继承BaseMapper他是怎么知道哪张表&#xff0c;哪些字段呢 3、实体类注解4、根据需要添加配…...

【PySpark大数据分析概述】03 PySpark大数据分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PySpark大数据分析与应用 ⌋ ⌋ ⌋ PySpark作为Apache Spark的Python API&#xff0c;融合Python易用性与Spark分布式计算能力&#xff0c;专为大规模数据处理设计。支持批处理、流计算、机器学习 (MLlib) 和图计算 (GraphX)&am…...

C# --- IEnumerable 和 IEnumerator

C# --- IEnumerable 和 IEnumerator IEnumerableIEnumeratorIEnumerable 和 IEnumerator 的作用手动实现 IEnumerableIEnumerable vs. IQueryable为什么有了ienumerator还需要ienumerable IEnumerable 在C#中&#xff0c;IEnumerable 是一个核心接口&#xff0c;用于表示一个可…...

Excel VBA 运行时错误1004’:方法‘Open’作用于对象‘Workbooks’时失败 的解决方法

使用Excel编写VBA脚本时出现如下错误&#xff1a; 运行时错误1004’: 方法‘Open’作用于对象‘Workbooks’时失败 我的功能是打开一系列excel文件从中自动复制数据到汇总excel的各个指定的sheet中&#xff0c;来源的excel是从网站上下载的。 出现这个问题后从网上查找各种办…...

03-算法打卡-数组-二分查找-leetcode(34)-第三天

1 题目地址 34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09;34. 在排序数组中查找元素的第一个和最后一个位置 - 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置…...

利用python从零实现Byte Pair Encoding(BPE)

喜欢可以到我的主页订阅专栏哟(^U^)ノ~YO 第一章:自然语言处理与分词技术基础 1.1 自然语言处理的核心挑战 自然语言处理(Natural Language Processing, NLP)作为人工智能领域的重要分支,其核心目标是实现计算机对人类语言的理解与生成。在深度学习技术快速发展的今…...

Redis的分布式锁

Redis的分布式锁 一.分布式锁的简介二.分布式锁的实现1.基本实现2.引入过期时间3.引入校验ID4.引入Lua5.引入看门狗(watch dog)6.引入RedLock算法 一.分布式锁的简介 在一个分布式的系统中&#xff0c; 会涉及到多个节点访问一个公共资源的情况&#xff0c;此时就需要通过锁的…...

SpringBoot分布式项目中实现智能邮件提醒系统

一、应用场景与需求分析 在电商、OA、客服等系统中,邮件提醒是用户触达的重要方式。本文针对以下典型需求进行方案设计: 多类型支持:订单超时、服务到期、待办通知等场景动态内容:支持纯文本/HTML/模板引擎内容格式智能重发:24小时未处理自动升级提醒级别高可用性:分布式…...