Linux网络编程——概念及实现双方聊天

网络编程的场景: 假设你面前有五座房子(服务器),你要走到其中一座房子的某一间,此时你站在五座房子面前很迷茫,突然,第二座房子上面有人在叫,并且用汉语(TCP/UDP)叫:“我是第二号楼(ip地址),我的房间是1102(端口号)”,那么你就得到了楼号和房间号(获取服务器ip和端口号),就可以去找那个人(连接)。那个人就回房间了,等待你的到来。

Sockt服务器和客户端的开发步骤

  1. 创建套接字socket
  2. 为套接字添加信息(IP地址和端口号)
  3. 绑定套接字socket
  4. 监听网络连接
  5. 监听到有客户端接入,接受一个连接
  6. 数据交互
  7. 关闭套接字socket,断开连接

需要用到的12个API: socket、bind、inet_aton、inet_ntoa、listen、accept、read、write、send、recv、connect、htons

socket 函数

socket 函数用于创建一个新的套接字,返回一个套接字文件描述符。

原型
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
参数
  • domain(协议族):指定使用的地址族。常见的值包括:
    • AF_INET:IPv4协议
    • AF_INET6:IPv6协议
    • AF_UNIX:本地通信(UNIX域套接字)
  • type(套接字类型):指定套接字的类型。常见的值包括:
    • SOCK_STREAM:提供面向连接的稳定数据传输(TCP)
    • SOCK_DGRAM:提供数据报文服务(UDP)
    • SOCK_RAW:提供原始网络协议访问
  • protocol:指定使用的协议。一般情况下,传递 0 以自动选择合适的协议。
返回值

成功时返回一个新的套接字文件描述符,失败时返回 -1 并设置 errno 来指示错误类型。

示例
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}

bind 函数

bind 函数将套接字与特定的地址和端口绑定。对服务器来说,这一步是必不可少的,以便客户端可以通过指定的地址和端口连接到服务器。

原型
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
  • sockfd:由 socket 函数返回的套接字文件描述符。
  • addr:指向 sockaddr 结构的指针,该结构包含了需要绑定的地址和端口信息。具体类型取决于协议族,比如 struct sockaddr_in 用于 IPv4。
  • addrlenaddr 结构的大小(字节数)。
返回值

成功时返回 0,失败时返回 -1 并设置 errno 来指示错误类型。

示例
// 声明并清零‘sockaddr_in’结构
struct sockaddr_in my_addr;
memset(&my_addr, 0, sizeof(my_addr));

// 设置地址族、端口号、IP地址
my_addr.sin_family = AF_INET;  // 使用IPv4地址
my_addr.sin_port = htons(8080);  // 绑定端口号 8080
my_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有本地地址

// 绑定套接字
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) == -1) {
    perror("bind");
    close(sockfd);
    exit(EXIT_FAILURE);
}

详细地解释一下 sockaddr 结构及其在 bind 函数中的使用:

sockaddr 是一个通用的地址结构,它可以表示多种不同类型的地址。为了方便使用特定协议族的地址,我们通常会使用具体的地址结构,并在需要时将其强制转换为 sockaddr 类型。

对于IPv4地址,我们使用 sockaddr_in 结构。以下是 sockaddr_in 的定义:

struct sockaddr_in {
    sa_family_t    sin_family; // 地址族 (例如 AF_INET)
    in_port_t      sin_port;   // 端口号 (需要使用 htons 转换为网络字节序)
    struct in_addr sin_addr;   // IP地址 (in_addr 结构)
    char           sin_zero[8]; // 填充字段, 使结构大小与 `sockaddr` 对齐
};

in_addr 结构表示一个IPv4地址,它包含一个成员:

struct in_addr {
    uint32_t s_addr; // IP地址 (使用 `inet_aton` 或 `INADDR_ANY` 等设置)
};

inet_atoninet_ntoa

inet_atoninet_ntoa 是两个用于处理IP地址的函数,分别用于将点分十进制的字符串格式的IP地址转换为二进制格式,以及将二进制格式的IP地址转换为点分十进制字符串格式。点分十进制(Dotted Decimal Notation)是一种表示IPv4地址的方法,将IP地址表示为四个以点(.)分隔的十进制数,每个十进制数对应一个字节(8位)。

inet_aton 函数

inet_aton 函数用于将点分十进制的字符串格式的IPv4地址转换为二进制格式,并存储在 in_addr 结构中。

原型

#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
参数
  • cp:指向以点分十进制表示的IP地址字符串(例如 "192.168.1.1")。
  • inp:指向 in_addr 结构的指针,函数将转换后的二进制IP地址存储在此结构中。
