【计算机网络】初识Socket编程,揭秘Socket编程艺术--UDP篇

news2024/11/18 18:20:42
🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

  • `Socket编程准备知识`
    • `理解源IP地址和目的IP地址 `
    • `认识端口号 `
    • `网络字节序 `
  • `socket编程`
    • `socket编程接口`
        • `socket系统调用`
        • `bzero函数`
        • `struct sockaddr结构体分类`
          • `以sockaddr_in为例`
        • `bind系统调用`
        • `inet_addr函数与inet_ntoa函数`
        • `recvfrom系统调用`
        • `sendto系统调用`
    • `udpsocket代码练习`
            • `Server.hpp`
            • `main.cc`
            • `Client.cc`
            • `InetAddr.hpp`


Socket编程准备知识

理解源IP地址和目的IP地址

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

思考: 我们光有IP地址就可以完成通信了吗? 想象一下发qq消息的例子,,有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出,这个数据要给哪个程序进行解析。
在这里插入图片描述

认识端口号

  • 端口号(port)是传输层协议的内容。
  • 端口号是一个2字节16位的整数(uint16_t)。
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理。
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程
  • 一个端口号只能被一个进程占用。
  • 注意:一个进程可以绑定多个端口号, 但是一个端口号不能被多个进程绑定。
  • 传输层协议(TCP和 UDP)的数据段中有两个端口号,分别叫做源端口号目的端口号.就是在描述 “数据是谁发的,要发给谁”;

端口号范围划分

  • 0 - 1023:知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议,他们的端口号都是固定的.
  • 1024 - 65535:操作系统动态分配的端口号.客户端程序的端口号,就是由操作系统从这个范围分配的.

网络字节序

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

  • 在网络编程中,网络字节序统一规定为大端序
    在这里插入图片描述
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

socket编程

在网络编程中,Socket 是一种非常重要的抽象,它提供了在不同主机之间或同一主机上的不同进程之间进行通信的端点。Socket 编程是网络通信的基础,尤其是在 TCP/IP 协议栈中。

socket编程接口

socket系统调用

在这里插入图片描述

  • 功能:主要作用是创建一个新的socket(套接字),这个socket是网络通信中的一个端点,用于实现不同主机之间或同一主机上不同进程之间的数据交换。
  • Domain(域)指定 Socket 所使用的协议族。对于 Internet 上使用的 TCP/IP 协议,该参数通常是 AF_INET(IPv4)或 AF_INET6(IPv6)。其他可能的值包括 AF_UNIX(Unix 域套接字,仅在同一台机器上的不同进程间通信时使用)。
  • Type(类型):指定 Socket 的类型。常用的类型有 SOCK_STREAM(表示面向连接的 TCP 协议)和 SOCK_DGRAM(表示无连接的 UDP 协议)。还有其他的类型,如 SOCK_RAW(原始套接字,可以接收 IP 层的数据包),但这些较为特殊。
  • Protocol(协议):在大多数情况下,对于 TCP 和 UDP,可以将此参数设置为 0,让系统自动选择默认的协议。
  • 成功时:socket() 函数成功执行后,会返回一个非负整数作为 Socket 描述符。这个描述符在后续的所有 Socket 操作中都会被用到。可以理解为一个文件描述符fd
  • 失败时:如果 socket() 函数执行失败,它会返回 -1,并设置全局变量 errno 以指示错误的具体原因。
bzero函数

在这里插入图片描述

  • 作用是将指定内存区域的前n个字节置为零
  • void *s:指向需要清零的内存区域的起始地址的指针。
  • size_t n:需要清零的字节数。这个参数指定了从s指向的内存地址开始,要清零的字节数量。
  • 该作用也可以使用memset函数实现。
struct sockaddr结构体分类
以sockaddr_in为例

代码示例:

 // 构建 sockaddr_in结构体并初始化
 struct sockaddr_in local;
 bzero(&local, sizeof(local)); // 清空addr 也可以使用 memset(&local,0,sizeof(local))
 local.sin_family = AF_INET;
 local.sin_port = htons(_Serverport);   // 主机序列转网络序列
 local.sin_addr.s_addr = inet_addr(_Serverip.c_str()); // IP字符串转为int,并且转为网络序列
  
bind系统调用

