【C语言】Linux socket 编程

一、Socket 通信过程

在 Linux 系统中,socket 是一种特殊的文件描述符,用于在网络中的不同主机间或者同一台主机中的不同进程间进行双向通信。它是通信链路的端点,可以看作是网络通信的接口。Socket 通信过程主要分为以下几个步骤:

1. 创建 Socket(socket()):

   首先需要调用 socket() 函数来创建一个 socket。这个函数会返回一个 socket 文件描述符,该描述符将用于其他所有的网络通信函数。创建 socket 时需要指定通信的类型(通常是 TCP 或 UDP)、通信协议(IP)和其他相关参数。

   int sockfd = socket(AF_INET, SOCK_STREAM, 0);

2. 绑定地址到 Socket(bind()):

   接下来,如果是服务器端,需要将一个地址(IP 地址和端口号)绑定到 socket 上。这样客户端就知道应该连接到哪个地址。

   struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));

3. 监听连接(listen()):

   对于服务器端应用程序来说,下一步是监听网络上的连接请求。这可以通过 listen() 函数实现。

   listen(sockfd, backlog);

4. 接受连接(accept()):

   当客户端请求连接时,服务器需要接受连接。采用 accept() 函数可以接受来自客户端的连接请求,并返回一个新的 socket 文件描述符,用于后续的通信。

   int new_socket = accept(sockfd, (struct sockaddr *)&addr, (socklen_t*)&addrlen);

5. 发起连接(connect()):

   对于客户端应用程序,使用 connect() 函数发起对远程服务器的连接请求。

  connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

6. 数据传输(send() / recv() 或 write() / read()):

   一旦连接建立,数据就可以在 socket 之间传输。可以使用 send() 和 recv() 函数,或者 write() 和 read() 函数来进行数据的发送和接收。

   send(sockfd, data, datalen, 0);recv(sockfd, buffer, buflen, 0);

7. 断开连接(close()):

   最后,当通信结束时,应当使用 close() 函数来关闭 socket,释放资源。

   close(sockfd);

这是一个高层次的概述,实际的实现可能会根据 TCP、UDP、IPv4、IPv6 等不同设置有所不同。此外,还有各种 flag 和选项可以设置,比如非阻塞模式、超时设置等,都会影响 socket 的行为。在多线程或多进程的服务器中,还会用到一些额外的技术来高效地处理多个连接。

二、详解创建 Socket(socket())

创建 socket 是网络编程中的第一步,它是网络通信过程的起点。在 Linux 中,我们使用 socket() 系统调用来创建一个 socket 描述符。创建 socket 实际上是在请求操作系统开启网络 I/O 端口。

socket() 函数的原型

定义在头文件 <sys/types.h> 和 <sys/socket.h> 中,函数定义如下:

#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);

函数参数详解:

- domain: 指定 socket 所使用的协议族 (例如 AF_INET 用于 IPv4, AF_INET6 用于 IPv6, AF_UNIX 用于本地通信)。
- type: 指定 socket 类型,常见的有 SOCK_STREAM(流式socket,通常用于 TCP)和 SOCK_DGRAM(数据报式socket,通常用于 UDP)。

- protocol: 指定在 domain 和 type 的基础上的具体协议。通常设为 0 表示选择默认协议 (例如,在 AF_INET 下使用 SOCK_STREAM 则默认使用 TCP 协议)。

调用 socket() 函数后,它会返回一个整数值 —— socket 文件描述符。如果成功创建,返回的描述符将作为后续所有基于该 socket 的操作的句柄。如果创建失败,返回 -1,并能通过 errno 获取具体的失败原因。

创建 TCP socket 的简单例子(IPv4):

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h> // for closeint main() {int sockfd;// 创建 socketsockfd = socket(AF_INET, SOCK_STREAM, 0);// 检查是否成功创建if (sockfd == -1) {perror("Failed to create socket");exit(EXIT_FAILURE);}printf("Socket created successfully with fd: %d\n", sockfd);// 使用结束后关闭 socketclose(sockfd);return 0;
}

