【Linux进阶之路】网络 —— “?“ (下)

文章目录

  • 前言
  • 一、概念铺垫
    • 1.TCP
    • 2.全双工
  • 二、网络版本计算器
    • 1. 原理简要
    • 2. 实现框架&&代码
      • 2.1 封装socket
      • 2.2 客户端与服务端
      • 2.3 封装与解包
      • 2.4 请求与响应
      • 2.5 对数据进行处理
      • 2.6 主程序逻辑
    • 3.Json的简单使用
  • 总结
  • 尾序

前言

 在上文我们学习使用套接字的相关接口进行了编程,因此对网络编程有了一定的认识,可是我们之前只是以字符串的形式简单的收发信息,如果我们要发送和接受的信息更加复杂,比如:客户端发送一个结构体,服务端要如何接收这个结构体呢? 如果说还要对结构体的数据进行处理并返回呢?下面就让我们带着这些疑问开始今天的学习吧!

  • 说明:
  1. 每台计算机的结构体的对齐方式可能会有所不同,因此不能直接发送结构体。
  2. 因此要将结构体里的数据要以特定的形式,即协议的方式发送和接收。
  3. 对数据处理后,还要以协议的方式发送给客户端,从而客户端收到并进行对应的处理。

一、概念铺垫

1.TCP

  • 众所周知,TCP是可靠的传输控制协议,一般是通过三次握手和四次挥手来保证数据的传输是可靠的。
  • 说明:下面只是简单的理解,后面博主详细讲解的。
  • 三次握手 :
    在这里插入图片描述
  • 三次交互,建立连接。
  • 四次挥手:

在这里插入图片描述

  • 断开连接,就是要断的干净,避免之后一方进行死缠烂打。

2.全双工

  • 所谓的全双工,就是服务端和客户端都是可以收消息和发消息的,例如UDP和TCP协议都是全双工的。
  1. UDP

在这里插入图片描述

  1. TCP
    在这里插入图片描述
  • 理解传输控制协议:
    1. 对于UDP来说,在传输层对于发消息不做控制,但是对于收消息如何处理,则全权交由UDP决定。
    2. 对于TCP来说,用户只负责将消息发送到发送和接收缓存区,但对于消息如何处理,则全权由TCP决定。
    • 说明:处理一般涉及什么时候传,传多少,传错了怎么办等等。
  • 从UDP与TCP相比较,TCP多了一个发送缓冲区,这在一定程度上可以体现TCP的可靠性。

二、网络版本计算器

1. 原理简要

  1. 因为我们做的是网络版本的计算器,数据格式设定为[ 数据(空格)方法(空格)数据(换行符)]即可,而且在网络中我们一般是以字符串的形式进行发送的,因此我们还要将整形数据转换为字符串,便于之后的解析。
  2. 数据的封装,为了能将一个完整的数据解析出来,因此我们应该在数据的前面封装数据的长度,当截取数据时,我们按照长度截取即可检查是否可获取到一个完整的数据,并且长度应与数据分开,便于获取,这里我们用换行符作为分割符即可。这里实现了数据的封装也就间接的实现了对数据解包。
  • 举一个体现自定义协议的例子,比如 [1 + 1]封装为 [5\n][1 + 1\n],数据按上面的封装,而服务器读取时,假如只读取到了[5\n 1 +],通过读取5这个字符串,转换为int,可以验证读取的报文是否是完整的报文,那么数据不是无法进行解包的,会直接返回。
  1. 因为客户端和服务端都要遵循这种规则,即自定义协议是一种约定,因此双方都要遵守的,因此不存在数据被污染的情况,即网络中传输的数据都是符合要求的。
  2. 因此客户端传输的数据可以被服务端正确的提取,提取之后,我们要进行解析和处理数据,并将处理后的数据以:【结果 返回码】,返回码用于检查数据是否计算可靠,比如1 除 0 无法进行计算,设返回码为1表示除0错误。并以上述同样的方式进行封装,将封装之后的结果,返回给用户进行解析,并处理。

2. 实现框架&&代码

  1. 实现服务器和封装socket套接字。
  2. 对请求和响应分别进行序列化和反序列化。
  3. 对序列化的数据进行封装与解包。
  4. 服务器对解析的数据进行处理和返回。
  • 代码框架:
    在这里插入图片描述

2.1 封装socket

 在之前我们实现代码时,主要目的是为了熟悉系统调用接口,熟练使用之后这里我们可以将Socket进行封装(包含客户端与服务端的常用的接口),方便我们之后进行调用:

