Linux网络-自定义协议、序列化和反序列化、网络计算服务器的实现和Windows端客户端

文章目录

  • 前言
  • 一、自定义协议
    • 传结构体对象
  • 序列化和反序列化
    • 什么是序列化?
    • 反序列化
  • 二、计算器服务端(线程池版本)
    • 1.main.cc
    • 2.Socket.hpp
    • 3.protocol.hpp
    • 4.Calculator.hpp
    • 5.serverCal.hpp
    • 6.threadPool.hpp
    • 7.Task.hpp
    • 8. log.hpp
  • 客户端
  • Windows客户端
  • 运行


前言

我们已经学会了Tcp、Udp网络传输协议,并且之前我们也实现了简易的聊天室和翻译器。

我们知道,传输层是OS系统里就给我们写好的, 应用层才是我们自己需要去编写的,现在我们对应用层来进行一些初步的了解。


一、自定义协议

在之前我们使用Tcp和Udp服务进行网络通信,我们一直都是以字符串的形式互相发送消息。

那么我们就只能发送字符串吗? 当然不是,Udp是基于数据流进行网络通信,Udp是基于字节流进行网络通信,虽然我们对字节流和数据流并没有一个特别清晰的认识,但是我们可以知道的是,我们其实是可以传各种各样的类型进行通行的。

这里就提出一种方案

传结构体对象

从我们之前写的代码来看,只要我们在发送数据和接收数据时制定一个协议,每次只读写一个结构体对象的大小,那么我们就可以将该结构体进行通信。 而实际上,底层的很多库也确实是这么做的,但是这并不代表这种方式不存在明显弊端。

最大的弊端就是,你能保证在不同环境之下,同样的结构体类型在不同主机、不同环境下的大小是一样的吗。 结构体的大小涉及到许多,比如说结构体字节对齐,32位和64位系统下内置类型大小可能不同…

所以我们并不推崇这种方案。

而实际上我们其实更推崇传字符串的方式。

序列化和反序列化

什么是序列化?

在现实生活中,我们介绍自己,有些人习惯先说自己的名字,有些人习惯先说自己来自于哪一个城市。 如果你需要去统计大量的人的信息,最好就是先列一个表格,然后让他们严格按照表格上的个人信息顺序去介绍自己,这就是序列化。

再比如说我们今天要写一个网络版本的计算器,我可以写成1+1,也可以写成一加一,再也可以写写成1 + 1,中间带几个空格。 那么这样的话,我们服务器接受到的数据就是形形色色的,不利于我们去解析。

于是我们就制定一个协议,你必须要写成"1 + 1"的形式,否则就是违反协议!

反序列化

反序列化很简单,就比如说我们收到了一段数据,它是一个字符串形式的"1 + 1",我们就需要将该字符串进行解析,反序列化成int x = 1, char op = ‘+’ , int y = 1.
这也是一种反序列化。

二、计算器服务端(线程池版本)

1.main.cc

#include "serverCal.hpp"void Usage(const char *mes)
{std::cout << "Usage: " << mes << " port[8080-9000]" << std::endl;
}const std::string default_ip = "0.0.0.0";enum{Usage_Err = 1
};int main(int argc, char *argv[])
{if (argc != 2){Usage("./serverCal");exit(Usage_Err);}ServerCal sc;sc.Init(AF_INET, default_ip, atoi(argv[1]));sc.Run();return 0;
}

2.Socket.hpp

对套接字进行封装

#pragma once#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "log.hpp"
enum
{Socket_Err = 1,Bind_Err,Listen_Err
};extern Log lg;const int backlog = 10;class Socket
{
public:Socket(): _sockfd(-1){}int Getfd(){return _sockfd;}void Init(){int socket_fd = socket(AF_INET, SOCK_STREAM, 0);if (socket_fd < 0){lg(Fatal, "Socket Create Failed...");exit(Socket_Err);}lg(Info, "Socket Create Succeeded...");_sockfd = socket_fd;}void Bind(const int sinfamily, const std::string &ip, const uint16_t port){memset(&_sockaddr, 0, sizeof _sockaddr);switch (sinfamily){case AF_INET:_sockaddr.sin_family = AF_INET;break;case AF_INET6:_sockaddr.sin_family = AF_INET6;break;}_sockaddr.sin_port = htons(port);inet_aton(ip.c_str(), &(_sockaddr.sin_addr));int n = bind(_sockfd, (const struct sockaddr *)&_sockaddr, sizeof _sockaddr);if (n < 0){lg(Fatal, "Bind Failed...");exit(Bind_Err);}lg(Info, "Bind Succeeded..., port: %d", port);}void Listen(){int n = listen(_sockfd, backlog);if (n < 0){lg(Fatal, "Listen Failed...");exit(Listen_Err);}lg(Info, "Listen Succeeded...");}int Accept(struct sockaddr_in *clientsock, socklen_t *len){int fd = accept(_sockfd, (sockaddr *)clientsock, len);if (fd < 0){lg(Warning, "Accept Failed...");return -1;}lg(Info, "Accept Succeeded..., Get A new Link, fd: %d", fd);return fd;}int Connect(const std::string &ip, const std::string &port){struct sockaddr_in serversock;serversock.sin_port = htons(atoi(port.c_str()));serversock.sin_family = AF_INET;inet_aton(ip.c_str(), &serversock.sin_addr);int n = connect(_sockfd, (const struct sockaddr *)&serversock, sizeof serversock);if (n < 0){lg(Warning, "Accept Failed...");return n;}lg(Info, "Connect Succeeded...");return n;}~Socket(){close(_sockfd);}private:int _sockfd;struct sockaddr_in _sockaddr;
};