返回值

成功时返回非零值(1),失败时返回零(0)。

示例
struct in_addr addr;
inet_aton("192.168.1.1", &addr);

inet_ntoa 函数

inet_ntoa 函数用于将 in_addr 结构中的二进制格式的IP地址转换为点分十进制的字符串格式。

原型
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);
参数
  • in:包含二进制格式IP地址的 in_addr 结构。
返回值

返回指向静态缓冲区中存储的以点分十进制表示的IP地址字符串的指针。

注意

返回的字符串存储在静态缓冲区中,因此在多线程环境中使用时需要注意线程安全问题。

示例
struct in_addr addr;
addr.s_addr = inet_addr("192.168.1.1");  // 或者使用 inet_aton
char *ip_str = inet_ntoa(addr);
printf("IP address: %s\n", ip_str);

listen 函数

listen 函数用于将套接字设置为被动模式,表示这个套接字用于接受来自客户端的连接请求。

原型
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd, int backlog);
参数
  • sockfd:套接字文件描述符,指向一个已经绑定了地址的套接字(通过 bind 函数)。
  • backlog:连接队列的最大长度,即等待处理的连接请求的最大数量。如果有更多的连接请求到达,它们可能会被拒绝或者忽略。
返回值

成功时返回0,失败时返回-1,并设置 errno 来指示错误。

示例
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;

// 绑定套接字到一个地址和端口
bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

// 设置套接字为监听模式,允许最多5个待处理连接
if (listen(sockfd, 5) == -1) {
    perror("listen");
    close(sockfd);
    exit(EXIT_FAILURE);
}

accept 函数

accept 函数用于从监听套接字的连接队列中接受一个连接请求,并返回一个新的套接字文件描述符用于与客户端通信。

原型
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
  • sockfd:监听套接字文件描述符,通过 listen 函数设置为监听模式的套接字。
  • addr:指向 sockaddr 结构的指针,用于存储连接客户端的地址信息。可以为 NULL,表示不关心客户端地址。
  • addrlen:指向 socklen_t 类型的变量,用于存储客户端地址结构的大小。可以为 NULL,表示不关心客户端地址。
返回值

成功时返回新的套接字文件描述符,失败时返回-1,并设置 errno 来指示错误。

示例
struct sockaddr_in cli_addr;
socklen_t clilen = sizeof(cli_addr);

// 从连接队列中接受一个连接请求
int newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
if (newsockfd == -1) {
    perror("accept");
    close(sockfd);
    exit(EXIT_FAILURE);
}

// 现在可以使用 newsockfd 与客户端通信

在Linux网络编程中,readwrite 函数用于从套接字读取数据和向套接字写入数据。这两个函数是从Unix系统编程中继承过来的,广泛应用于文件I/O和网络I/O操作。

read 函数(从哪里读)

read 函数用于从文件描述符中读取数据。在网络编程中,文件描述符可以是一个套接字。

原型
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
参数
  • fd:文件描述符(或套接字描述符)。
  • buf:指向存储读取数据的缓冲区。
  • count:要读取的最大字节数。
返回值
  • 成功时,返回读取的字节数。如果返回值是0,表示已到达文件末尾(对于套接字,表示对端已关闭连接)。
  • 失败时,返回-1,并设置 errno 指示错误。
示例
char buffer[256];
ssize_t n = read(sockfd, buffer, sizeof(buffer));
if (n < 0) {
    perror("ERROR reading from socket");
}

write 函数(写到哪里去)

write 函数用于向文件描述符中写入数据。在网络编程中,文件描述符可以是一个套接字。

原型
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
参数
  • fd:文件描述符(或套接字描述符)。
  • buf:指向要写入数据的缓冲区。
  • count:要写入的字节数。
返回值
  • 成功时,返回写入的字节数。
  • 失败时,返回-1,并设置 errno 指示错误。
示例
const char *message = "Hello, World!";
ssize_t n = write(sockfd, message, strlen(message));
if (n < 0) {
    perror("ERROR writing to socket");
}

在网络编程中,sendrecv 函数是用于在套接字上发送和接收数据的更灵活的函数。它们提供了一些额外的选项,可以更精细地控制数据传输行为。

send 函数

send 函数用于向一个连接的套接字发送数据。

原型
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数
  • sockfd:要发送数据的套接字描述符。
  • buf:指向要发送数据的缓冲区。
  • len:要发送的数据长度。
  • flags:控制数据传输的标志,可以是以下标志的组合:设置为0,意味着不使用任何特殊选项
    • MSG_OOB:发送带外数据。
    • MSG_DONTROUTE:不使用路由表发送数据。
    • MSG_NOSIGNAL:在向已关闭的连接发送数据时不产生 SIGPIPE 信号。