在这里插入图片描述

  • bind 函数的作用是将一个套接字(Socket)与一个特定的IP地址和端口号(对于TCP/IP协议)绑定起来,或者与一个特定的路径名(对于Unix域套接字)绑定起来。
  • sockfd:这是一个由socket函数返回的套接字描述符。它标识了要进行绑定的套接字。
  • addr:这是一个指向sockaddr结构(或其特定于协议的变体,如sockaddr_in对于IPv4)的指针。这个结构包含了要绑定的IP地址和端口号。对于IPv4,通常使用sockaddr_in结构,它包含了一个sin_family字段(指定地址族,如AF_INET),一个sin_addr字段(包含IP地址),以及一个sin_port字段(包含端口号,以网络字节顺序表示)。注意:使用的时候都会强转为sockaddr类型。
  • addrlen:这是一个表示addr参数所指向的地址结构长度的整数。这允许bind函数知道传递给它的是哪种类型的地址结构。
  • 调用成功返回0,失败返回-1,错误码被设置。

注意:客户端需要绑定,但是不需要调用bind显示的绑定,udp客户端首次发送消息的时候,OS会自动随机给客户端进行端口号的绑定,原因:要绑定,必然要和本地的特定的端口号相关联起来,但是一个端口号只能和一个进程相关联,这样做可以防止客户端端口号的冲突。

我们使用的云服务器不允许bind公网ip,也不推荐绑定公网IP或任意一个确定的ip,推荐在绑定IP的时候,将ip设置为0(或则宏 INADDR_ANY),即任意IP地址绑定。

代码示例:

// 构建 sockaddr_in结构体并初始化
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 清空addr 也可以使用 memset(&local,0,sizeof(local))
local.sin_family = AF_INET;
local.sin_port = htons(_Serverport);                  // 主机序列转网络序列
local.sin_addr.s_addr = inet_addr(_Serverip.c_str()); // IP字符串转为int,并且转为网络序列

socklen_t len;
len = sizeof(local);

// bind 绑定ip 与 port
 bind(_socketfd, (struct sockaddr *)&local, len);

inet_addr函数与inet_ntoa函数