3.protocol.hpp

序列化和反序列化的协议制定

#pragma once
#include <iostream>
#include <string>
#include "log.hpp"extern Log lg;
const char blank_space_sep = ' ';
const char protocol_sep = '\n';enum Code
{Div_Zero_Err = 1,Mod_Zeor_Err,Operatorr_Err
};class Request
{
public:Request() {} // 提供一个无参构造Request(int x, int y, char op): _x(x), _y(y), _operator(op) {}bool serialize(std::string *out_str){// 协议规定 字符串格式应序列化为"len\n""_x + _y\n"std::string main_body = std::to_string(_x);main_body += blank_space_sep;main_body += _operator;main_body += blank_space_sep;main_body += std::to_string(_y);*out_str = std::to_string(main_body.size());*out_str += protocol_sep;*out_str += main_body;*out_str += protocol_sep;return true;}bool deserialize(std::string &in_str){// 协议规定 in_str的格式应为"len\n""_x + _y\n..."size_t pos = in_str.find(protocol_sep);if (pos == std::string::npos){// 说明没找到'\n'lg(Warning, "Message Format Error..., No Found The First Second \\n");return false;} std::string sl = in_str.substr(0, pos);int len = std::stoi(sl); // 如果这里的sl不是一串数字,stoi就会抛异常! BUGint total_len = sl.size() + 1 + len + 1;if (in_str.size() < total_len){lg(Warning, "Message Format Error..., Lenth Error");return false;}if (in_str[total_len - 1] != '\n'){lg(Warning, "Message Format Error..., No Found The Second \\n");return false;}std::string main_body = in_str.substr(pos + 1, len);// main_body"_x +  _y"int left = main_body.find(blank_space_sep);if (left == std::string::npos){// 说明没找到' 'lg(Warning, "Message Format Error..., No Found The First ' '");return false;}int right = main_body.rfind(blank_space_sep);if (left == right){// 说明只有一个' 'lg(Warning, "Message Format Error...,No Found The Second ' '");return false;}_x = std::stoi(main_body.substr(0, left));   // 如果这里的sl不是一串数字,stoi就会抛异常! BUG_y = std::stoi(main_body.substr(right + 1)); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG_operator = main_body[left + 1];in_str.erase(0, total_len);return true;}void print(){std::cout << _x << " " << _operator << " " << _y << std::endl;}~Request() {}public:int _x;int _y;char _operator;
};class Respond
{
public:Respond() {} // 提供一个无参构造Respond(int result, int code): _result(result), _code(code) {}bool serialize(std::string *out_str){// 协议规定 字符串格式应序列化为"_result _code"*out_str = std::to_string(_result);*out_str += blank_space_sep;*out_str += std::to_string(_code);return true;}bool deserialize(const std::string &in_str){// 协议规定 in_str的格式应为"_result _code"size_t pos = in_str.find(blank_space_sep);if (pos == std::string::npos){// 没找到字符' 'lg(Warning, "Result Message Error...");return false;}_result = std::stoi(in_str.substr(0, pos));_code = std::stoi(in_str.substr(pos + 1));return true;}void print(){std::cout << _result << " " << _code << std::endl;}~Respond() {}public:int _result;int _code; // 表示结果可信度  0表示可信
};

4.Calculator.hpp

计算器功能接口函数

