【Linux网络编程】网络编程套接字(1)

news2024/12/28 19:51:01

【Linux网络编程】网络编程套接字(1)

目录

  • 【Linux网络编程】网络编程套接字(1)
      • 源IP地址和目的IP地址
      • 端口号
        • 端口号和进程ID的关系
      • 网络通信
      • TCP协议
      • UDP协议
      • 网络字节序
      • socket编程接口
      • 简单的UDP网络程序

作者:爱写代码的刚子

时间:2024.1.29

前言:先提前写网络编程的博客,管道以及多线程的博客之后补上。

源IP地址和目的IP地址

IP数据包头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址

  1. 源IP地址(Source IP Address):
    • 源IP地址是发送数据包的设备(或主机)的IP地址。
    • 在一个网络通信过程中,源IP地址标识了消息的来源。
    • 在IPv4中,源IP地址通常以四个十进制数字的形式表示,如 “192.168.0.1”。
    • 在IPv6中,源IP地址以一种更长的形式表示,如 “2001:0db8:85a3:0000:0000:8a2e:0370:7334”。
  2. 目的IP地址(Destination IP Address):
    • 目的IP地址是接收数据包的设备(或主机)的IP地址。
    • 在一个网络通信过程中,目的IP地址标识了消息的目标。
    • 同样,目的IP地址可以以IPv4或IPv6的形式表示。

端口号

端口号(port)是传输层协议的内容.

  • 端口号是一个2字节16位的整数;

  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;

  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;

  • 一个端口号只能被一个进程占用

端口号和进程ID的关系
  • 当网络服务启动时,它会监听一个特定的端口号,等待客户端连接。
  • 当客户端尝试连接到服务时,客户端和服务之间的通信将使用该端口号进行标识。
  • 当连接建立后,服务的进程ID与该连接相关联。这使得操作系统可以跟踪网络连接与哪个进程相关。

【问题】:我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程。那么这两者之间是怎样的关系?

  • 网络模块和进程管理模块进行解耦合。进程pid在技术上是可以标定当前主机上某一个唯一的进程,但是实际上不会用进程pid做,进程pid属于进程管理范畴,而端口号属于网络范畴。如果非要用进程pid做两用(既做调度进程管理,又在网络上标定主机的一个唯一进程),无疑是将进程管理和网络强耦合起来了。它可以但不合理。

  • 在我们的系统中,并不是所有的进程都要进行网络通信的。而端口号是一种数字,标定当前主机上某一个唯一的进程,它更加的是一种证明,证明对应的进程是要进行网络通信的。没有端口号,这个进程只是本地间跑某些业务。而有端口号,一定是要对外的。

【问题】:底层如何通过port找到对应进程的?

  • 实际底层采用哈希的方式建立了端口号和进程PID或PCB之间的映射关系,当底层拿到端口号时就可以直接执行对应的哈希算法,然后就能够找到该端口号对应的进程。

【问题】:一个进程可以绑定多个端口号吗?

  • 可以的。未来一个进程在进行网络通信的时候,它可能既和客户端A通信,也和客户端A的子模块通信,所以此进程就会绑定两个端口号。只要能够通过端口号找到同一个进程即可。但是一个端口号不能被多个进程绑定。因为端口号到进程具有唯一性。

网络通信

我们在网络通信的时候,只要让两台主机能够通信就可以了吗?

  • 实际上,在进行通信的时候,不仅仅要考虑两台主机间互相交互数据。

  • 本质上讲,进行数据交互的时候,是用户和用户在进行交互。用户的身份,通常是用程序体现的。程序一定是在运行中的 ---- 进程。

    所以主机间通信的目的本质是:在各自的主机上的两个进程在互相交互数据。IP地址可以完成主机和主机的通信,而主机上各自的通信进程,才是发送和接受数据的一方。