#pragma once#include<iostream>
#include<string>#include<cstring>
#include<strings.h>
#include<unistd.h>//网络相关的头文件。
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//小组件
#include"Log.hpp"using std::string;enum FAIL
{CREAT = 1,SIP_TO_NIP,BIND,LISTEN,ACCEPT,CONNECT,
};
uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
class Sock
{
public:Sock(uint16_t port = defaultport,string ip = defaultip):_port(port),_ip(ip){}~Sock(){if(_sockfd > 0){close(_sockfd);}}//创建套接字void Socket(){_sockfd = socket(AF_INET,SOCK_STREAM,0);if(_sockfd < 0){lg(CRIT,"socket create fail,reason is\%s,errno is %d",strerror(errno),errno);exit(CREAT);}lg(INFORE,"sockfd is %d,create success!",_sockfd);}//获取套接字int GetSocket(){return _sockfd; }//绑定void Bind(){sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(_port);if(inet_pton(AF_INET,_ip.c_str(),&server.sin_addr) != 1){lg(CRIT,"string_ip to inet_ip fail,reason is %s\,errno is %d",strerror(errno),errno);exit(SIP_TO_NIP);}if(bind(_sockfd,(sockaddr*)&server,sizeof(server)) == -1){lg(CRIT,"bind fail,reason is %s,errno \is %d",strerror(errno),errno);exit(BIND);}lg(INFORE,"bind success!");}//监听void Listen(){if(listen(_sockfd,_backlog) == -1){lg(CRIT,"bind fail,reason is %s,errno is\%d",strerror(errno),errno);exit(LISTEN);}lg(INFORE,"lisen success!");}//接收连接int Accept(sockaddr_in* client,socklen_t* len){int fd = accept(_sockfd,(sockaddr*)client,len);if(fd < 0){lg(CRIT,"accept fail,reason is %s,\errno is %d",strerror(errno),errno);exit(ACCEPT);}uint16_t port = ntohs(client->sin_port);char ip[64] = {0};inet_ntop(AF_INET,&(client->sin_addr),ip,sizeof(ip) - 1);lg(INFORE,"accept success,get a new link,ip is\%s, port is %d",ip,port);return fd;}//连接void Connect(sockaddr_in* server){memset(server,0,sizeof(sockaddr_in));server->sin_family = AF_INET;server->sin_port = htons(_port);if(inet_pton(AF_INET,_ip.c_str(),\&(server->sin_addr)) == -1){lg(WARNNING,"inet_pton fail,reason is %s\,errno is %d",strerror(errno),errno);return;}int res = connect(_sockfd,\(sockaddr*)server,sizeof(sockaddr_in));if(res == -1){lg(CRIT,"connect fail,reason is %s,\errno is %d",strerror(errno),errno);exit(CONNECT);return;}lg(INFORE,"connect success!");}//从指定的套接字文件描述符里面读取数据。string Read(int fd){char buffer[128] = {0};ssize_t n = read(fd,buffer,sizeof(buffer) - 1);if(n < 0){lg(CRIT,"read fail,reason is %s,\errno is %d",strerror(errno),errno);sleep(1);return "";}else if(n == 0){lg(INFORE,"read nothing!");sleep(1);return "";}buffer[n] = '\0';return buffer;}//向指定的套接字文件描述符里面写数据。int Write(int fd,const string& str){ssize_t n = write(fd,str.c_str(),str.size());if(n < 0){lg(CRIT,"write fail,reason is %s,errno \is %d",strerror(errno),errno);sleep(1);return n;}else if(n == 0){lg(INFORE,"write nothing!");sleep(1);return n;}return n;  }void Close(int fd){close(fd);}
private:int _sockfd;uint16_t _port;string _ip;int _backlog = 5;//?
};
  • 以后我们直接用这个小组件即可,不用再手搓系统调用的接口了。

2.2 客户端与服务端

 这里我们使用上面封装的socket接口,实现的服务端与客户端。

  • 服务端