在这个例子中,我们创建了一个用于 IPv4 网络通信的流式 socket(即 TCP socket)。创建完后,我们检查了返回的文件描述符,看是否创建成功。如果创建不成功,使用 perror 打印出错误信息。创建成功后,可以使用返回的文件描述符来配置 socket,例如通过 bind、`listen`、`accept`、`connect` 等系统调用进行进一步的网络操作。使用完毕后,用 close() 系统调用关闭文件描述符,清理资源。

三、详解绑定地址到 Socket(bind())

函数 bind() 在 socket 编程中的作用是将一个本地地址绑定到指定的 socket 文件描述符上。对于 TCP 服务器来说,这一步骤是必需的,因为它定义了服务的地址和端口,客户端需要这些信息来能够连接到服务器。对于 UDP 协议,`bind()` 用于指定一个端口用于监听传入的数据报。

下面是 bind() 函数的一般用法和详细解释:

原型

在 <sys/socket.h> 头文件中,`bind()` 函数的原型如下:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:socket 文件描述符,由之前调用 socket() 函数返回。
addr:指向 sockaddr 结构体或其子类型(如 sockaddr_in 用于 IPv4 地址)的指针,持有你想要绑定给 socket 的地址信息。

addrlen:`addr` 指针指向的地址的字节长度。

参数

关于参数的详细解释如下:

1. sockfd:这是调用 socket() 函数时得到的 socket 描述符,它代表一个 socket 实例。

2. addr:这是一个指向 sockaddr 结构体的指针,它定义了要绑定的地址包括 IP 地址和端口号。在实际编程中,通常使用特定于协议的地址结构体(如 IPv4 的 sockaddr_in),并且在传递给 bind() 之前转换为 sockaddr 类型的指针。

3. addrlen:这表示 addr 结构体的大小,确保 bind() 函数可以正确地读取或解释地址结构体中的信息。

地址结构体(IPv4 为例)

对于 IPv4 地址,`sockaddr_in` 结构体的定义如下:

struct sockaddr_in {short            sin_family;   // 地址族(如 AF_INET)unsigned short   sin_port;     // 端口号(使用网络字节顺序)struct in_addr   sin_addr;     // IP 地址char             sin_zero[8];  // 填充 0 以保持结构体大小与 sockaddr 一致
};

在使用时,你通常会填充 sockaddr_in 结构体,然后将其转换为 sockaddr 结构类型的指针传递给 bind() 函数。

使用示例

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>int main() {// 创建 socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);// 创建 sockaddr_in 结构体来指定地址和端口struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));  // 初始化结构体addr.sin_family = AF_INET;       // 指定地址族addr.sin_port = htons(8080);     // 指定端口号addr.sin_addr.s_addr = INADDR_ANY; // 使用任意可用地址或指定具体 IP// 绑定 socket 到地址和端口if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {// 错误处理close(sockfd);return -1;}// 其他操作...// 关闭 socketclose(sockfd);return 0;
}

在上面的代码中,`sockfd` 是一个有效的 socket 描述符,`addr` 结构体填充了所需的 IP 地址和端口信息,然后通过 bind() 函数与 sockfd 绑定。常量 INADDR_ANY 用于绑定到所有可用的接口上,端口号由 htons() 函数转换为网络字节顺序。

如果 bind() 调用成功,则返回 0;如果失败,则返回 -1 并设置全局变量 errno 以指示错误。常见的错误代码包括 EACCES(表示没有权限绑定指定端口)、`EADDRINUSE`(指定的地址或端口已在使用中)、`EBADF`(非法的文件描述符)等。

四、详解监听连接(listen())

listen() 函数在 Socket 编程中用于将一个未连接的 socket 转换成一个被动的监听 socket,指示内核应当接受指向该 socket 的连接请求。这个函数特别适用于服务器端的 socket,在客户端尝试建立连接之前,服务器需要明确准备好接受连接。

 listen() 函数的原型:

int listen(int sockfd, int backlog);

参数说明:

sockfd:这是 socket() 函数调用成功后返回的文件描述符。它代表一个打开的 socket,服务器将通过该 socket 接收客户端的连接请求。

backlog:这个参数指定了内核中未处理连接请求的最大数量。具体来说,这个参数定义了内核应该为相应 socket 队列的最大长度。当队列满时,会拒绝新的连接请求。不同的系统对这个数值的限制不同,当你设置的值超过系统最大值时,系统会将其调整为最大值。

函数行为:

- 当 listen() 调用成功时,这个 socket 将可以接收连接请求,通常紧接着会使用 accept() 函数来等待和响应实际的连接请求。

- 当 listen() 调用失败时,它会返回 -1,并设置全局变量 errno 来指明错误原因。

在实际编程中,你通常需要先通过 socket() 创建一个 socket,再通过 bind() 函数将其绑定到本地地址和端口,最后使用 listen() 函数来开始监听连接请求。

下面是这个步骤的示例代码:

#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd;struct sockaddr_in addr;// 创建 socketsockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {// 错误处理}// 初始化地址结构体addr.sin_family = AF_INET;         // 使用 IPv4addr.sin_port = htons(8080);       // 设置端口号 (e.g., 8080)addr.sin_addr.s_addr = INADDR_ANY; // 监听所有地址// 绑定 socket 到指定的地址和端口if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {// 错误处理}// 开始监听连接请求if (listen(sockfd, 10) == -1) { // 设置 backlog 为 10// 错误处理}// ... 接下来可以接受连接和处理数据return 0;
}

当服务器调用 listen() 之后,操作系统会处理所有到来的网络连接请求。如果接受请求的队列被填满,则新的连接请求可能会被忽略或者被拒绝,这取决于操作系统如何处理这种情况。通常,服务器会在多线程或者异步的环境下运行,来高效地处理多个客户端的连接。

五、详解接受连接(accept())

accept() 函数在服务器端用于接受客户机的连接请求。在调用 accept() 之前,服务器应该已经使用 socket() 创建好一个套接字(socket),然后使用 bind() 将其绑定到一个本地地址和端口上,最后用 listen() 设置此套接字为监听模式,准备接收客户端发起的连接请求。

函数原型如下:

#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:这是调用 socket() 函数时返回的套接字文件描述符,并且已经通过 bind() 和 listen() 函数绑定地址并且监听。
addr:这是一个指向 sockaddr 结构的指针,该结构用于返回连接方的协议地址(即客户端的地址)。在实际应用中,这通常指向一个 sockaddr_in 或 sockaddr_in6 结构(根据是 IPv4 还是 IPv6)。

addrlen:这是一个指向 socklen_t 类型变量的指针,在调用 accept() 之前,`*addrlen` 应该被设置为 addr 指向结构的大小。函数返回后,`*addrlen` 被设置为实际的地址长度。

调用 accept() 函数时,如果存在待处理的连接请求,它会创建一个新的已连接套接字,并从队列中移除该请求。`accept()` 返回一个新的文件描述符来指代这个连接。这个新的文件描述符完全独立于原来监听的 sockfd,应该用于后续的数据发送和接收操作。原来的 sockfd 仍然保持打开着,可以继续用于接受其他连接请求。

如果 addr 和 addrlen 非空,`accept()` 会在 addr 指向的结构中填充连接客户端的地址和端口信息,而 addrlen 会被设置为该结构体的实际长度。如果你对客户端的地址不感兴趣,可以将这两个参数设置为 NULL

在默认情况下,`accept()` 是阻塞的,这意味着如果没有客户端连接请求,调用 accept() 的程序将会挂起,直到有一个连接请求到来。如果想要 accept() 在没有连接请求时不阻塞程序,可以将监听套接字设置为非阻塞模式。