所以:

  • IP —— 确保主机的唯一性

  • 端口号(port)—— 确保该主机上的进程的唯一性

  • IP + PORT = 标识互联网中唯一的一个进程。—— socket

  • 网络通信的本质:也是进程间通信


    socket:

  • socket在英文上有“插座”的意思,插座上有不同规格的插孔,我们将插头插入到对应的插孔当中就能够实现电流的传输。

  • 在进行网络通信时,客户端就相当于插头,服务端就相当于一个插座,但服务端上可能会有多个不同的服务进程(多个插孔),因此当我们在访问服务时需要指明服务进程的端口号(对应规格的插孔),才能享受对应服务进程的服务。

TCP协议

先简单介绍一下,之后再详细介绍

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议

先简单介绍一下,之后再详细介绍

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

总结:由于我们不能保证通信双方存储数据的方式是一样的,因此网络当中传输的数据必须考虑大小端问题。因此TCP/IP协议规定如下:网络数据流采用大端字节序,即低地址高字节。无论是大端机还是小端机,都必须按照TCP/IP协议规定的网络字节序来发送和接收数据。

  • 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
  • 小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
  • 需要注意的是,所有的大小端的转化工作是由操作系统来完成的,因为该操作属于通信细节,不过也有部分的信息需要我们自行进行处理,比如端口号和IP地址。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络 字节序和主机字节序的转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。

  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

socket编程接口

几个重要的函数接口

  • socket套接字

在这里插入图片描述

  • htons通常用于将主机字节顺序(host byte order)的16位整数转换为网络字节顺序(network byte order)

在这里插入图片描述

  • inet_addr 通常用于将点分十进制的 IPv4 地址转换为网络字节顺序的 32 位整数

在这里插入图片描述

  • sendto用于在UDP协议中发送数据的函数

在这里插入图片描述

  • recvfrom用于在UDP协议中接收数据的函数

在这里插入图片描述

  • socket常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
          socklen_t address_len);

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
          socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
					socklen_t addrlen);
  • sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同。
在这里插入图片描述

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.

  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.

  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好 处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为 参数;

  • sockaddr结构

struct sockaddr
{
		_SOCKADDR_COMMON(sa_);/*Common data: address family and length. */
		char sa_data[14];  /*Address data*/
}
  • sockaddr_in结构
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
		_SOCKADDR_COMMON(sin_);
		in_port_t sin_port;		/* Port number. */
		struct in_addr sin_addr;	/* Internet address. */
		
		/* Pad to size of 'struct sockaddr.' */
		ussigned char sin_zero[sizeof(struct sockaddr) -
				__SOCKADDR_COMMON_SIZE - 
				sizeof(in_port_t) - 
				sizeof(struct in_addr)];
}
  • in_addr结构
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
		in_addr_t s_addr;
}

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

简洁版:

#include <netinet/in.h>

struct sockaddr_in {
    sa_family_t sin_family;     // 地址家族 AF_INET
    in_port_t sin_port;         // 16位端口号,网络字节顺序
    struct in_addr sin_addr;    // 32位IPv4地址,网络字节顺序
    char sin_zero[8];           // 不使用,填充0以使结构体大小与 struct sockaddr 相同
};

sockaddr_in 结构体包含以下字段:

  • sin_family: 地址家族,通常为 AF_INET,表示IPv4地址。
  • sin_port: 16位端口号,以网络字节顺序存储,即大端字节序。
  • sin_addr: 32位IPv4地址,以网络字节顺序存储,即大端字节序。它是一个结构体 struct in_addr,其中包含一个字段 s_addr,表示IPv4地址。
  • sin_zero: 用于填充,以使 sockaddr_in 的大小与通用地址结构 struct sockaddr 相同。

在网络编程中,当使用套接字 API 中的函数时,sockaddr_in 结构体通常需要进行类型转换,以便与通用的 struct sockaddr 结构体一起使用。这是因为套接字函数(如 bindconnectrecvfromsendto)通常使用通用地址结构 struct sockaddr 来表示地址信息。在使用时,可以使用类型强制转换sockaddr_in 转换为 struct sockaddr

套接字编程的种类:

  1. 域间套接字编程——同一个机器内
  2. 原始套接字编程——网络工具
  3. 网络套接字编程——用户间的网络通信