#pragma once 
#include<iostream>
#include<pthread.h>
#include<functional>
#include"../Tools/Socket.hpp"
#include"../Tools/Log.hpp"
using cal_t = function<string(string&)>;
class TcpServer;struct ThreadData
{ThreadData(int fd,TcpServer* tp):_fd(fd),_tp(tp){}int _fd;TcpServer* _tp;
};
class TcpServer
{
public:TcpServer(uint16_t port = 8080,cal_t cal = nullptr):_socket(port),_cal(cal){}~TcpServer(){}void Init(){_socket.Socket();_socket.Bind();_socket.Listen();}static void* Rouetine(void* args){//分离线程pthread_detach(pthread_self());auto thread_ptr = static_cast<ThreadData*>(args);TcpServer* tp = thread_ptr->_tp;int fd = thread_ptr->_fd;tp->Server(fd);return nullptr;}void Run(){for(;;){sockaddr_in client;socklen_t len = sizeof(client);int fd = _socket.Accept(&client,&len);pthread_t tid;pthread_create(&tid,nullptr,Rouetine,\new ThreadData(fd,this));}}void Server(int fd){string mes;for(;;){sleep(10);//收消息string str = _socket.Read(fd);//啥也没读到if(str == "") break;mes += str;//处理消息string ans;string echo_mes;//一次处理一批while((echo_mes = _cal(mes)) != ""){ans += echo_mes;}//没有读取到整段的报文或者报文为空。int res = _socket.Write(fd,ans);if(res <= 0) break;}_socket.Close(fd);}
private:Sock _socket;cal_t _cal; //这里的cal函数是对接收的消息的处理方法。
};
  • 根据上面的信息,我们可以大致了解服务器的基本框架:
  1. 创建套接字,绑定套接字,监听套接字。
  2. 接收外面的请求,建立连接,接收信息。
  3. 调用处理信息的接口,返回处理之后的信息。