还要注意的是,`accept()` 可以在多线程或者多进程的服务器中被调用,以便同时处理多个连接请求。此时,必须确保对于每个连接都会有一个独立的线程或进程负责处理。

当 accept() 成功时,会返回一个非负的文件描述符用于操作这个连接。如果出错,则返回 -1,并设置 errno 以指示错误类型。

六、详解发起连接(connect())

connect() 函数在客户端用于建立与服务器端的连接。连接建立后,客户端和服务器就可以开始通信。该函数定义在 <sys/types.h> 和 <sys/socket.h> 头文件中,通常用于 SOCK_STREAM 类型的 socket(如 TCP 连接)。

函数原型:

#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

- sockfd: 调用 socket() 函数时返回的文件描述符,代表了客户端用于尝试连接的本地 socket。

- addr: 指向 struct sockaddr 结构体的指针,该结构体包含了目标服务器的地址和端口信息。对于不同的地址类型(如 IPv4、IPv6),该结构体有不同的具体实现,分别为 struct sockaddr_in 和 struct sockaddr_in6

- addrlen: addr 结构体的长度,用字节为单位。

函数返回值:

- 返回 0,表示连接成功。

- 返回 -1,表示连接失败,并设置 errno 以指示错误类型。

实际使用中,需要先准备好服务器的地址信息:

struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;           // AF_INET 表示 IPv4
server_addr.sin_port = htons(port);         // 设置服务器端口,并转化为网络字节序
server_addr.sin_addr.s_addr = inet_addr("服务器 IP 地址"); // 将 IP 地址转化为网络字节序

之后,可以调用 connect() 函数尝试建立连接:

int status = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (status == -1) {perror("connect failed");// 错误处理// 根据 errno 的不同值处理不同的错误情况
}

当 connect() 函数调用成功端,此时 socket 已经与服务器建立了 TCP 连接,可以通过 read/write 或 send/recv 函数进行数据传输。

在阻塞模式下(默认情况),如果连接无法立即建立,`connect()` 会阻塞当前线程直到连接完成,或者发生错误(如超时)。可以通过将 socket 设置为非阻塞来改变这个行为。

如果是非阻塞 socket 的话,`connect()` 函数调用可能立即返回 -1,并且 errno 设置为 EINPROGRESS,表示连接正在进行中。此时,你可以使用 select()、`poll()` 或 epoll() 等函数来检测连接是否成功,或者使用 getsockopt() 来检查 socket 的 SO_ERROR 选项来获取实际的错误代码。

七、详解数据传输(send() / recv() 或 write() / read())

数据传输是 Socket 编程中数据在建立的连接上进行读取和发送的过程。在 TCP 套接字中,这个过程是可靠的,保证数据按序到达;在 UDP 套接字中,则是不可靠的,数据可能会丢失或者顺序被改变。下面详细介绍 TCP 套接字的数据传输函数。

TCP 数据传输

对于 TCP 协议(`SOCK_STREAM`)的套接字,`send()` 和 recv() 函数经常被用来发送和接收数据。

1. send() 函数:

   send() 函数用于向 TCP 连接的另一端发送数据。

   ssize_t send(int sockfd, const void *buf, size_t len, int flags);

   - sockfd 是要发送数据的套接字文件描述符。
   - buf 是指向要发送数据的缓冲区的指针。
   - len 是要发送数据的字节数。

   - flags 通常设置为0;其它可选值可以提供额外的控制,如 MSG_DONTROUTE

   这个函数返回值是实际发送的字节数,或者在出错时返回-1。

2. recv() 函数:

   recv() 函数用于从 TCP 连接的另一端接收数据。

   ssize_t recv(int sockfd, void *buf, size_t len, int flags);

   - sockfd 是要接收数据的套接字文件描述符。
   - buf 是指向用于接收数据的缓冲区的指针。
   - len 是缓冲区的大小,即最大可接收的字节数。

   - flags 同样通常设置为0,提供特定控制如 MSG_PEEK(预览数据而不移除队列中的数据)等。

   recv() 返回实际接收到的字节数,如果连接已经正常关闭,则返回0;出错时返回-1。