网络接口统一抽象化:参数的类型必须是统一的。

在这里插入图片描述

简单的UDP网络程序

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;
};

Main.cc

#include "UdpServer.hpp"
#include <memory>
#include <cstdio>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: "<< proc << " port[1024+]\n" << std::endl;
}

std::string Handler(const std::string &str)
{
    std::string res = "Server get a message: ";
    res += str;
    std::cout << res << std::endl;

    return res;
}
std::string ExcuteCommand(const std::string &cmd)
{
    //SafeCheck(cmd);
    FILE *fp = popen(cmd.c_str(),"r");//文件中存放命令的执行结果
    if(nullptr == fp)
    {
        perror("popen");
        return "error";
    }
    std::string result;
    char buffer[4096];
    while(true)
    {
        char* p = fgets(buffer,sizeof(buffer),fp);
        if(p == nullptr)break;
        result += buffer;
    }
    pclose(fp);
    return result;
}

//运行时的指令 ./udpserver port
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init();
    svr->Run(ExcuteCommand);//传递一个func指针;

    return 0;

}

Makefile

.PHONY:all
all:udpserver udpclient

udpserver:Main.cc
	g++ -o $@ $^ -std=c++11

udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udpserver udpclient

UdpClient.cc

#include <iostream>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>


using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: "<< proc << " serverip serverport\n"<<std::endl;
}
int main(int argc,char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }

    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    struct sockaddr_in server;
    bzero(&server,sizeof(server));
    server.sin_family= AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    socklen_t len = sizeof(server);
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);

    if(sockfd < 0)
    {
        cout<< "socker error!!!" <<endl;
        return 1;
    }

    // client 也要bind,只不过不需要用户显示的bind!一般有OS自由随机选择!
    // 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
    // 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!
    // 系统什么时候给我bind呢?首次发送数据的时候

    string message;
    char buffer[1024];
    while(true)
    {
        cout<<"Please Enter@";
        getline(cin, message);

        //给某某发数据
        sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr *)&server,len);

        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        size_t s = recvfrom(sockfd,buffer,1023,0,(struct sockaddr*)&temp,&len);
        if(s > 0)
        {
            buffer[s] = 0;
            cout<<buffer<<endl;
        }
    }

    close(sockfd);
    return 0;
}

UdpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"

//以下两种写法都可以
// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;

Log lg;

enum{
    SOCKET_ERR=1,
    BIND_ERR
};

uint16_t defaultport = 8080;
std::string default_ip = "0.0.0.0";
const int size = 1024;

class UdpServer{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = default_ip):sockfd_(0), port_(port), ip_(ip),isrunning_(false)
    {}
    void Init()
    {
        // 第一步 创建udp socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);
        
        
        // 第二步 bind socket
        struct sockaddr_in local;

        bzero(&local, sizeof(local));//Linux里面的函数
        local.sin_family = AF_INET;
        local.sin_port = htons(port_); //需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的,htons函数用于将主机字节序(host byte order)的16位整数转换为网络字节序(network byte order)
        local.sin_addr.s_addr = inet_addr(ip_.c_str()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 
        //local.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)//bind函数用于将一个套接字(socket)与特定的地址(IP地址和端口号)关联起来
        {
            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
    }
    void Run(func_t func) // 对代码进行分层
    {
        isrunning_ = true;
        char inbuffer[size];
        while(isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);//recvfrom是一个用于从网络套接字接收数据的函数
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            inbuffer[n] = 0;
            std::string info = inbuffer;
            std::string echo_string = func(info);//func为处理函数
            sendto(sockfd_,echo_string.c_str(),echo_string.size(),0,(const sockaddr*)&client,len);
        }
    }
    ~UdpServer()
    {
        if(sockfd_>0) close(sockfd_);
    }
private:
    int sockfd_;     // 网路文件描述符
    std::string ip_; // 任意地址bind 0
    uint16_t port_;  // 表明服务器进程的端口号
    bool isrunning_;
};