  • 因此: 我们可以让服务器与处理信息的逻辑进行解耦,并且使用封装之后的套接字是很方便的。
  • 客户端:
#pragma once
#include<iostream>
#include<string>#include"../Tools/Log.hpp"
#include"../Tools/protocol.hpp"
#include"../Tools/Socket.hpp"using std::string;
string default_ip = "59.110.171.164";
uint16_t default_port = 8080;
class TcpClient
{
public:TcpClient(string ip = default_ip,uint16_t port = default_port):_sock(port,ip){}void Init(){}void Run(){string res;for(;;){_sock.Socket();sockaddr_in server;_sock.Connect(&server); int fd = _sock.GetSocket();while(true){cout << "Please Enter@";int x,y;char oper;cin >> x >> oper >> y;Request req(x,y,oper);string str = req.Serialize();//为了更好的体现自定义协议,这里我们多次进行写入。_sock.Write(fd,str);_sock.Write(fd,str);_sock.Write(fd,str);_sock.Write(fd,str);_sock.Write(fd,str);sleep(10);//一次读一批res += _sock.Read(fd);Response resq;//一次处理一批:while(resq.Deserialize(res));}_sock.Close(fd);}}
private:Sock _sock;
};
  • 说明:这里我们让客户端一次发一批消息,处理一批消息,服务端一次处理一批消息,发一批消息,这样更加能够体现自定义协议的功能。

2.3 封装与解包

//.....char space = ' ';
char newline = '\n';
//解包
string Decode(string& str)
{int pos = str.find(newline);if(pos == string::npos) return "";int len = stoi(str.substr(0,pos));int totalsize = pos + len + 2;//如果总的报文的长度大于读取的字符串的长度,说明没有一个完整的报文。if(totalsize > str.size()){return "";}//将有效载荷截取出来string actual_load = str.substr(pos + 1,len);//将完整的报文丢弃,便于下一次进行读取。str.erase(0,totalsize);return actual_load; 
}
//编码
string InCode(const string& str)
{//一个完整的报文:有效载荷的长度 + 换行符 + 有效载荷 + 换行。string text = to_string(str.size()) + newline + str + newline;return text;
}
  1. 封装数据,我们将在报头处封装有效载荷的长度,并以换行符作为分割符。
  2. 解析数据,首先要找到有效载荷的长度,并检验是否存在一个完整的报文。

2.4 请求与响应

struct Request
{Request(int x, int y, char oper):_x(x), _y(y), _oper(oper){}Request(){}bool Deserialize(string& str){cout << "+++++++++++++++++++++++++++++" << endl;//首先把字符串的报头和有效载荷进行分离string content = Decode(str);if(content == "") return false;//解析字符串:字符 + 空格 + 字符int left = content.find(space);int right = content.rfind(space);if (left + 1 != right - 1){//说明是无效的字符return false;}_x = stoi(content.substr(0, left));_y = stoi(content.substr(right + 1));_oper = content[left + 1];cout << "解析的字符串:"<< _x << _oper << _y << endl; cout << "待读取的字符串:" << endl << str << endl;cout << "-------------------------------" << endl;return true;}string Serialize(){string package;//首先对结构体进行编码//编码格式:字符 + 空格 + 操作符 + 空格 + 字符package = to_string(_x) + space + _oper + space\+ to_string(_y);	//对报文再进行封装package = InCode(package);return package;}int _x = 0;int _y = 0;char _oper = '0';//给出一个缺省值,避免编译器告警。
};struct Response
{Response(int res, int code):_res(res), _code(code){}Response(){}bool Deserialize(string& str){string content = Decode(str);if (content == "") return false;int pos = content.find(space);_res = stoi(content.substr(0,pos));_code = stoi(content.substr(pos + 1));//for debug:cout << "+++++++++++++++++++++++++++++++" << endl;cout <<"转换结果:"<< _res << " " << _code << endl;cout << "待读取的字符串" << endl << str << endl;cout << "-------------------------------" << endl;return true;}string Serialize(){string package = to_string(_res) + space \+ to_string(_code);package = InCode(package);return package;}int _res = 0;int _code = 0;//同理。
};
  1. Request,是客户端对服务器发送的请求,要客户端进行序列化,服务端进行反序列化,并进行解析。
  2. Response,是服务端对客户端发送的响应,要服务端进行序列化,客户端进行反序列化,并进行解析。

2.5 对数据进行处理

#include<iostream>
#include"../Tools/Log.hpp"
#include"../Tools/protocol.hpp"enum CAL 
{DIV_ZERO = 1,MOD_ZERO,
};
struct CalHelper
{string Cal(string& str){Request req;if(req.Deserialize(str) == false) return "";int x = req._x;int y = req._y;char op = req._oper;int res = 0, code = 0;switch(op){case '+':res = x + y;break;case '-':res = x - y;break;case '*': res = x * y;break;case '/':if(!y){code = DIV_ZERO;break;}res = x / y;break;case '%':if(!y){code = MOD_ZERO;break;}res = x % y;break;default:break;}return Response(res,code).Serialize();}
};

  • 这是服务器对客户端请求的处理,包含请求的反序列化和对数据的处理,以及结果的序列化。

2.6 主程序逻辑

  • client.cc
#include<iostream>
#include<memory>
#include"clientcal.hpp"
using std::unique_ptr;
void Usage(char* pragma_name)
{cout << endl << "Usage: " << pragma_name << \"+ ip + port[8000-8888]" << endl << endl; 
}
int main(int argc,char* argv[])
{if(argc != 3){Usage(argv[0]);return 1;}string ip = argv[1];uint16_t port = stoi(argv[2]);unique_ptr<TcpClient> cp(new TcpClient(ip,port));cp->Init();cp->Run();return 0;
}
  • server.cc
#include<iostream>
#include<memory>
#include<functional>
#include"server.hpp"
#include"servercal.hpp"
using std::unique_ptr;void Usage(char* pragma_name)
{cout << endl << "Usage: " << pragma_name \<< " + port[8000-8888]" << endl << endl; 
}
int main(int argc,char* argv[])
{if(argc != 2){Usage(argv[0]);return 1;}uint16_t port = stoi(argv[1]);CalHelper cal;unique_ptr<TcpServer> tp(new TcpServer(port,\bind(&CalHelper::Cal,&cal,placeholders::_1)));//bind是C++的一个接口,用于封装函数,便于使用。//因为cal是库里面的,因此要指定作用域,并传this指针,//绑定参数,进而封装出指定类型的函数。tp->Init();tp->Run();return 0;
}
  • bind的使用:跳转详见目录
  • 运行结果:
    在这里插入图片描述
  • 这里我们传数据,接收数据,处理数据都是一批一批的进行的,因此可以看见待处理的字符串。

3.Json的简单使用

  • 在上面实现的过程中,唯一比较难设计的就是序列化与反序列化的过程,上面我们为了进一步的理解,所以自己设计,但是市面上有一些简单好用的序列化与反序列化工具,下面我们介绍一种。

在网络中,序列化与反序列化有现成的工具,比如json 和 protobuf这两个工具,下面我们简单介绍Json的使用。

  1. 安装Json库
sudo yum install -y jsoncpp-devel
  • 说明: 普通用户需要输入root密码并且要添加到系统的信任白名单中,所以这里建议直接su命令切到root用户直接安装。
  1. 简单使用
  • test.cc
#include<iostream>
#include<string>
#include<jsoncpp/json/json.h>
using namespace std;
int main()
{Json::Value root;Json::StyledWriter writer;//Json::FastWriter writer;//StyleWriter打印起来比较有风格。//FastWrier打印比较紧凑,比较省空间。root["x"] = 1;root["y"] = 2;root["oper"] = '+';string res = writer.write(root);//序列化之后的结果:cout << "序列化之后的结果:" << endl;cout << res << endl;Json::Value des;Json::Reader r;r.parse(res,des);int x = des["x"].asInt();int y = des["y"].asInt();char oper = des["oper"].asInt();//反序列化的结果:cout << "反序列化的结果为:" << endl;cout << x << " " << oper << " " << y << endl;return 0;
}
  1. 编译运行查看结果
g++  test.cc -std=c++11 -ljsoncpp

在这里插入图片描述

总结

  1. 铺垫TCP三次握手,四次挥手的概念,以及理解全双工。
  2. 实现了自定义协议(封装报头) + 序列化与反序列化的 网络版本的计算器。
  3. 介绍了Json工具的基本使用。

 了解自定义协议之后,我们将在下篇认识现成的应用层协议之Http。

尾序

  • 我是舜华,期待与你的下一次相遇!

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

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

相关文章

《计算机网络》考研:2024/3/7 2.1.4 奈氏准则和香农定理

2024/3/7 (作者转行去干LLMs了&#xff0c;但是又想搞定考研&#xff0c;忙不过来了就全截图了呜呜呜。。。 生活真不容易。) 2.1.4 奈氏准则与香农定理

个人博客系列-后端项目-用户验证(5)

介绍 创建系统管理app&#xff0c;用于管理系统的用户&#xff0c;角色&#xff0c;权限&#xff0c;登录等功能&#xff0c;项目中将使用django-rest_framework进行用户认证和权限解析。这里将完成用户认证 用户验证 rest_framework.authentication模块中的认证类&#xff…

升级ChatGPT4.0失败的解决方案

ChatGPT 4.0科普 ChatGPT 4.0是一款具有多项出众功能的新一代AI语言模型。以下是关于ChatGPT 4.0的一些关键特点和科普内容&#xff1a; 多模态&#xff1a;ChatGPT 4.0具备处理不同类型输入和输出的能力。这意味着它不仅可以接收文字信息&#xff0c;还能处理图片、视频等多…

基于springboot+vue的旅游管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

简单接入电商API接口|轻松实现实时采集淘宝、抖音、快手、1688商品,挖掘潜力款

今天给大家带来一款非常实用的电商API接口&#xff0c;这款数据采集接口支持淘宝采集、抖音采集、快手采集、1688采集以及潜力款分析&#xff0c;功能强大&#xff0c;助您在电商领域更上一层楼。 首先&#xff0c;我们来了解一下淘宝采集功能。作为国内最大的电商平台&#xf…

UI学习 一

教程&#xff1a;Accessibility – Material Design 3 需要科学上网&#xff0c;否则图片显示不出来。设计教程没有图片说明&#xff0c;不容易理解。 优化UI方向 清晰可见的元素足够的对比度和尺寸重要性的明确等级一眼就能辨别的关键信息 传达某一事物的相对重要性 将重…

AI会砸了我们的饭碗?

Sora&#xff0c;由OpenAI推出&#xff0c;是一款创新的文本到视频生成模型。它能够将文本描述转化为引人入胜的高清视频片段。采用了扩散模型和变换器架构&#xff0c;Sora实现了高效的训练。其方法包括统一表示法、基于补丁的表示法、视频压缩网络和扩散变换器。 Sora具备多种…

就业班 2401--3.11 Linux Day15--ftp数据传输测试server和client+谷歌验证码登录远程连接

文件服务器 路漫漫其修远兮&#xff0c;吾将上下而求索.构建NFS远程共享存储 一、NFS介绍 文件系统级别共享&#xff08;是NAS存储&#xff09; --------- 已经做好了格式化&#xff0c;可以直接用。 速度慢比如&#xff1a;nfs&#xff0c;sambaNFS NFS&#xff1a;Networ…

硬件工程师面试题梳理-百度硬件面试题

硬件工程师基本职责 在公司里面&#xff0c;硬件工程师的主要职责包括设计、开发和测试硬件系统&#xff0c;以满足产品需求和性能要求。他们负责确保硬件系统的可靠性、稳定性和可维护性&#xff0c;并与软件工程师和其他团队成员合作&#xff0c;以确保硬件和软件的协同工作…

网络攻防中nginx安全配置,让木马上传后不能执行、让木马执行后看不到非网站目录文件、命令执行后权限不能过高

网络攻防中nginx安全配置,让木马上传后不能执行、让木马执行后看不到非网站目录文件、命令执行后权限不能过高。 0x01 Nginx介绍 nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把结果返回给客户端。nginx一般是把请求发…

【SpringCloud微服务实战03】Nacos 注册中心

一、Nacos安装 官方文档安装Nacos教程:Nacos 快速开始 这里安装的是1.4.7版本,安装之后访问http://127.0.0.1:8848/nacos 管理界面如下:(用户名:nacos,密码:nacos) 二、Nacos服务注册和发现 1、在父工程中配置文件pom.xml 中添加spring-cloud-alilbaba的管理依赖:…

web基础05-jQuery

目录 一、jQuery 1.概述 2.原生js与jQuery对比 3.特点 4.使用 &#xff08;1&#xff09;入口函数 &#xff08;2&#xff09;语法 &#xff08;3&#xff09;jQuery选择器 5.方法 &#xff08;1&#xff09;获取属性值&#xff1a; &#xff08;2&#xff09;删除属…

校园外卖创业中的信息差,了解这些创业不迷路

外卖已经成为大学生日常生活的重要组成部分&#xff0c;但大部分高校对外卖都有着严格的管理&#xff0c;外卖取餐不便、配送时间过长、拿错餐等问题频出&#xff0c;基于此校园外卖创业成了高校市场的热门项目。 本文就校园外卖创业中存在的信息差展开分析&#xff0c;帮你理…

【MySQL】-知识点整理

1、存储引擎 -- 查询数据库支持的存储引擎 show engines; -- 查询当前数据库使用的存储引擎 show variables like %storage_engines%; 主要的存储引擎说明&#xff1a; 1&#xff09;MyISAM&#xff1a;无外键、表锁、所有索引都是非聚簇索引、无事务、记录表总条数、删除表…

Linux最小系统安装无法查看IP地址

1&#xff0c;出现原因 服务器重启完成之后&#xff0c;我们可以通过linux的指令 ip addr 来查询Linux系统的IP地址&#xff0c;具体信息如下: 从图中我们可以看到&#xff0c;并没有获取到linux系统的IP地址&#xff0c;这是为什么呢&#xff1f;这是由于启动服务器时未加载网…

【MySQL系列 05】Schema 与数据类型优化

良好的数据库 schema 设计和合理的数据类型选择是 SQL 获得高性能的基石。 一、选择优化的数据类型 MySQL 支持的数据类型非常多&#xff0c;选择正确的数据类型对于获得高性能至关重要。不管存储哪种类型的数据&#xff0c;下面几个简单的原则都有助于做出更好的选择。 1. …

数据通信练习题

1.0osi七层模型 应用层 data 表示层 会话层 传输层 数据段 防火墙&#xff0c;端口&#xff08;TCP UDP&#xff09; 网络层 数据包 路由器 数据链路层 数据帧 交换机 物理层 比特流 网卡 2.IP地址分类 私有地址 A类 0--127 10.0.0.0…

提升日志管理效率:掌握CKA认证中的边车容器技巧

往期精彩文章 : 提升CKA考试胜算&#xff1a;一文带你全面了解RBAC权限控制&#xff01;揭秘高效运维&#xff1a;如何用kubectl top命令实时监控K8s资源使用情况&#xff1f;CKA认证必备&#xff1a;掌握k8s网络策略的关键要点提高CKA认证成功率&#xff0c;CKA真题中的节点维…

C++字符串操作【超详细】

零.前言 本文将重点围绕C的字符串来展开描述。 其中&#xff0c;对于C/C中字符串的一些区别也做出了回答&#xff0c;并对于C的&#xff08;string库&#xff09;进行了讲解&#xff0c;最后我们给出字符串的不同表达形式。 开发环境&#xff1a; VS2022 一.字符串常量跟字…

光伏数字化管理平台:驱动绿色能源革命的智能化引擎

随着全球对可再生能源需求的不断增长&#xff0c;光伏产业已经成为推动绿色能源革命的重要力量。在这个背景下&#xff0c;光伏数字化管理平台应运而生&#xff0c;以其强大的数据处理、实时监控和智能优化功能&#xff0c;为光伏电站的运营管理和维护带来了革命性的变革。 光伏…