UDP 数据传输

虽然 send() 和 recv() 函数同样可以用于 UDP 协议(`SOCK_DGRAM`)的套接字,但是对于 UDP 更常用的是 sendto() 和 recvfrom() 函数,因为 UDP 具有连接无关的特征,没有固定的连接状态。

1. sendto() 函数:用于发送数据到特定的地址。
   ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

2. recvfrom() 函数:用于从特定地址接收数据。
   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

write() 和 read() 函数

write() 和 read() 是更一般的 POSIX 文件操作函数,也可以用于 socket 数据传输,这是因为在Unix和类Unix系统中,一切皆文件。

1. write() 函数:

用于向文件描述符写入数据,它可以用于 TCP socket 和文件的写操作。

   ssize_t write(int fd, const void *buf, size_t count);

2. read() 函数:

用于从文件描述符读取数据,同样适用于 TCP socket 和读取普通文件。

   ssize_t read(int fd, void *buf, size_t count);

在处理socket传输时,一个重要的实践是适当的错误处理和重试机制。网络传输可能会因为各种原因导致发送和接收不完全,因此编写健壮的代码需要处理这些问题,如使用循环确保所有数据都发送/接收成功,或处理丢包和超时情况。

八、详解断开连接(close())

在 Linux socket 编程中,`close()` 函数用于关闭一个已经打开的 socket 连接。当你完成了数据的发送和接收,不再需要这个 socket 时,应当关闭它以释放系统资源。

在 TCP 连接中,`close()` 函数的执行将启动 TCP 连接的终止过程,通常称为四次挥手(four-way handshake)。详细来说,`close()` 会使得执行了关闭操作的一方(通常是客户端或服务器端的应用程序)发送一个 FIN(结束)信号,以通知另一方它已经完成了发送数据。

四次挥手的过程如下:

1. 第一次挥手:关闭操作的一方发送一个 FIN 包给对方,表明它已经没有数据要发送了。

2. 第二次挥手:接收到 FIN 包的一方会发送一个 ACK (确认) 包作为响应。

3. 第三次挥手:接收 FIN 的一方在完成它自己的数据发送后,会发送一个自己的 FIN 包。

4. 第四次挥手:最初发送 FIN 的原始关闭方收到这个 FIN 包后,发送一个 ACK 包作为最终确认,然后关闭这个连接。

一旦这个过程完成,连接就被完全关闭了。然而,对于原始调用 close() 函数的一方来说,这个函数通常会立即返回,不会等待整个四次挥手过程结束。

另外,需要注意以下几点:

- 半关闭(Half-close):TCP 提供的是全双工的服务,意味着数据可以在两个方向上同时传输。调用 close() 表示应用层不再发送数据,但是依然可以接收数据直到收到对方的 FIN 包。

- TIME_WAIT 状态:在 TCP 连接完全关闭后,关闭端口的一方(通常是发起连接的客户端)会进入 TIME_WAIT 状态。在这个状态下,端口需要等待一段时间(通常是 2MSL,其中 MSL 是最大报文生存时间)以确保对方收到了最后一个 ACK 包。这个机制确保了在相同的端口上快速重用连接是安全的,并且处理了可能在网络中延迟的旧数据包。

- 引用计数:在某些系统中,当多个进程或线程共享同一个 socket 文件描述符时,这个描述符会有一个引用计数。在所有拥有这个文件描述符的进程或线程中,`close()` 需要被调用相应次数才能真正关闭连接。

- shutdown() 函数:此外,还有一个 shutdown() 函数可以用来关闭连接。与 close() 不同的是,`shutdown()` 允许你执行部分关闭,即关闭读和/或写的一部分。这对于实现半关闭非常有用。