返回值
  • 成功时,返回发送的字节数。
  • 失败时,返回-1,并设置 errno 指示错误。
示例
const char *message = "Hello, World!";
ssize_t n = send(sockfd, message, strlen(message), 0);
if (n < 0) {
    perror("ERROR sending to socket");
}

recv 函数

recv 函数用于从一个连接的套接字接收数据。

原型
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数
  • sockfd:要接收数据的套接字描述符。
  • buf:指向接收数据的缓冲区。
  • len:接收数据的最大长度。
  • flags:控制数据接收的标志,可以是以下标志的组合:设置为0,意味着不使用任何特殊选项
    • MSG_OOB:接收带外数据。
    • MSG_PEEK:查看数据但不从输入队列中删除。
    • MSG_WAITALL:等待所有数据到达。
返回值
  • 成功时,返回接收的字节数。如果连接被关闭,返回0。
  • 失败时,返回-1,并设置 errno 指示错误。
示例
char buffer[256];
ssize_t n = recv(sockfd, buffer, sizeof(buffer), 0);
if (n < 0) {
    perror("ERROR receiving from socket");
}

connect 函数

在网络编程中,connect 函数是用来在客户端程序中连接服务器的。connect 函数用于将客户端的套接字连接到服务器上的套接字。

原型
#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:套接字描述符。
  • addr:指向包含目标地址和端口的 struct sockaddr 结构体的指针。
  • addrlen:地址结构的大小。
返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno 以指示错误。
示例
int sockfd;
struct sockaddr_in serv_addr;

// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
    perror("ERROR opening socket");
    exit(1);
}

// 初始化服务器地址结构
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
    perror("ERROR invalid server IP address");
    close(sockfd);
    exit(1);
}

// 连接服务器
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    perror("ERROR connecting");
    close(sockfd);
    exit(1);
}

htons 函数

htons 函数用于将主机字节顺序转换为网络字节顺序。网络通信使用大端字节序,而主机可能使用小端字节序。因此,在设置端口号时,需要将其转换为网络字节顺序。

  • h:host(主机)
  • t:to(到)
  • n:network(网络)
  • s:short(短整数)

因此,htons 代表 "host to network short",即将主机字节顺序的短整数(16 位整数)转换为网络字节顺序。

  • Host:主机字节顺序。不同的计算机体系结构可能使用不同的字节顺序来存储数据。常见的有两种:

    • 小端序 (Little Endian):最低有效字节存储在最低地址。
    • 大端序 (Big Endian):最高有效字节存储在最低地址。
  • To:表示转换的方向。

  • Network:网络字节顺序。互联网协议采用大端序来表示数据。

  • Short:短整数,指 16 位的整数类型。这个函数专门用于处理 16 位的整数。

示例
uint16_t port = 8080;
uint16_t net_port;

net_port = htons(port);
printf("Host order: %d, Network order: %d\n", port, net_port);

CTRL+Z - 暂停进程,用于将当前前台进程挂起(暂停),并将其放到后台。(还活着)

CTRL+C - 终止进程,用于强制终止当前前台进程。(死了)

下面演示的是一个服务端和2个客户端之间互相交流,服务端自动回复:

server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];

        int mark = 0;
        char msg[128] = {0};

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        if(argc != 3) {
                printf("param is not good\n");
                exit(-1);
        }

        memset(&s_addr, 0, sizeof(struct sockaddr_in));
        memset(&c_addr, 0, sizeof(struct sockaddr_in));

        s_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(s_fd == -1) {
                perror("socket");
                exit(-1);
        }

        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1], &s_addr.sin_addr);

        bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));

        listen(s_fd, 10);

        int clen = sizeof(struct sockaddr_in);
        while(1) {

                c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
                if(c_fd == -1) {
                        perror("accept");
                }

                mark++;
                printf("get connect: %s\n", inet_ntoa(c_addr.sin_addr));

                if(fork() == 0) {

                        if(fork() == 0) {
                                while(1) {
                                        sprintf(msg, "welcom No.%d client", mark);
                                        write(c_fd, msg, strlen(msg));
                                        sleep(3);
                                }
                        }

                        while(1) {
                                memset(readBuf, 0, sizeof(readBuf));
                                n_read = read(c_fd, readBuf, 128);
                                if(n_read == -1) {
                                        perror("read");
                                } else if(n_read > 0) {
                                        printf("\nget: %s\n", readBuf);
                                } else {
                                        printf("client quit\n");
                                        break;
                                }
                        }
                        break;
                }
        }
        return 0;
}