#pragma once
#include"protocol.hpp"class Calculator{public:Calculator() {}Respond calculate(const Request& rq){Respond rs;switch (rq._operator){case '+':rs._result = rq._x + rq._y;break;case '-':rs._result = rq._x - rq._y;break;case '*':rs._result = rq._x * rq._y;break;case '/':if(rq._y == 0){lg(Warning,"Found Div Zero Error...");rs._code = Div_Zero_Err;}rs._result = rq._x / rq._y;break;case '%':if(rq._y == 0){lg(Warning,"Found Mod Zero Error...");rs._code = Mod_Zeor_Err;}rs._result = rq._x - rq._y;break;default:lg(Warning,"Found Operator Error...");rs._code = Operatorr_Err;break;}return rs;}
};

5.serverCal.hpp

代码如下(示例):

#pragma once#include "Socket.hpp"
#include "protocol.hpp"
#include "threadPool.hpp"
#include "Task.hpp"
class ServerCal
{
public:ServerCal(){}void Init(const int sinfamily, const std::string &ip, const uint16_t port){_listensock.Init();_listensock.Bind(sinfamily, ip, port);_listensock.Listen();}void Run(){ThreadPool<Task> *tp = ThreadPool<Task>::GetInstance();tp->Start();struct sockaddr_in client;while (true){memset(&client, 0, sizeof client);socklen_t len;int socketfd = _listensock.Accept(&client, &len);if (socketfd < 0)continue;tp->Push(socketfd);}}private:Socket _listensock;
};

6.threadPool.hpp