关闭 socket 连接时还需要处理可能产生的异常和错误,比如网络中断或对端突然关闭连接等情况。正确地管理 socket 的关闭操作对于保持应用程序和系统稳定性是非常重要的。


 

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

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

相关文章

RocketMQ5.0顺序消息设计实现

前言 顺序消息是 RocketMQ 提供的一种高级消息类型&#xff0c;支持消费者按照发送消息的先后顺序获取消息&#xff0c;从而实现业务场景中的顺序处理。 顺序消息的顺序关系通过消息组&#xff08;MessageGroup&#xff09;判定和识别&#xff0c;发送顺序消息时需要为每条消息…

RocketMQ5.0Pop消费模式

前言 RocketMQ 5.0 消费者引入了一种新的消费模式&#xff1a;Pop 消费模式&#xff0c;目的是解决 Push 消费模式的一些痛点。 RocketMQ 4.x 之前&#xff0c;消费模式分为两种&#xff1a; Pull&#xff1a;拉模式&#xff0c;消费者自行拉取消息、上报消费结果Push&#x…

C++字符串操作

1. 字符串比较 strcmp 1. 字符串比较 strcmp 头文件 string.h&#xff1b;变量需要传指针&#xff1b;返回>0 则第一个字符串比第二个字符串大&#xff0c;反之则小&#xff0c;0则表示两个字符串相同。 # include <iostream> # include <stdio.h> # include …

面试 Vue 框架八股文十问十答第一期

面试 Vue 框架八股文十问十答第一期 作者&#xff1a;程序员小白条&#xff0c;个人博客 相信看了本文后&#xff0c;对你的面试是有一定帮助的&#xff01;关注专栏后就能收到持续更新&#xff01; ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 1&#xff09;MVVM 的理解 MVVM (Mod…

LDD学习笔记 -- 用户空间 内核空间

LDD学习笔记 -- 用户空间 & 内核空间 内核空间用户空间用户空间和内核空间交互 操作系统上下文中User space和Kernel space概念 用户空间&#xff1a;Restricted Mode&#xff0c;用户级编程内核空间&#xff1a;Privileged Mode&#xff0c;内核级代码&#xff08;linux …

深入理解Vue生命周期钩子及其应用

Vue.js其独有的生命周期系统允许我们在组件的不同阶段执行自定义代码。在本文中&#xff0c;我们将深入探讨一个简单的Vue组件&#xff0c;通过观察其生命周期钩子的执行顺序&#xff0c;以及如何在特定时刻插入自己的逻辑。 Vue组件代码 <template><div><p&g…

华为OD机试 - 抢7游戏(Java JS Python C)

题目描述 A、B两个人玩抢7游戏,游戏规则为: A先报一个起始数字 X(10 ≤ 起始数字 ≤ 10000),B报下一个数字 Y (X - Y < 3),A再报一个数字 Z(Y - Z < 3),以此类推,直到其中一个抢到7,抢到7即为胜者; 在B赢得比赛的情况下,一共有多少种组合? 输入描述 …

分布式(8)

目录 36.什么是TCC&#xff1f; 37.分布式系统中常用的缓存方案有哪些&#xff1f; 38.分布式系统缓存的更新模式&#xff1f; 39.分布式缓存的淘汰策略&#xff1f; 40.Java中定时任务有哪些&#xff1f;如何演化的&#xff1f; 36.什么是TCC&#xff1f; TCC&#xff08…

Spring 事务实现

Spring 事务实现 Spring 事务使用 Transactional注解配置项事务传播行为PROPAGATION_REQUIRED当前方法必须在事务中&#xff0c;没有就创建&#xff0c;有就加入。PROPAGATION_SUPPORTS有事务就加入&#xff0c;没有就以非事务方式执行。PROPAGATION_MANDATORY有事务就加入&a…

【算法挨揍日记】day41——【模板】01背包、416. 分割等和子集