client.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
        int c_fd;
        int n_read;
        char readBuf[128];
        int tmp;

        char msg[128] = {0};
        struct sockaddr_in c_addr;
        struct sockaddr_in f_addr;
        memset(&c_addr, 0, sizeof(struct sockaddr_in));

        if(argc != 3) {
                printf("param is not good\n");
                exit(-1);
        }

        printf("%d\n", getpid());

        c_fd = socket(AF_INET, SOCK_STREAM, 0);
        if(c_fd == -1) {
                perror("socket");
                exit(-1);
        }

        f_addr.sin_family = AF_INET;
        f_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1], &f_addr.sin_addr);

        if(connect(c_fd, (struct sockaddr *)&f_addr, sizeof(struct sockaddr)) == -1) {
                perror("connect");
                exit(-1);
        }

        while(1) {
                if(fork() == 0) {
                        while(1) {
                                memset(msg, 0, sizeof(msg));
                                printf("input: ");
                                fgets(msg, sizeof(msg), stdin);
                                write(c_fd, msg, strlen(msg));
                        }
                }

                while(1) {
                        memset(readBuf, 0, sizeof(readBuf));
                        n_read = read(c_fd, readBuf, 128);
                        if(n_read == -1) {
                                perror("read");
                                exit(-1);
                        }else{
                                printf("\nget:%s\n", readBuf);
                        }
                }
        }


        return 0;
}

运行结果:下面是同一台虚拟机,再下面是虚拟机和手机。 

 

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

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

相关文章

seerfar丨OZON运营工具,OZON选品插件

随着全球电商市场的蓬勃发展&#xff0c;OZON作为俄罗斯及东欧地区的重要电商平台&#xff0c;吸引了众多中国商家的目光。然而&#xff0c;如何在OZON平台上脱颖而出&#xff0c;实现高效的商品运营&#xff0c;成为了众多商家亟待解决的问题。在这样的背景下&#xff0c;seer…

tailwindcss的@apply使用

tailwindcss的apply是把在html写的tailwindcss可以挪到style里面 简化页面的可读性 没写之前的 <section class"block-risk absolute flex flex-col items-center p-4 text-center left-0 text-white;" :style"{ top, left: 60px }"> </section…

【Week-R2】使用LSTM实现火灾预测(tf版本)

【Week-R2】使用LSTM实现火灾预测&#xff08;tf版本&#xff09; 一、 前期准备1.1 设置GPU1.2 导入数据1.3 数据可视化 二、数据预处理(构建数据集)2.1 设置x、y2.2 归一化2.3 划分数据集 三、模型创建、编译、训练、得到训练结果3.1 构建模型3.2 编译模型3.3 训练模型3.4 模…

移动安全赋能化工能源行业智慧转型

随着我国能源化工企业的不断发展&#xff0c;化工厂中经常存在火灾爆炸的危险&#xff0c;特别是生产场所&#xff0c;约有80%以上生产场所区域存在爆炸性物质。而目前我国化工危险场所移动通信设备的普及率高&#xff0c;但是对移动通信设备的安全防护却有所忽视&#xff0c;包…

国自然基金的检索

&#xff08;1&#xff09;网址 跳转国自然基金网址&#xff1a;https://www.nsfc.gov.cn/ &#xff08;2&#xff09;查询入口 &#xff08;3&#xff09;进行查询

antdv 穿梭框

antd的穿梭框的数据貌似只接收key和title&#xff0c;而且必须是字符串&#xff08;我测试不是字符串的不行&#xff09;&#xff0c; 所以要把后端返回的数据再处理一下得到我们想要的数据 除了实现简单的穿梭框功能&#xff0c;还想要重写搜索事件&#xff0c;想达到的效果是…

【96】write combine机制介绍

前言 这篇文章主要介绍了write combine的机制 一、write combine的试验 1.系统配置 &#xff08;1&#xff09;、CPU&#xff1a;11th Gen Intel(R) Core(TM) i7-11700 2.50GHz &#xff08;2&#xff09;、GPU&#xff1a;XX &#xff08;3&#xff09;、link status&am…

Towards Graph Contrastive Learning: A Survey and Beyond

目录 Towards Graph Contrastive Learning- A Survey and Beyond摘要IntroductionPRELIMINARY符号说明GNN对比学习下游任务 GCL自监督学习增强策略基于规则随机扰动或mask子图采样图扩散 基于学习图结构学习图对抗训练图合理化 对比模式同尺度对比全局上下文局部 跨尺度对比局部…

构建体育直播平台源码:深度解析数据分析模块的核心展示内容