运行结果:

在这里插入图片描述

(不能使用设置了别名的命令)

window下网络编程套接字代码与Linux类似,添加对应的头文件,更改部分代码,比如:bzero函数要换成memset函数。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时序预测 | Python基于Multihead-Attention-TCN-LSTM的时间序列预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 时序预测 | Python基于Multihead-Attention-TCN-LSTM的时间序列预测 Multihead-Attention-TCN-LSTM&#xff08;多头注意力-TCN-LSTM&#xff09;是一种结合了多个注意力机制、时序卷积网络&#xff08;TCN&#xff0…

ORB-SLAM策略思考之RANSAC

ORB-SLAM策略思考之RANSAC 1. 初始化器的RANSAC ORB-SLAM中的初始化器是一个端到端的地图初始化策略&#xff0c;即不需要人的参与双线程同时计算本质矩阵和单应性矩阵使用基于RANSAC和卡方检验的评价方法 为了保证两种算法评价的一致性&#xff0c;计算本质矩阵F和单应性矩阵…

ETCD监控方法以及核心指标

文章目录 1. 监控指标采集1.1 监控指标采集1.2 配置promethues采集和大盘 2. 核心告警指标3. 参考文章 探讨etcd的监控数据采集方式以及需要关注的核心指标&#xff0c;便于日常生产进行监控和巡检。 1. 监控指标采集 etcd默认通过/metrics指标暴露相关指标&#xff0c;因此不…

引入 js 文件报错: Uncaught SyntaxError: Unexpected token <

文章目录 问题分析问题 在开发中引入 Cesium.js 文件时报错如下 分析 我是这么引入的<body><noscript><strong>Were sorry but <%= htmlWebpackPlugin.options.title %> doesnt work properly without JavaScript enabled.Please enable it to c

C++仿函数、万能头文件、transform学习

这是网上的一个代码,里面的一些东西以前没用过; #include <bits/stdc++.h> using namespace std;// A Functor class increment { private:int num; public:increment(int n) : num(n) { }int operator () (int arr_num) const {return num + arr_num;} };// Driver …

Python列表中的append功能及用法举例

Python列表中的append功能及用法举例 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;append()&#x1f333;&#x1f340;功能介绍&#x1f340;&#x1f340;语法&#x1f340;&#x1f340;示例&#x1f340;&#x1f340;注意事项&#x…

Spark3内核源码与优化

文章目录 一、Spark内核原理1、Spark 内核概述1.1 简介1.2 Spark 核心组件1.3 Spark 通用运行流程概述 2、Spark 部署模式2.1 YARN Cluster 模式(重点)2.2 YARN Client 模式2.3 Standalone Cluster 模式2.4 Standalone Client 模式 3、Spark 通讯架构3.1 Spark 通信架构概述3.2…

江科大stm32学习笔记5——蜂鸣器

目录 一、接线 二、代码部分 三、查看库函数的方法 一、接线 蜂鸣器选择有源高电平触发蜂鸣器。 GND——GND VCC——正极 I/O——B12 注意&#xff1a;32上的PA15、PB3、和PB4是默认调试端口&#xff0c;如果使用需要进行额外配置&#xff0c;一般避开这三个端口。 二、…

二手交易|校园二手交易小程序|基于微信小程序的闲置物品交易平台设计与实现(源码+数据库+文档)

校园二手交易小程序目录 目录 基于微信小程序的闲置物品交易平台设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、商品信息管理 3、公告信息管理 4、论坛信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕…

5天!软件开发考试该如何准备?

作者&#xff1a;统信UOS技术团队 意外的早晨 那是一个阳光明媚的早晨&#xff0c;我正走早上班的路上&#xff0c;慢悠悠地享受着早餐。忽然&#xff0c;我的手机发出了提示音。一条来自领导的消息突然打破了宁静的早餐时光。它通知我&#xff0c;五天后将有一场考试——离线…

算法39:统计全 1 子矩形(力扣1504)----单调栈