很熟悉的线程池封装

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 10;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(const pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){// std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPool<T> *tp_;static pthread_mutex_t lock_;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

7.Task.hpp

派发给线程池的任务

#pragma once
#include "Socket.hpp"
#include "protocol.hpp"
#include "Calculator.hpp"
class Task
{
public:Task(int socket_fd): _socket_fd(socket_fd){}void run(){char in_buffer[1024];Calculator cal;std::string message = "";while (true){memset(in_buffer, 0, sizeof in_buffer);//std:: cout << "开始等待读取..." <<std::endl;int n = read(_socket_fd, (void *)in_buffer, sizeof in_buffer - 1);//std::cout << n << " " << strerror(errno) <<std::endl;//std::cout << "读取到的有效字符为" << n << std::endl;if (n == 0){lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...", _socket_fd);break;}else if (n < 0){lg(Warning, "Read Error, socketfd[%d]...", _socket_fd);break;}in_buffer[n] = 0;message += in_buffer;//std::cout << "报文大小: "<< message.size() <<" ,报文内容: "<< message << std::endl;Request rq;if(!rq.deserialize(message))  continue;Respond rs = cal.calculate(rq);std::string res;rs.serialize(&res);printf("%d %c %d = %d\n",rq._x,rq._operator,rq._y,rs._result);write(_socket_fd, res.c_str(), res.size());}}void operator()(){run();close(_socket_fd);}~Task(){}private:int _socket_fd;
};

8. log.hpp

输出日志消息

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};Log lg;

客户端

#include "Socket.hpp"
#include "protocol.hpp"void Usage(const char *mes)
{std::cout << "Usage: " << mes << " ip[xxx.xxx.xxx.xxx] port[8080-9000]" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage("./clientCal");}Socket local;local.Init();int n = local.Connect(argv[1], argv[2]);if (n < 0){return 1;}int localfd = local.Getfd();std::cout << "       简易计算器, 目前仅支持\" + - * / %\"运算符 " << std::endl;std::cout << "       数字和运算符请用空格或回车隔开" << std::endl;Request rq;Respond rs;std::string message;char buffer[1024];while (true){memset(buffer, 0, sizeof buffer);std::cout << "请输入您的算式@ ";std::cin >> rq._x >> rq._operator >> rq._y;rq.serialize(&message);write(localfd, message.c_str(), message.size());// 开始等待结果n = read(localfd, buffer, sizeof buffer - 1);if (n == 0){lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...",localfd);break;}else if (n < 0){lg(Warning, "Read Error, socketfd[%d]...", localfd);break;}buffer[n] = 0;std::string res = buffer;rs.deserialize(res);if(rs._code != 0){switch(rs._code){case 1:std::cout << "出现除0错误" << std::endl;break;case 2:std::cout << "出现模0错误" << std::endl;break;case 3:std::cout << "使用了除 + - * / % 以外的运算符" << std::endl;break;default:std::cout << "发生未知错误" <<std::endl;break;}continue;}printf("%d %c %d = %d\n",rq._x,rq._operator,rq._y,rs._result);}return 0;
}

Windows客户端

#include<iostream>
#include<string>
#include<WinSock2.h>
#include<Windows.h>
#include<functional>
#include<stdlib.h>
#include"protocol.hpp"#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable:4996)
#pragma execution_character_set("utf-8")const int server_port = 8889;
const std::string server_ip = "43.143.58.29";int main()
{//初始化网络环境WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);system("chcp 65001");//申请套接字SOCKET socket_fd = socket(AF_INET, SOCK_STREAM, 0);if (socket_fd == SOCKET_ERROR){perror("Socket Error");exit(1);}//创建并初始化Server端sockaddr_in结构体struct sockaddr_in server;memset(&server, 0, sizeof server);server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(server_ip.c_str());server.sin_port = htons(server_port);//开始连接服务器int n = connect(socket_fd, (const struct sockaddr*)&server, sizeof server);if (n < 0){//连接失败std::cout << "Connect Failed" << std::endl;return 1;}std::cout << "       简易计算器, 目前仅支持\" + - * / %\"运算符 " << std::endl;std::cout << "       数字和运算符请用空格或回车隔开" << std::endl;Request rq;Respond rs;std::string message;char buffer[1024];while (true){memset(buffer, 0, sizeof buffer);std::cout << "请输入您的算式@ ";std::cin >> rq._x >> rq._operator >> rq._y;rq.serialize(&message);send(socket_fd, message.c_str(), (int)message.size(),0);// 开始等待结果n = recv(socket_fd, buffer, sizeof buffer - 1,0);if (n == 0){lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...", socket_fd);break;}else if (n < 0){lg(Warning, "Read Error, socketfd[%d]...", socket_fd);break;}buffer[n] = 0;std::string res = buffer;rs.deserialize(res);if (rs._code != 0){switch (rs._code){case 1:std::cout << "出现除0错误" << std::endl;break;case 2:std::cout << "出现模0错误" << std::endl;break;case 3:std::cout << "使用了除 + - * / % 以外的运算符" << std::endl;break;default:std::cout << "发生未知错误" << std::endl;break;}continue;}printf("%d %c %d = %d\n", rq._x, rq._operator, rq._y, rs._result);}//清理环境closesocket(socket_fd);WSACleanup();return 0;
}

运行

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小白级教程—安装Ubuntu 20.04 LTS服务器

下载 本教程将使用20.04版进行教学 由于官方速度可能有点慢&#xff0c;可以下方的使用清华镜像下载 https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/ 点击20.24版本 选择 ubuntu-20.04.6-live-server-amd64.iso 新建虚拟机 下载好后 我们使用 VMware 打开它 这里选…

一篇文章讲透排序算法之归并排序

0.前言 本篇文章将详细解释归并排序的原理&#xff0c;以及递归和非递归的代码原理。 一.概念 归并排序是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使…

苹果或面临退一赔三,新iPad悄悄砍了核心规格

618 快过去了一半&#xff0c;各家都卖的如火如荼&#xff0c;这其中当属苹果搞得最热火朝天。 某东手机竞速榜中&#xff0c;iPhone15 Pro Max 销量稳坐头把交椅&#xff0c;平板方面虽然没有统计表&#xff0c;但是相信销量也是不差。 加上今年刚刚发布的新系列的 iPad&…

求助!什么软件可以人声分离?手机上可以进行人声分离操作吗?

在数字时代&#xff0c;音频处理变得越来越重要&#xff0c;而人声分离技术则是其中的一项关键技术。很多人可能都有过这样的疑问&#xff1a;什么软件可以实现人声分离&#xff1f;手机上能否进行人声分离操作&#xff1f;今天&#xff0c;我们就来为大家解答这些问题&#xf…

提取伴奏与人声分离软件:5款手机必备音频软件

在数字音乐的浪潮中&#xff0c;音频处理软件已经成为手机用户不可或缺的工具。特别是在音乐制作、卡拉OK伴奏制作以及日常音频编辑中&#xff0c;人声与伴奏的分离显得尤为重要。本文将为您介绍五款免费且实用的手机音频软件&#xff0c;它们都具有人声与伴奏分离的功能&#…

spring boot 3.x版本 引入 swagger2启动时报错

一&#xff0c;问题 Spring Boot 3.x版本的项目里&#xff0c;准备引入Swagger2作为接口文档&#xff0c;但是项目启动报错&#xff1a; java.lang.TypeNotPresentException: Type javax.servlet.http.HttpServletRequest not present at java.base/sun.reflect.generics.…

安装windows x64的开源录屏软件GifCapture

下载压缩包 GIF软件 安装报错 下载.NET桌面版运行 .NET 即可在最近安装部分找到GifCapture打开使用

容器项目之前后端分离

容器化部署ruoyi项目 #需要的镜像nginx、java、mysql、redis、 #导入maven镜像、Java镜像和node镜像 docker load -i java-8u111-jdk.tar docker load -i maven-3.8.8-sapmachine-11.tar docker load -i node-18.20.3-alpine3.20.tar #拉取MySQL和nginx镜像 docker pull mysql…

【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析

🔥 个人主页:空白诗 🔥 热门专栏:【JavaScript】 文章目录 🌿 引言五、 Destructuring Assignment - 解构赋值,数据提取的艺术 🎨📌 数组解构📌 对象解构&

文件夹突变解析:类型变文件的数据恢复与预防

在数字化时代&#xff0c;文件夹作为我们存储和组织数据的基本单元&#xff0c;其重要性不言而喻。然而&#xff0c;有时我们可能会遇到一种令人困惑的情况——文件夹的类型突然变为文件&#xff0c;导致无法正常访问其中的内容。这种现象不仅会影响我们的工作效率&#xff0c;…

Solon2分布式事件总线的应用价值探讨

随着现代软件系统的复杂性日益增加&#xff0c;微服务架构逐渐成为开发大型应用的主流选择。在这种架构下&#xff0c;服务之间的通信和协同变得至关重要。Solon2作为一个高性能的Java微服务框架&#xff0c;其分布式事件总线&#xff08;Distributed Event Bus&#xff09;为微…

FastAPI给docs/配置自有域名的静态资源swagger-ui

如果只是要解决docs页面空白的问题&#xff0c;可先看我的这篇博客&#xff1a;FastAPI访问/docs接口文档显示空白、js/css无法加载_fastapi docs打不开-CSDN博客 以下内容适用于需要以自用域名访问swagger-ui的情况&#xff1a; 1. 准备好swagger-ui的链接&#xff0c;如&am…

读后感:《SQL数据分析实战》运营SQL实用手册

学习SQL&#xff0c;先有用起来&#xff0c;有了使用价值&#xff0c;之后才是去了解它的原理&#xff0c;让使用更加顺畅。 在大部分业务场景中&#xff0c;通过SQL可以快速的实现数据处理与统计。《SQL数据分析实战》区别于其他工具书&#xff0c;它并没有介绍SQL是什么&…

为何限定项目的 Node.js 版本

首先区分三个概念nvm&#xff0c;npm&#xff0c;nodejs。 Node.js: Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。它允许开发者使用 JavaScript 在服务器端编写应用程序,而不仅限于在浏览器中运行 JavaScript。Node.js 提供了一系列内置的模块和 API,使得开发…

Redis之常用实战场景

1.Redis数据丢失场景 1.1 持久化丢失 采用RDB或者不持久化&#xff0c;就会有数据丢失&#xff0c;因为是手动或者配置以快照的形式来进行备份。 解决: 启用AOF&#xff0c;以命令追加的形式进行备份&#xff0c;但是默认也会有1s丢失&#xff0c;这是在性能与数据安全性中寻…

2021 hnust 湖科大 数据结构课设报告+代码

2021 hnust 湖科大 数据结构 课设报告代码 描述 hnust大一下学期数据结构课设的报告和源代码&#xff08;放在了附录里面&#xff09; 目录 项目名称完成日期页码复杂度分析(Ⅰ)2021-06-211—2复杂度分析(Ⅱ)2021-06-213—4Josephus问题(Ⅰ)2021-06-215—6Josephus问题(Ⅱ…

做抖音小店掌握这些方法,轻松和达人合作!

大家好&#xff0c;我是喷火龙。 抖店的新手商家想要出单&#xff0c;最快速稳定的出单方法无疑是与达人合作&#xff0c;通过达人的自身流量来帮助我们进行产品的转化。 接下来我就教大家怎么和达人合作。 首先我们可以通过蝉妈妈、达人广场、抖音私信等方式来找到我们想要…

Linux之线程互斥

线程简单封装 试着用线程控制力介绍的一些系统调用, 将线程的创建、执行和等待等都封装起来. 我们在程序中指定一个函数Print, 让多个线程不断地执行该函数. myThread.hpp #pragma once #include <functional> #include <pthread.h> #include <string>//假…

centos7下卸载MySQL,Oracle数据库

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 操作系统版本为CentOS 7 使⽤ MySQ…

HiveMetastore

HiveMetastore 背后的存储 select * from DBS; select * from TBLS; select * from TABLE_PARAMS; 查找出没有 totalSize stats 的table SELECT DBS.NAME,t.TBL_NAME from DBS inner join (select DB_ID,TBL_NAME from TBLS where TBLS.TBL_ID not in(select TBL_ID from T…