在这里插入图片描述

  • inet_addr函数的主要作用是将一个用点分十进制(例如"192.168.1.1")格式的IPv4地址字符串转换成一个无符号长整型数(u_long类型),并且这个长整型数已经按照网络字节顺序排列。网络字节顺序通常指的是大端字节序,这是在网络通信中广泛采用的一种字节序标准。(inet_addr函数只能处理IPv4地址,无法处理IPv6地址
  • inet_ntoa函数的作用与inet_addr函数相反,它将一个表示IPv4地址的无符号长整型数(u_long类型,网络字节顺序)转换回用点分十进制格式表示的字符串。(inet_ntoa函数也只能处理IPv4地址)
recvfrom系统调用

在这里插入图片描述

  • recvfrom在Linux中用于接收数据的系统调用。它特别适用于面向无连接的协议,如UDP(用户数据报协议)。recvfrom函数允许程序接收数据,并同时获取数据发送方的地址信息。
  • recvfrom函数的返回值类型为ssize_t,即有符号的size_t类型。这种类型可以表示正值、负值和零,用于表示接收到的数据大小或错误信息。
    • 成功时:返回值为接收到的数据字节数。
    • 失败时:返回值为-1,此时可以通过检查errno来获取具体的错误原因。
  • sockfd:需要接收数据的socket文件描述符。这是之前通过socket()系统调用创建的。
  • buf:指向接收数据的缓冲区的指针。接收到的数据将被存储在这个缓冲区中。
  • len:缓冲区的大小,即可以接收的最大数据量。如果接收到的数据量超过了这个大小,数据可能会被截断
  • flags:指定接收数据的方式和行为。这个参数可以包含多个标志位,用于修改函数的行为。常见的标志位包括MSG_DONTWAIT(非阻塞模式,如果没有数据可读,则立即返回)和MSG_WAITALL(阻塞模式,直到接收到指定长度的数据才返回)。
  • src_addr:含义:指向一个sockaddr结构体的指针,该结构体用于存储发送方的地址信息。在调用recvfrom之前,这个参数可以设置为NULL,如果不关心发送方的地址信息。但是,如果提供了非NULL的指针,recvfrom会在接收数据的同时,将发送方的地址信息填充到这个结构体中。
  • addrlen:含义:指向一个socklen_t 变量的指针,该变量用于存储发送方地址的长度。在调用recvfrom之前,应该将这个变量的值设置为src_addr所指向的sockaddr结构体的大小。在调用之后,recvfrom会更新这个变量的值,以反映实际存储的发送方地址的长度。

示例代码:

char buffer[1024];
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
socklen_t len;
len = sizeof(peer);
ssize_t n = recvfrom(_socketfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
if(n<0)
{
	exit(1);
}
sendto系统调用

在这里插入图片描述

  • sendto函数是Linux系统(以及类Unix系统)中用于在网络上发送数据的函数之一。该函数允许程序将数据发送到指定的目标地址,目标地址包括目标主机的IP地址和端口号。
  • 成功时:返回值是实际发送的数据字节数。如果数据被完全发送,则返回值等于请求发送的字节数。
  • 失败时:返回值是-1,表示发送数据失败。
  • sockfd:socket文件描述符,它是通过socket()系统调用创建的。
  • buf:指向要发送数据的缓冲区的指针。
  • len:指定msg指向的缓冲区中数据的长度(以字节为单位)。
  • flags:指定sendto函数的行为。这个参数通常设置为0,表示使用默认行为。
  • dest_addr:指向sockaddr结构体的指针,该结构体包含了目标地址的信息,包括目标主机的IP地址和端口号。这是sendto函数将数据发送到的目的地。
  • addrlen:指定 dest_addr 指向的sockaddr结构体的大小。这个参数用于确保sendto函数能够正确地解释目标地址信息。

代码示例:

	std::string message;

	std::cout << "Please Enter#" << std::endl;
    getline(std::cin, message);   //键盘获取信息

    struct sockaddr_in Server;  //创建一个sockaddr_in结构体
    bzero(&Server, sizeof(Server));
    
    //初始化
    Server.sin_family = AF_INET;
    Server.sin_port = htons(Serverport);
    Server.sin_addr.s_addr = inet_addr(ServerIp.c_str());

    sendto(socketfd, message.c_str(), sizeof(message), 0, (struct sockaddr *)&Server, sizeof(Server));

udpsocket代码练习

Udp_echo_Server(Client向Server发送什么就返回什么)

Server.hpp
#pragma

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

using namespace std;

enum EXITERROR
{
    SOCKETCREATE = 1,
    SOCKETBIND
};

class UdpServer
{
public:
    UdpServer( uint16_t port) : _Serverport(port),/* _Serverip(ip)*/ _IsRuning(false), _socketfd(-1)
    {}
    void UdpServer_init()
    {
        // 创建 socket套接字
        _socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socketfd == -1) // 创建失败
        {
            LOG(FATAL, "socket fialed...cerrno:%d strerror:%s", errno, strerror(errno));
            exit(SOCKETCREATE);
        }
        LOG(DEBUG, "socket create success..., _socketfd is: %d", _socketfd);

        // 构建 sockaddr_in结构体并初始化
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清空addr 也可以使用 memset(&local,0,sizeof(local))
        local.sin_family = AF_INET;
        local.sin_port = htons(_Serverport);                  // 主机序列转网络序列
        //local.sin_addr.s_addr = inet_addr(_Serverip.c_str()); // IP字符串转为int,并且转为网络序列
        local.sin_addr.s_addr = INADDR_ANY;//0 


        socklen_t len;
        len = sizeof(local);

        // bind 绑定ip 与 port
        int n = bind(_socketfd, (struct sockaddr *)&local, len);
        if (n == -1)
        {
            LOG(FATAL, "bind socket fialed... ,cerrno: %d ,strerror: %s", errno, strerror(errno));
            exit(SOCKETBIND);
        }
        LOG(DEBUG, "bind  socket success...");
    }
    void UdpServer_Start()
    {
        _IsRuning = true;

        while (true)
        {
            // 1. 接受Client的信息
            char buffer[1024];

            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len;
            len = sizeof(peer);
            ssize_t n = recvfrom(_socketfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
            if (n < 0)
            {
                LOG(DEBUG, "UdpServer receive message error,errno:%d ,strerrno:%s", errno, strerror(errno));
            }
            InetAddr addr(&peer);
            cout << "UpdServer receive a message is:" << buffer<<"  from: "<<addr.GetIP()<<"  "<<addr.Getport() << endl;
            // 2. 将获取的信息再返回回去
            sendto(_socketfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, len);
        }
        _IsRuning = false;
    }
    ~UdpServer()
    {}
private:
    int _socketfd;
    uint16_t _Serverport;
    // string _Serverip;
    bool _IsRuning;
};
main.cc
#include <memory>
#include <iostream>
#include "UdpServer.hpp"

void Usage(string str)
{
    std::cout << "Usage:\n\t "<< str << " Serverport" << std::endl;
}

//  ./Server  Serverport
int main(int argc, char *args[])
{
    if (argc != 2)
    {
        Usage(args[0]);
        exit(1);
    }
    uint16_t Serverport = std::stoi(args[1]);

    EnIsPrint();
    std::unique_ptr<UdpServer> Serptr = std::make_unique<UdpServer>(Serverport);   //c++14语法

    Serptr->UdpServer_init();
    Serptr->UdpServer_Start();

    return 0;
}
Client.cc
#pragma

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>

void Usage(std::string str)
{
    std::cout << "Usage:\n\t " << str << " Serverip Serverport" << std::endl;
}

//  ./UdpClient  Serverport
int main(int argc, char *args[])
{
    if (argc != 3)
    {
        Usage(args[0]);
        exit(1);
    }
    uint16_t Serverport = std::stoi(args[2]);
    // std::string ServerIp = args[1];

    // 创建套接字
    int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketfd == -1)
    {
        std::cout << "Client socket create failed..." << std::endl;
        exit(1);
    }

    struct sockaddr_in Server;
    bzero(&Server, sizeof(Server));
    Server.sin_family = AF_INET;
    Server.sin_port = htons(Serverport);
    // Server.sin_addr.s_addr = inet_addr(ServerIp.c_str());
    Server.sin_addr.s_addr = 0;

    // std::string message;
    // Client不需要显示的bind
    while (true)
    {
        std::string message;
        std::cout << "Please Enter#" << std::endl;
        getline(std::cin, message);   //键盘获取信息

       

        sendto(socketfd, message.c_str(), sizeof(message), 0, (struct sockaddr *)&Server, sizeof(Server));
        struct sockaddr_in Server;
        socklen_t size = sizeof(Server);

        char buffer[1024];
        ssize_t n = recvfrom(socketfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&Server, &size);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "Server echo# " << buffer << std::endl;
        }
    }
    return 0;
}