题目: 给你一个 m x n 的二进制矩阵 mat &#xff0c;请你返回有多少个 子矩形 的元素全部都是 1 。 示例 1&#xff1a; 输入&#xff1a;mat [[1,0,1],[1,1,0],[1,1,0]] 输出&#xff1a;13 解释&#xff1a; 有 6 个 1x1 的矩形。 有 2 个 1x2 的矩形。 有 3 个 2x1 的矩…

申万宏源基于 StarRocks 构建实时数仓

作者 &#xff1a;申万宏源证券 实时数仓项目组 小编导读&#xff1a; 申万宏源证券有限公司是由新中国第一家股份制证券公司——申银万国证券股份有限公司与国内资本市场第一家上市证券公司——宏源证券股份有限公司&#xff0c;于 2015 年 1 月 16 日合并组建而成&#xff0c…

error: failed to open index: Database already open. Cannot acquire lock报错解决办法

ordinals节点数据同步出现报错 error: failed to open index: Database already open. Cannot acquire lock.问题分析&#xff1a; 出现问题的原因是btcoin core节点数据没有同步完我们就开始进行ordinals数据同步&#xff0c;导致/root/.local/share/ord/index.redb 文件数据…

盒子模型的内容总结

知识引入 1.认识盒子模型 在浏览网站时我们会发现内容都是按照区域划分的。这使得网页很工整、美观。在页面中&#xff0c;每一块区域分别承载不同的内容&#xff0c;使得网页的内容虽然零散&#xff0c;但是在版式排列上依然清晰有条理。如图1 图1 *承载内容的区域称为盒子…

Next.js 学习笔记(八)——优化

优化 Next.js 提供了多种内置优化&#xff0c;旨在提高应用程序的速度和核心网络生命指数。本指南将介绍可用于增强用户体验的优化功能。 内置组件 内置组件抽象化了实现常见 UI 优化的复杂性。这些组件包括&#xff1a; 图像&#xff1a;基于本地 <img> 元素构建。图…

Flink问题解决及性能调优-【Flink rocksDB读写state大对象导致背压问题调优】

RocksDB是Flink中用于持久化状态的默认后端&#xff0c;它提供了高性能和可靠的状态存储。然而&#xff0c;当处理大型状态并频繁读写时&#xff0c;可能会导致背压问题&#xff0c;因为RocksDB需要从磁盘读取和写入数据&#xff0c;而这可能成为瓶颈。 遇到的问题 Flink开发…

STM32 有源蜂鸣器

模块介绍: 结构&#xff1a;有源蜂鸣器通常由一个振膜和一个驱动电路组成。振膜是负责产生声音的部分&#xff0c;而驱动电路则负责控制振荡频率和幅度。 工作原理&#xff1a;有源蜂鸣器的驱动电路会向振膜施加电压&#xff0c;使其振动产生声音。驱动电路可以根据输入信号的…

centos7安装mysql5.7 或者mysql8

1、centos7安装mysql8 mysql官网 https://dev.mysql.com/downloads/mysql/ 示例2个版本的下载地址 #5.7.30下载地址 wget https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.30-1.el7.x86_64.rpm-bundle.tar #8.0.22下载地址 wget https://cdn.mysql.com/archives/mysql-8…

网络防御保护——课程笔记

一.防火墙 防火墙的主要职责&#xff1a;控制和防护 --- 安全策略 --- 防火墙可以根据安全策略来抓取流量之后做出对应的动作。 防火墙的分类 防火墙的发展进程 防火墙的控制 带内管理 --- 通过网络环境对设备进行控制 --- telnet&#xff0c;ssh&#xff0c;web --- 登录设备…

【node】Node.js的常用内置模块:

文章目录 一、os模块&#xff1a;【1】常用的OS模块方法包括&#xff1a;【2】案例&#xff1a; 二、path模块&#xff1a;【1】常用的path模块方法包括&#xff1a;【2】案例&#xff1a; 三、url模块&#xff1a;【1】常用的url模块方法包括&#xff1a;【2】案例&#xff1a…