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.pswp.cn/bicheng/22350.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/22350.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/22350.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

我有點想用JDK17了

大家好呀&#xff0c;我是summo&#xff0c;JDK版本升級的非常快&#xff0c;現在已經到JDK20了。JDK版本雖多&#xff0c;但應用最廣泛的還得是JDK8&#xff0c;正所謂“他發任他發&#xff0c;我用Java8”。 其實我也不太想升級JDK版本&#xff0c;感覺投入高&#xff0c;收…

華為、華三交換機、路由器啟用基于端口的環回檢測功能配置

目的 在進行某些特殊功能測試時&#xff0c;例如初步定位以太網故障時&#xff0c;需要開啟以太網接口環回檢測功能&#xff0c;測試接口功能是否異常。 當以太網接口無故障時&#xff0c;開啟環回檢測功能后接口物理狀態和協議狀態將始終處于Up狀態&#xff1b;如果以太網接口…

Linux環境搭建NextCloud

NextCloud是什么 Nextcloud是一款開源免費的私有云存儲網盤項目&#xff0c;可以讓你快速便捷的搭建一套屬于自己或者團隊的云同步網盤&#xff0c;從而實現跨平臺跨設備文件同步&#xff0c;共享&#xff0c;版本控制&#xff0c;團隊協做等功能。它的客戶端覆蓋windows&#…

使用AdaBoost分類方法實現對Wine數據集分類

目錄 1. 作者介紹2. 什么是AdaBoost&#xff1f;2.1 什么是弱分類器2.2 什么是強分類器2.3 如何自適應增強2.4 如何組合弱分類器成為一個強分類器&#xff1f; 3. 什么是Wine數據集3.1 Wine 數據集3.2 Wine 數據集結構 4. 使用AdaBoost分類方法實現對Wine數據集分類5. 完整代碼…

PLC的編程方式有什么編程:深度探索與實用指南

PLC的編程方式有什么編程&#xff1a;深度探索與實用指南 在現代工業自動化領域&#xff0c;可編程邏輯控制器&#xff08;PLC&#xff09;扮演著至關重要的角色。PLC的編程方式多種多樣&#xff0c;每種方式都有其獨特的優點和適用場景。本文將從四個方面、五個方面、六個方面…

k8s 配置資源管理

一、Secret的資源配置 1.1 Secret配置的相關說明 Secret 是用來保存密碼、token、密鑰等敏感數據的 k8s 資源&#xff0c;這類數據雖然也可以存放在 Pod 或者鏡像中&#xff0c;但是放在 Secret 中是為了更方便的控制如何使用數據&#xff0c;并減少暴露的風險。 有四種類型&a…

日志優化開發效率

日志怎么打&#xff1f; 1.在關鍵節點打日志 (1).請求入口 (2).結果響應 2.可能發生錯誤的節點打日志 3.日志不是越多越好&#xff0c;打日志也會消耗性能 RequestMapping("/add")public Boolean publishBlog(String title, String content, HttpServletRequest req…

react快速開始(四)-之Vite 還是 (Create React App) CRA? 用Vite創建項目

文章目錄 react快速開始(四)-之Vite 還是 (Create React App) CRA? 用Vite創建項目背景Vite 和 (Create React App) CRAVite&#xff1f;Vite 是否支持 TypeScript&#xff1f; 用Vite創建react項目參考 react快速開始(四)-之Vite 還是 (Create React App) CRA? 用Vite創建項…

Java面向對象筆記

多態 一種類型的變量可以引用多種實際類型的對象 如 package ooplearn;public class Test {public static void main(String[] args) {Animal[] animals new Animal[2];animals[0] new Dog();animals[1] new Cat();for (Animal animal : animals){animal.eat();}} }class …

Java面試題-集合

Java面試題-集合 1、什么是集合&#xff1f;2、集合和數組的區別是什么&#xff1f;3、集合有哪些特點&#xff1f;4、常用的集合類有哪些&#xff1f;5、List&#xff0c; Set&#xff0c; Map三者的區別&#xff1f;6、說說集合框架底層數據結構&#xff1f;7、線程安全的集合…

MeshFusion Pro : Ultimate Optimization Tool