【模板】01背包_牛客题霸_牛客网你有一个背包&#xff0c;最多能容纳的体积是V。 现在有n个物品&#xff0c;第i个物品的体积为 ,。题目来自【牛客题霸】https://www.nowcoder.com/practice/fd55637d3f24484e96dad9e992d3f62e?tpId230&tqId2032484&ru/exam/oj&qru…

Flutter基础

一、关键字 class&#xff1a;用于定义一个新的类&#xff1b; extends: 用于指定一个类继承另一个类&#xff1b; mixin: 用于将一个类的代码片段添加到另一个类中&#xff0c;实现代码复用&#xff1b; abstract: 用于声明一个抽象类或抽象方法&#xff0c;不能直接实例化&a…

C++ map容器

通俗一点讲map其实就是python的字典(学会python字典 c_map)!!! map和unordered_map都是C中的关联容器&#xff0c;用于存储键值对。其主要区别在于底层实现方式和性能表现。 1、底层实现方式   map内部使用红黑树&#xff08;一种自平衡二叉查找树&#xff09;来实现&…

HarmonyOS-ArkTS基本语法及声明式UI描述

初识ArkTS语言 ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集。因此&#xff0c;在学习ArkTS语言之前&#xff0c;建议开发者具备TS语…

机器学习常用算法模型总结

文章目录 1.基础篇&#xff1a;了解机器学习1.1 什么是机器学习1.2 机器学习的场景1.2.1 模式识别1.2.2 数据挖掘1.2.3 统计学习1.2.4 自然语言处理1.2.5 计算机视觉1.2.6 语音识别 1.3 机器学习与深度学习1.4 机器学习和人工智能1.5 机器学习的数学基础特征值和特征向量的定义…

Shell 文本处理常用命令

1、Sed sed 即 Stream EDitor&#xff0c;和 vi 不同&#xff0c;sed是基于行的文本编辑器。 Sed是从文件或管道中读取一行&#xff0c;处理一行&#xff0c;输出一行&#xff1b;再读取一行&#xff0c;再处理一行&#xff0c;再输出一行&#xff0c;直到最后一行。 # 查看文…

软件测试/测试开发丨Python 模块与包

python 模块与包 python 模块 项目目录结构 组成 package包module模块function方法 模块定义 定义 包含python定义和语句的文件.py文件作为脚本运行 导入模块 import 模块名from <模块名> import <方法 | 变量 | 类>from <模块名> import * 注意&a…

小红书如何高效引流?

近年来&#xff0c;公域流量价格不断上涨&#xff0c;私域流量的优势逐渐凸显。企业正花费大量资源和成本来获取新流量&#xff0c;但与其如此&#xff0c;不如将精力放在留存和复购上&#xff0c;从而实现业绩的新增长。其中关键在于如何有效地将公域流量转化为私域流量。 然而…

c++期末考题笔试来咯

最后一道大题题目再现 写一个person类&#xff0c;有姓名&#xff0c;性别&#xff0c;年龄。然后在此基础上派生出教师类和学生类。教师类增加了以下数据&#xff1a;工号&#xff0c;职称&#xff0c;工资。学生类增加了以下数据成员&#xff1a;学号&#xff0c;专业&#…

JVM调优相关参数学习

Xms 是指设定程序启动时占用内存大小。一般来讲&#xff0c;大点&#xff0c;程序会启动的快一点&#xff0c;但是也可能会导致机器暂时间变慢。 Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存&#xff0c;超出了这个设置值&#xff0c;就会抛…

境内深度合成服务算法备案清单(2023年12月)

截止2024年1月3日&#xff0c;第三批深度合成服务算法备案信息的公告尚未发布&#xff0c;预计将会在2024-1-10左右发布&#xff0c;我公司已知晓部分公示名单&#xff0c;如中国电信数字人生成算法&#xff0c;详情联系WX号&#xff1a;SuanfabeiandayuAI生成合成类算法应办理…