在现代的体育直播平台中&#xff0c;数据分析展示已经成为不可或缺的一部分。如下参考借助“东莞梦幻网络科技”提供的体育直播源码&#xff0c;打造的平台&#xff0c;并通过表格形式为用户列出以下数据分析内容&#xff1a; 1、积分排名&#xff1a;反映了各支队伍在赛季中的…

HIK录像机GB28181对接相机不在线问题随笔

一、问题现象 【设备信息】型号&#xff1a;DS-8664N-I16-V3 V4.63.000 build 230412 【问题现象】HIK录像机使用GB28181对接异常相机无法正常上线&#xff0c;对接HIK相机可以正常上线。 【现场拓扑】现场拓扑如下 NVR侧使用固定公网IP地址。IPC侧使用家用宽带的方式&…

玩转ChatGPT:最全学术论文提示词分享【上】

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 在当今数字时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正迅速改变各行各业的运作方式。特别是&#xff0c;OpenAI的ChatGPT等语言模型以其强大的文本生成能力&#xff0c;…

从零开始实现自己的串口调试助手(8)-循环发送

循环发送 准备 创建槽函数 设置QSpinBox的最大值 注意&#xff1a; // 我们不能在qt的ui线程中延时&#xff0c;否则将导致页面刷新问题 //QThread::msleep(ui->spinBox->text().toInt());//设置下次发送时间间隔 定时器实现 关联信号与槽: //添加自动换行定…

C++学习/复习13--list概述

一、list概念 1.带头双向链表 2.构造函数 3.迭代器&#xff08;其迭代器需尤其注意&#xff09; 4、size 5.front/back 6.插入删除 删除时的迭代器失效 由于list的节点特殊&#xff0c;既有数据又有指针&#xff0c;其实现需要节点/迭代器/list各成一类再组合

UI框架与MVC模式详解(1)——逻辑与数据分离

【效率最高的耦合方式】 以实际的例子来说明&#xff0c;更容易理解些。 这里从上到下&#xff0c;从左到右共有8个显示项&#xff0c;如果只需要显示这8个&#xff0c;不会做任何改变&#xff0c;数据固定&#xff0c;那么我们只需要最常规的思路去写就好&#xff0c;这是最…

【小海实习日记】Git使用规范

1.Git使用流程 1.1 从master分支拉一个分支&#xff0c;命名要符合规范且清晰。 1.2 commit到本地&#xff0c;push 到远端。 1.3 在Gitlab创建MR&#xff0c;选择develp分支。 1.4 如果要修改的话&#xff0c;先把Gitlab上的MR修改为Draft(修改态)&#xff0c;然后在本地修改代…

SwiftUI获取用户的位置信息(CLLocationManager,CLLocationManagerDelegate)

本篇文章将会介绍一下在SwiftUI中如何通过CorLocation框架获取用户的位置信息&#xff0c;因为获取位置信息属于用户的隐私信息&#xff0c;所以需要在Info.plist文件里面加上访问位置权限的说明。 关于位置信息&#xff0c;可以请求两种级别的许可&#xff1a;always和when i…

图的创建和BFS,DFS遍历

图的创建 图是一种用于表示对象及其关系的抽象数据结构&#xff0c;由节点&#xff08;也称为顶点&#xff09;和连接节点的边组成。图可以分为有向图&#xff08;Directed Graph&#xff09;和无向图&#xff08;Undirected Graph&#xff09;&#xff0c;以及加权图&…

Redis的事务与关系型数据库事务有何不同?

引言&#xff1a;关于 Redis 的事务很多人可能都是一知半解&#xff0c;大多数人只了解数据库的事务&#xff0c;并且是单体事务&#xff0c;对于 Redis 事务和常见关系型数据库的事务的区别还没有去了解过&#xff0c;本文就来详细进行介绍。 题目 Redis的事务与关系型数据库…

git【工具软件】分布式版本控制工具软件

一、Git 的介绍 git软件的作用&#xff1a;管理软件开发项目中的源代码文件。 常用功能&#xff1a; 仓库管理、文件管理、分支管理、标签管理、远程操作 功能指令&#xff1a; add&#xff0c;commit&#xff0c;log&#xff0c;branch&#xff0c;tag&#xff0c;remote…

UE5 Mod Support 思路——纯蓝图

原创作者&#xff1a;Chatouille 核心功能 “Get Blueprint Assets”节点&#xff0c;用于加载未来的mod。用基础类BP_Base扩展即可。打包成补丁&#xff0c;放到Content\Paks目录下&#xff0c;即可让游戏访问到内容。 与文中所写不同的地方 5.1或者5.2开始&#xff0c;打…