InetAddr.hpp
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr
{
private:
    void GetAddr(std::string *ip, uint16_t *port)
    {
        *ip = inet_ntoa(_Addr->sin_addr);
        *port = ntohs(_Addr->sin_port);
    }

public:
    InetAddr(struct sockaddr_in *Addr): _Addr(Addr)
    {
        GetAddr(&_ip,&_port);
    }
    std::string GetIP()
    {
        return _ip;
    }
    uint16_t Getport()
    {
        return _port;
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in *_Addr;
    std::string _ip;
    uint16_t _port;
};

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

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

相关文章

生信机器学习入门4 - 构建决策树(Decision Tree)和随机森林(Random Forest)分类器

机器学习文章回顾 生信机器学习入门1 - 数据预处理与线性回归&#xff08;Linear regression&#xff09;预测 生信机器学习入门2 - 机器学习基本概念 生信机器学习入门3 - Scikit-Learn训练机器学习分类感知器 生信机器学习入门4 - scikit-learn训练逻辑回归&#xff08;L…

【Android 14源码分析】Activity启动流程-2

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

高并发内存池(六):补充内容

目录 有关大于256KB内存的申请和释放处理方法 处理大于256KB的内存申请 补充内容1 补充内容2 补充内容3 处理大于256KB的内存释放 新增内容1 新增内容2 测试函数 使用定长内存池替代new 释放对象时不传对象大小 补充内容1 补充内容2 补充内容3 补充内容4 测试…

Python(五)-函数

目录 函数的定义与调用 特点 语法格式 函数的参数 函数的返回值 函数嵌套调用 变量的作用域 局部变量 全局变量 函数的多种参数 位置参数 关键字参数 默认参数 可变参数 函数的定义与调用 python函数需要使用def关键字来定义,需要先定义,后调用 特点: 先定义…

课堂讨论:评价计算机性能的指标

**课堂讨论&#xff1a;评价计算机性能的指标** --- ### 课堂开始 **王老师**&#xff1a;同学们&#xff0c;今天我们来讨论如何评价计算机性能的指标。小明&#xff0c;你知道有哪些指标吗&#xff1f; **小明**&#xff1a;嗯...有吞吐率和响应时间吧&#xff1f;&#…

双链表的插入删除遍历

双链表的插入操作 双链表的删除操作 双链表的遍历操作

Watchdog Timers(WDT)

文章目录 1. 介绍2. Feature List3. 概述3.1. Safety Watchdog3.2. CPU Watchdog 4. 看门狗定时器功能5. Endinit Functions5.1 Password Access to WDTxCON05.1.1 Static Password5.1.2 Automatic Password Sequencing 5.2 Check Access to WDTxCON05.3 Modify Access to WDTx…

点餐小程序实战教程13餐桌管理

目录 1 创建数据源2 搭建管理后台3 生成餐桌码4 找到自己的appid和secret5 小程序里获取餐桌信息总结 我们上一篇介绍了点餐界面的菜品展示功能。现实中如果你去餐馆用餐&#xff0c;总是给餐桌贴一个二维码&#xff0c;服务员会告诉你扫码点餐。 扫码大家现在都已经非常熟练了…

“从零开始学排序:简单易懂的算法指南“

“一辈人有一辈人要做的事&#xff01;&#xff01;&#xff01;” 这一期的节目呢&#xff0c;是关于排序的内容&#xff0c;相信大家对此一定很熟悉吧&#xff01; 排序&#xff1a; 排序是将一组元素按照一定的规则或标准进行组织和排列的过程。 冒泡排序&#xff1a; 冒…

此连接非私人连接

当你手机浏览器输入网站打开提示“此连接非私人连接&#xff0c;此网站可能在冒充来窃取你的个人或财务信息。你应回到之前的页面”这是因为该网站的SSL数字证书到期导致&#xff0c;需要此网站的管理员重新申请数字证书替换之前的文件才可以实现。 注意&#xff1a;如果你不是…

Token: 数据库、存储系统和API安全的应用

一. Token Token是一种常见的计算机术语&#xff0c;它在不同的上下文中有不同的含义。在身份验证和授权的上下文中&#xff0c;Token通常指的是服务端生成的一串字符串&#xff0c;作为客户端进行请求的一个令牌。当用户登录后&#xff0c;服务器会生成一个Token并返回给客户…

【高阶数据结构】平衡二叉树(AVL)的删除和调整

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《高阶数据结构》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多《高阶数据结构》点击专栏链接查看&a…

记一次教学版内网渗透流程

信息收集 如果觉得文章写的不错可以共同交流 http://aertyxqdp1.target.yijinglab.com/dirsearch dirsearch -u "http://aertyxqdp1.target.yijinglab.com/"发现 http://aertyxqdp1.target.yijinglab.com/joomla/http://aertyxqdp1.target.yijinglab.com/phpMyA…

DialFRED基准:具有对话能力的具身智能Agent

目录 一、DialFRED数据集1.1 数据集规模与任务结构1.2 任务实例的构成1.3 人类标注的问答数据1.4 Oracle自动生成答案1.5 任务多样性与数据增强1.6 数据集的词汇多样性1.7 任务和环境的多样性 二、提问者-执行者框架2.1 框架概述2.2 提问者模型设计2.3 执行者模型设计2.4 强化学…

【读书笔记-《30天自制操作系统》-25】Day26

本篇仍然是围绕着命令行窗口做文章。首先优化命令行窗口的移动速度&#xff0c;然后增加多个命令行窗口功能。接着优化了命令行窗口的关闭&#xff0c;最后增加了两个命令start与ncst。 1. 优化命令行窗口移动速度 首先对命令行窗口的移动速度进行优化。主要的优化点有以下几…

WEB服务器——Tomcat

服务器是可以使用java完成编写&#xff0c;是可以接受页面发送的请求和响应数据给前端浏览器的&#xff0c;而在开发中真正用到的Web服务器&#xff0c;我们不会自己写的&#xff0c;都是使用目前比较流行的web服务器。 如&#xff1a;Tomcat 1. 简介 Tomcat 是一个开源的轻量…

二维数组的存放

今天我水的文章是二维数组的存放 二维数组的存放方式其实和一维数组没有区别&#xff0c;但如果想要更直观的了解&#xff0c;我们可以把它们的地址打印出来。 代码如下&#xff1a; #include <stdio.h> int main() {int arr[3][3];//二维数组&#xff0c;int数组类型…

【高效管理集合】并查集的实现与应用

文章目录 并查集的概念主要操作优化技术应用场景 并查集的实现基本框架并查集的主要接口总体代码 并查集的应用省份的数量等式方程的可满足性 总结 并查集的概念 并查集&#xff0c;也称为不相交集&#xff0c;是一种树形的数据结构&#xff0c;用于处理一些不相交集合的合并及…

ClickHouse | 查询

1 ALL 子句 2 ARRAY JOIN 使用别名 :在使用时可以为数组指定别名&#xff0c;数组元素可以通过此别名访问&#xff0c;但数组本身则通过原始名称访问 3 DISTINCT子句 DISTINCT不支持当包含有数组的列 4 FROM子句 FROM 子句指定从以下数据源中读取数据: 1.表 2.子…

建筑资质应该怎么选?

建筑资质是建筑企业承接工程项目的必备条件&#xff0c;它不仅关系到企业的市场竞争力&#xff0c;还直接影响到企业的经营效益。因此&#xff0c;选择适合自己企业的建筑资质至关重要。以下是一些选择建筑资质时需要考虑的关键因素&#xff1a; 1. 明确企业定位 首先&#x…