MeshFusion Pro是Unity的強大優化工具,它使用一種高效的方法來組合對象,以減少繪制調用并提高FPS。 MeshFusion Pro可用于組合靜態對象以及LODGroups。您還可以創建動態組合對象,其中每個單獨的網格都可以在運行時移動,新的組合網格將自動更新。在保持單個網格自由度的同時…

【數據結構與算法 | 二叉樹篇】力扣101, 104, 111

1. 力扣101 : 對稱二叉樹 (1). 題 給你一個二叉樹的根節點 root &#xff0c; 檢查它是否軸對稱。 示例 1&#xff1a; 輸入&#xff1a;root [1,2,2,3,4,4,3] 輸出&#xff1a;true示例 2&#xff1a; 輸入&#xff1a;root [1,2,2,null,3,null,3] 輸出&#xff1a;false…

Java1.8語言+ springboot +mysql + Thymeleaf 全套家政上門服務平臺app小程序源碼

Java1.8語言 springboot mysql Thymeleaf 全套家政上門服務平臺app小程序源碼 家政系統是一套可以提供上門家政、上門維修、上門洗車、上門搬家等服務為一體的家政平臺解決方案。它能夠與微信對接、擁有用戶端小程序&#xff0c;并提供師傅端app&#xff0c;可以幫助創業者在…

樹的算法基礎知識

什么是樹&#xff1a; 樹是n&#xff08;n>0&#xff09;個結點的有限集。n0時稱為空樹。在任意一棵非空樹中&#xff1a; 有且僅有一個特定的稱為根的結點當n>1時&#xff0c;其余結點可分為m&#xff08;m>0&#xff09;個互不相交的有限集T1、T2、......、Tm&…

ElasticSearch學習筆記之三:Logstash數據分析

第3章 Logstash數據分析 Logstash使用管道方式進行日志的搜集處理和輸出。有點類似*NIX系統的管道命令 xxx | ccc | ddd&#xff0c;xxx執行完了會執行ccc&#xff0c;然后執行ddd。 在logstash中&#xff0c;包括了三個階段: 輸入input --> 處理filter&#xff08;不是必須…

異或炸彈(easy)(牛客小白月賽95)

題目鏈接: D-異或炸彈&#xff08;easy&#xff09;_牛客小白月賽95 (nowcoder.com) 題目&#xff1a; 題目分析&#xff1a; 一看 還以為是二維差分的題呢 到后來才發現是一維差分問題 這里的距離是 曼哈頓距離 dis abs(x - xi) abs(y - yi) 暴力的做法 就是枚舉 n * n 個…

word-海報制作

1、確定海報的尺寸大小 2、創建主題顏色 設計-顏色-自定義顏色-柑橘rgb值改變著色1-著色6的顏色 3、將文字添加至文本框&#xff0c;更改字體顏色、大小和格式 4、添加背景水印&#xff1a;插入-形狀-文本框 5、組合全部元素 圖片素材網址&#xff1a;

Power BI前端設計:深度探索與實戰技巧

Power BI前端設計&#xff1a;深度探索與實戰技巧 Power BI作為一款強大的商業智能工具&#xff0c;其前端設計對于用戶體驗和數據可視化效果至關重要。本文將深入探討Power BI前端設計的四個關鍵方面、五個實用技巧、六個設計要素以及七個注意事項&#xff0c;助您提升Power …

學習分享-如何避免 Apache ShardingSphere 中的笛卡爾積現象

前言 Apache ShardingSphere 是一個開源的分布式數據庫中間件&#xff0c;旨在通過數據分片、分布式事務、分布式治理等技術&#xff0c;提升數據庫系統的性能和可擴展性。然而&#xff0c;最近在使用 ShardingSphere 進行分庫分表并多表查詢時&#xff0c;出現了笛卡爾積現象…

Spark Streaming 概述及入門案例

一、介紹 1. 不同的數據處理 從數據處理的方式&#xff1a; 流式數據處理(Streaming)批量數據處理(Batch) 從數據處理的延遲&#xff1a; 實時數據處理(毫秒級別)離線數據處理(小時或天級別) 2. 簡介 SparkStreaming 是一個準實時(秒或分鐘級別)、微批量的數據處理框架Spa…