【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。

尾序

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构入门(3)2.链表接口实现

目录 前言 头文件 动态申请一个结点 单链表打印 单链表尾插 单链表的头插 单链表的尾删 单链表头删 单链表查找 单链表在pos位置之后插入x 单链表删除pos位置之后的值 在pos的前面插入 删除pos位置 销毁顺序表 前言 本文将介绍链表常见的功能的实现 头文件 #…

【深度学习】Lora

论文标题&#xff1a;LoRA: Low-Rank Adaptation of Large Language Models 论文链接&#xff1a;https://arxiv.org/abs/2106.09685 论文来源&#xff1a;NVIDIA 1.提出背景 自然语言处理的一个重要范式为使用领域数据对模型进行大规模的预训练 &#xff0c;并适应特定的任务或…

LLM PreTraining from scratch -- 大模型从头开始预训练指北

最近做了一些大模型训练相关的训练相关的技术储备,在内部平台上完成了多机多卡的llm 预训练的尝试,具体的过程大致如下: 数据准备: 大语言模型的训练依赖于与之匹配的语料数据,在开源社区有一群人在自发的整理高质量的语料数据,可以通过 以下的一些链接获取 liwu/MNBVC…

全面认识计算机操作系统(二)

目录 一、操作系统的诞生 相关概念&#xff1a; 1. 手工操作阶段 2. 脱机输入 / 输出阶段 &#xff08;1&#xff09;脱机输入技术 &#xff08;2&#xff09;脱机输出技术 3. 单道批处理阶段 4. 多道批处理阶段 5. 分时技术产生 6. 实时系统产生 二、现代操作系统的…

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

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

vue3项目安装unplugin-auto-import插件实现按需导入API

1、安装unplugin-auto-import npm i -D unplugin-auto-import2.vite.config.ts文件进行配置 import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite// https://vitejs.dev/config/ export default defineCo…

低代码平台如何选型 盘点国内外主流低代码开发平台

随着数字化转型的加速&#xff0c;低代码开发平台作为一种新型软件开发方式&#xff0c;受到了广泛关注。国内低代码市场也呈现出蓬勃发展的态势&#xff0c;各种低代码平台如雨后春笋般涌现。本文将对国内低代码平台进行盘点&#xff0c;以帮助企业和开发者更好地了解市场情况…

亚马逊多账号怎么防关联?超级浏览器来帮你!

很多做亚马逊跨境电商的小伙伴都会遇到的问题就是多登店铺账号被关联&#xff0c;我们要知道&#xff0c;如果在亚马逊上运营多个店铺&#xff0c;保持账户之间的独立性是很重要的。一旦账户之间被平台识别为关联&#xff0c;不仅可能导致收入损失&#xff0c;还可能面临账号被…

鸿蒙开发(四)-低代码开发

鸿蒙开发(四)-低代码开发 本文主要介绍下鸿蒙下的低代码开发。 鸿蒙低代码是指在鸿蒙操作系统进行应用开发时&#xff0c;采用简化开发流程和减少编码量的方式来提高开发效率。 1&#xff1a;开启低代码开发 首先我们打开DevEco Studio .然后创建工程。 如图所示&#xff…

Java网络编程详解

目录 网络编程 1、概述 2、网络通信的要素 3、IP 4、端口 5、通信协议 6、TCP 文件上传 Tomcat 7、UDP 单方发送单方接受 双方发送接收 8、URL URL测试 URL下载网络资源 网络编程 1、概述 信件&#xff1a; 计算机网络&#xff1a; 计算机网络是指将地理位置不…

微服务配置中心

什么是配置中心 配置中心是一种用于管理应用程序或系统配置信息的中央服务。它允许开发人员在多个环境&#xff08;如开发、测试、生产&#xff09;之间共享配置&#xff0c;并且可以在不停止应用程序的情况下动态更新配置。 配置中心是统一管理各种应用配置的工具。它能够集中…

导出谷歌浏览器收藏的网页,并查看网页保存的登录密码

导出谷歌浏览器&#xff08;Chrome&#xff09;收藏的网页&#xff08;书签&#xff09;&#xff1a; 打开谷歌浏览器。在浏览器右上角找到并点击三个垂直排列的小点&#xff08;或称汉堡菜单&#xff09;以打开主菜单。在下拉菜单中选择“书签” > “书签管理器”。在书签…

R语言扩展包与MaxEnt模型的集成:实现高效的物种分布模拟

在生态学研究中&#xff0c;物种分布模拟是一项至关重要的任务。它有助于我们理解物种与环境之间的复杂关系&#xff0c;预测物种在气候变化或人类活动影响下的潜在分布变化。近年来&#xff0c;随着计算机技术的不断发展&#xff0c;基于机器学习的物种分布模拟方法逐渐成为研…

Day35:安全开发-JavaEE应用原生反序列化重写方法链条分析触发类类加载

目录 Java-原生使用-序列化&反序列化 Java-安全问题-重写方法&触发方法 Java-安全问题-可控其他类重写方法 思维导图 Java知识点&#xff1a; 功能&#xff1a;数据库操作&#xff0c;文件操作&#xff0c;序列化数据&#xff0c;身份验证&#xff0c;框架开发&…

Oracle 层级查询(Hierarchical Queries)

如果一张表中的数据存在分级&#xff08;即数据间存在父子关系&#xff09;&#xff0c;利用普通SQL语句显示数据间的层级关系非常复杂&#xff0c;可能需要多次连接才能完整的展示出完成的层级关系&#xff0c;更困难的是你可能不知道数据到底有多少层。而利用Oracle的层级查询…

java Day7 正则表达式|异常

文章目录 1、正则表达式1.1 常用1.2 字符串匹配&#xff0c;提取&#xff0c;分割 2、异常2.1 运行时异常2.2 编译时异常2.3 自定义异常2.3.1 自定义编译时异常2.3.2 自定义运行时异常 1、正则表达式 就是由一些特定的字符组成&#xff0c;完成一个特定的规则 可以用来校验数据…

一体机电脑辐射超标整改

电脑一体机是目前台式机和笔记本电脑之间的一个新型的市场产物&#xff0c;它将主机部分、显示器部分整合到一起的新形态电脑&#xff0c;该产品的创新在于内部元件的高度集成。随着无线技术的发展&#xff0c;电脑一体机的键盘、鼠标与显示器可实现无线链接&#xff0c;机器只…

NLP:文本相似度计算

前面我们已经实现了把长段的句子&#xff0c;利用HanLP拆分成足够精炼的分词&#xff0c;后面我们要实现“联想”功能&#xff0c;我这里初步只能想到通过文本相似度计算来实现。下面介绍一下文本相似度计算 &#xff08;当然HanLP也有文本相似度计算的方法&#xff0c;这里我…

手把手教使用静默 搭建Oracle 19c 一主一备ADG集群

一、环境搭建 主机IPora19192.168.134.239ora19std192.168.134.240 1.配置yum源 1.配置网络yum源 1.删除redhat7.0系统自带的yum软件包&#xff1b; rpm -qa|grep yum >oldyum.pkg 备份原信息rpm -qa|grep yum|xargs rpm -e --nodeps 不检查依赖&#xff0c;直接删除…

23.网络游戏逆向分析与漏洞攻防-网络通信数据包分析工具-实现配置工具数据结构

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;22.加载配置文件…