《Linux 网络架构:基于 TCP 协议的多人聊天系统搭建详解》

一、系统概述

        本系统是一个基于 TCP 协议的多人聊天系统,由一个服务器和多个客户端组成。客户端可以连接到服务器,向服务器发送消息,服务器接收到消息后将其转发给其他客户端,实现多人之间的实时聊天。系统使用 C 语言编写,利用了 Unix 系统的网络编程接口和多线程、I/O 多路复用等技术。

二、文件结构

  • server.c:服务器端程序,负责监听客户端连接、接收客户端消息并将消息转发给其他客户端。
  • client1.c:客户端程序,使用 poll 函数实现 I/O 多路复用,同时处理用户输入和服务器消息。
  • client2.c:客户端程序,使用多线程技术,一个线程负责接收服务器消息,另一个线程负责处理用户输入。

三、代码详细分析

1. 数据包结构体

        在三个文件中都定义了相同的数据包结构体 Packet,用于在客户端和服务器之间传输数据。

typedef struct {int type;  // 0 for message, 1 for disconnectchar data[BUFFER_SIZE];
} Packet;
  • type:数据包类型,0 表示消息,1 表示断开连接。
  • data:数据包携带的数据,最大长度为 BUFFER_SIZE

2. 服务器端程序(server.c)

2.1 主要变量

  • server_fd:服务器套接字文件描述符。
  • client_fd:客户端套接字文件描述符。
  • max_fd:记录最大的文件描述符,用于 select 函数。
  • activity:记录 select 函数返回的活动文件描述符数量。
  • valread:记录从客户端读取的数据长度。
  • server_addr:存储服务器的地址信息。
  • client_addr:存储客户端的地址信息。
  • client_sockets:数组用于存储所有客户端的套接字文件描述符。
  • readfds:文件描述符集合,用于 select 函数监听可读事件。

2.2 主要步骤

  1. 创建套接字:使用 socket 函数创建一个 TCP 套接字。
  2. 绑定地址:使用 bind 函数将套接字绑定到指定的地址和端口。
  3. 监听连接:使用 listen 函数开始监听客户端连接。
  4. 循环处理:使用 select 函数监听服务器套接字和客户端套接字的可读事件。
    • 若服务器套接字有可读事件,说明有新的客户端连接请求,使用 accept 函数接受连接。
    • 若客户端套接字有可读事件,从客户端读取数据包,根据数据包类型进行相应处理。
      • 若数据包类型为消息,将消息转发给其他客户端。
      • 若数据包类型为断开连接或读取到的数据长度为 0,说明客户端断开连接,关闭客户端套接字。

3. 客户端程序(client1.c)

3.1 主要变量

  • client_fd:客户端套接字文件描述符。
  • server_addr:存储服务器的地址信息。
  • packet:用于存储要发送或接收的数据包。
  • fds:数组用于存储要监听的文件描述符及其事件。

3.2 主要步骤

  1. 创建套接字:使用 socket 函数创建一个 TCP 套接字。
  2. 连接服务器:使用 connect 函数连接到服务器。
  3. 初始化 poll 结构体:监听标准输入和客户端套接字的可读事件。
  4. 循环处理:使用 poll 函数监听文件描述符集合中的可读事件。
    • 若标准输入有可读事件,从标准输入读取数据,设置数据包类型为消息,发送给服务器。
    • 若客户端套接字有可读事件,从服务器读取数据包,根据数据包类型进行相应处理。
      • 若数据包类型为消息,输出接收到的消息。
      • 若数据包类型为断开连接或读取数据失败,说明服务器断开连接,关闭客户端套接字,跳出循环。

4. 客户端程序(client2.c)

4.1 主要变量

  • client_fd:客户端套接字文件描述符。
  • server_addr:存储服务器的地址信息。
  • packet:用于存储要发送或接收的数据包。
  • thread_id:存储线程的标识符。

4.2 主要步骤

  1. 创建套接字:使用 socket 函数创建一个 TCP 套接字。
  2. 连接服务器:使用 connect 函数连接到服务器。
  3. 创建线程:创建一个线程来接收服务器发送的消息。
  4. 循环处理:在主线程中,从标准输入读取数据,设置数据包类型为消息,发送给服务器。
  5. 线程函数:在子线程中,持续接收服务器消息,根据数据包类型进行相应处理。
    • 若数据包类型为消息,输出接收到的消息。
    • 若数据包类型为断开连接或读取数据失败,说明服务器断开连接,关闭客户端套接字,退出程序。

四、编译和运行

4.1 编译

gcc server.c -o server
gcc client1.c -o client1
gcc client2.c -o client2 -lpthread

4.2 运行

  1. 启动服务器:
./server
  1. 启动客户端:
./client1
./client2

4.3运行结果展示

五、源码

5.1服务器端程序(server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>// 定义服务器监听的端口号
#define PORT 8080
// 定义数据缓冲区的大小
#define BUFFER_SIZE 1024
// 定义服务器允许的最大客户端连接数
#define MAX_CLIENTS 10/*** 定义数据包结构体,用于在服务器和客户端之间传输数据* type 数据包类型,0 表示消息,1 表示断开连接* data 数据包携带的数据*/
typedef struct {int type;  // 0 for message, 1 for disconnectchar data[BUFFER_SIZE];
} Packet;/*** 主函数,服务器程序的入口点* @return 程序的退出状态码,0 表示正常退出*/
int main() {// server_fd 为服务器套接字文件描述符,client_fd 为客户端套接字文件描述符// max_fd 记录最大的文件描述符,用于 select 函数// activity 记录 select 函数返回的活动文件描述符数量// valread 记录从客户端读取的数据长度int server_fd, client_fd, max_fd, activity, valread;// server_addr 存储服务器的地址信息,client_addr 存储客户端的地址信息struct sockaddr_in server_addr, client_addr;// client_len 存储客户端地址结构体的长度socklen_t client_len = sizeof(client_addr);// packet 用于存储从客户端接收的数据包Packet packet;// client_sockets 数组用于存储所有客户端的套接字文件描述符int client_sockets[MAX_CLIENTS] = {0};// readfds 是一个文件描述符集合,用于 select 函数监听可读事件fd_set readfds;// 创建服务器套接字,使用 IPv4 地址族和 TCP 协议if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {// 若套接字创建失败,输出错误信息并退出程序perror("socket failed");exit(EXIT_FAILURE);}// 设置服务器地址信息server_addr.sin_family = AF_INET;// 监听所有可用的网络接口server_addr.sin_addr.s_addr = INADDR_ANY;// 将端口号从主机字节序转换为网络字节序server_addr.sin_port = htons(PORT);// 将服务器套接字绑定到指定的地址和端口if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {// 若绑定失败,输出错误信息,关闭套接字并退出程序perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 开始监听客户端连接,允许的最大连接请求队列长度为 3if (listen(server_fd, 3) < 0) {// 若监听失败,输出错误信息,关闭套接字并退出程序perror("listen");close(server_fd);exit(EXIT_FAILURE);}// 输出服务器启动信息,显示监听的端口号printf("Server started on port %d\n", PORT);// 进入无限循环,持续处理客户端连接和数据while (1) {// 清空文件描述符集合FD_ZERO(&readfds);// 将服务器套接字添加到文件描述符集合中,监听其可读事件FD_SET(server_fd, &readfds);// 初始化最大文件描述符为服务器套接字文件描述符max_fd = server_fd;// 遍历客户端套接字数组for (int i = 0; i < MAX_CLIENTS; i++) {// 获取当前客户端的套接字文件描述符int sd = client_sockets[i];if (sd > 0) {// 若该客户端套接字有效,将其添加到文件描述符集合中FD_SET(sd, &readfds);}if (sd > max_fd) {// 更新最大文件描述符max_fd = sd;}}// 调用 select 函数监听文件描述符集合中的可读事件,无超时时间activity = select(max_fd + 1, &readfds, NULL, NULL, NULL);if ((activity < 0) && (errno != EINTR)) {// 若 select 函数调用失败且不是被信号中断,输出错误信息perror("select error");}if (FD_ISSET(server_fd, &readfds)) {// 若服务器套接字有可读事件,说明有新的客户端连接请求if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len)) < 0) {// 若接受连接失败,输出错误信息并继续循环perror("accept");continue;}// 输出新客户端连接的信息,包括套接字文件描述符、IP 地址和端口号printf("New connection, socket fd is %d, ip is : %s, port : %d\n",client_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 遍历客户端套接字数组,找到一个空闲位置存储新客户端的套接字文件描述符for (int i = 0; i < MAX_CLIENTS; i++) {if (client_sockets[i] == 0) {client_sockets[i] = client_fd;break;}}}// 遍历客户端套接字数组,检查每个客户端是否有可读事件for (int i = 0; i < MAX_CLIENTS; i++) {int sd = client_sockets[i];if (FD_ISSET(sd, &readfds)) {// 从客户端读取数据包valread = read(sd, &packet, sizeof(Packet));if (valread == 0) {// 若读取到的数据长度为 0,说明客户端断开连接getpeername(sd, (struct sockaddr*)&client_addr, &client_len);// 输出客户端断开连接的信息,包括 IP 地址和端口号printf("Host disconnected, ip %s, port %d\n",inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 关闭客户端套接字close(sd);// 将该位置的客户端套接字文件描述符置为 0,表示空闲client_sockets[i] = 0;} else {if (packet.type == 0) {// 若数据包类型为消息,输出接收到的消息printf("Received from client %d: %s", sd, packet.data);// 将消息转发给其他客户端for (int j = 0; j < MAX_CLIENTS; j++) {if (client_sockets[j] != sd && client_sockets[j] != 0) {// 发送数据包给其他客户端send(client_sockets[j], &packet, sizeof(Packet), 0);}}} else if (packet.type == 1) {// 若数据包类型为断开连接,输出客户端断开连接的信息printf("Client %d disconnected\n", sd);// 关闭客户端套接字close(sd);// 将该位置的客户端套接字文件描述符置为 0,表示空闲client_sockets[i] = 0;}}}}}return 0;
}

5.2客户端程序(client1.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>// 定义服务器监听的端口号
#define PORT 8080
// 定义数据缓冲区的大小
#define BUFFER_SIZE 1024/*** 定义数据包结构体,用于在客户端和服务器之间传输数据* type 数据包类型,0 表示消息,1 表示断开连接* data 数据包携带的数据*/
typedef struct {int type;  // 0 for message, 1 for disconnectchar data[BUFFER_SIZE];
} Packet;/*** 主函数,客户端程序的入口点* @return 程序的退出状态码,0 表示正常退出*/
int main() {// client_fd 为客户端套接字文件描述符int client_fd;// server_addr 存储服务器的地址信息struct sockaddr_in server_addr;// packet 用于存储要发送或接收的数据包Packet packet;// fds 数组用于存储要监听的文件描述符及其事件struct pollfd fds[2];// 创建客户端套接字,使用 IPv4 地址族和 TCP 协议if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {// 若套接字创建失败,输出错误信息并退出程序perror("socket failed");exit(EXIT_FAILURE);}// 设置服务器地址信息server_addr.sin_family = AF_INET;// 将端口号从主机字节序转换为网络字节序server_addr.sin_port = htons(PORT);// 将服务器的 IP 地址转换为网络字节序server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");// 连接到服务器if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {// 若连接失败,输出错误信息,关闭套接字并退出程序perror("connect");close(client_fd);exit(EXIT_FAILURE);}// 输出连接成功的信息printf("Connected to server\n");// 初始化 poll 结构体// 监听标准输入的可读事件fds[0].fd = STDIN_FILENO;fds[0].events = POLLIN;// 监听客户端套接字的可读事件fds[1].fd = client_fd;fds[1].events = POLLIN;// 进入无限循环,持续处理输入和服务器消息while (1) {// 调用 poll 函数监听文件描述符集合中的可读事件,无超时时间int activity = poll(fds, 2, -1);if ((activity < 0) && (errno != EINTR)) {// 若 poll 函数调用失败且不是被信号中断,输出错误信息perror("poll error");}if (fds[0].revents & POLLIN) {// 若标准输入有可读事件// 清空数据包的数据部分memset(packet.data, 0, BUFFER_SIZE);if (fgets(packet.data, BUFFER_SIZE, stdin) != NULL) {// 若成功从标准输入读取数据// 设置数据包类型为消息packet.type = 0;// 发送数据包给服务器send(client_fd, &packet, sizeof(Packet), 0);}}if (fds[1].revents & POLLIN) {// 若客户端套接字有可读事件// 清空数据包memset(&packet, 0, sizeof(Packet));if (read(client_fd, &packet, sizeof(Packet)) > 0) {// 若成功从服务器读取数据if (packet.type == 0) {// 若数据包类型为消息,输出接收到的消息printf("Received from server: %s", packet.data);} else if (packet.type == 1) {// 若数据包类型为断开连接,输出服务器断开连接的信息printf("Server disconnected\n");// 关闭客户端套接字close(client_fd);// 跳出循环break;}} else {// 若读取数据失败,说明服务器断开连接printf("Server disconnected\n");// 关闭客户端套接字close(client_fd);// 跳出循环break;}}}// 关闭客户端套接字close(client_fd);return 0;
}

5.3客户端程序(client2.c)

// 包含标准输入输出库,用于使用 printf、perror 等函数进行输入输出操作
#include <stdio.h>
// 包含标准库,提供 exit 等函数用于程序退出等操作
#include <stdlib.h>
// 包含字符串处理库,提供 memset、strlen 等字符串操作函数
#include <string.h>
// 包含 Unix 标准库,提供 close、read、write 等系统调用函数
#include <unistd.h>
// 包含网络地址转换库,提供 inet_ntoa、htons 等网络地址转换函数
#include <arpa/inet.h>
// 包含线程相关的头文件,用于创建和管理线程
#include <pthread.h>// 定义服务器监听的端口号
#define PORT 8080
// 定义数据缓冲区的大小
#define BUFFER_SIZE 1024/*** 定义数据包结构体,用于在客户端和服务器之间传输数据* type 数据包类型,0 表示消息,1 表示断开连接* data 数据包携带的数据*/
typedef struct {int type;  // 0 for message, 1 for disconnectchar data[BUFFER_SIZE];
} Packet;// 全局变量,存储客户端套接字文件描述符
int client_fd;/*** 线程函数,用于接收服务器发送的消息* arg 线程函数的参数,此处未使用* @return 线程返回值,此处为 NULL*/
void *receive_messages(void *arg) {// 定义数据包变量,用于存储从服务器接收的数据包Packet packet;// 进入无限循环,持续接收服务器消息while (1) {// 清空数据包memset(&packet, 0, sizeof(Packet));if (read(client_fd, &packet, sizeof(Packet)) > 0) {// 若成功从服务器读取数据if (packet.type == 0) {// 若数据包类型为消息,输出接收到的消息printf("Received from server: %s", packet.data);} else if (packet.type == 1) {// 若数据包类型为断开连接,输出服务器断开连接的信息printf("Server disconnected\n");// 关闭客户端套接字close(client_fd);// 退出程序,返回失败状态exit(EXIT_FAILURE);}} else {// 若读取数据失败,说明服务器断开连接printf("Server disconnected\n");// 关闭客户端套接字close(client_fd);// 退出程序,返回失败状态exit(EXIT_FAILURE);}}return NULL;
}/*** 主函数,客户端程序的入口点* @return 程序的退出状态码,0 表示正常退出*/
int main() {// server_addr 存储服务器的地址信息struct sockaddr_in server_addr;// packet 用于存储要发送的数据包Packet packet;// thread_id 存储线程的标识符pthread_t thread_id;// 创建客户端套接字,使用 IPv4 地址族和 TCP 协议if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {// 若套接字创建失败,输出错误信息并退出程序perror("socket failed");exit(EXIT_FAILURE);}// 设置服务器地址信息server_addr.sin_family = AF_INET;// 将端口号从主机字节序转换为网络字节序server_addr.sin_port = htons(PORT);// 将服务器的 IP 地址转换为网络字节序server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");// 连接到服务器if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {// 若连接失败,输出错误信息,关闭套接字并退出程序perror("connect");close(client_fd);exit(EXIT_FAILURE);}// 输出连接成功的信息printf("Connected to server\n");// 创建线程来接收消息if (pthread_create(&thread_id, NULL, receive_messages, NULL) != 0) {// 若线程创建失败,输出错误信息,关闭套接字并退出程序perror("pthread_create");close(client_fd);exit(EXIT_FAILURE);}// 进入无限循环,持续从标准输入读取数据并发送给服务器while (1) {// 清空数据包的数据部分memset(packet.data, 0, BUFFER_SIZE);if (fgets(packet.data, BUFFER_SIZE, stdin) != NULL) {// 若成功从标准输入读取数据// 设置数据包类型为消息packet.type = 0;// 发送数据包给服务器send(client_fd, &packet, sizeof(Packet), 0);}}// 关闭客户端套接字close(client_fd);return 0;
}

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

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

相关文章

JavaIO流的使用和修饰器模式(直击心灵版)

系列文章目录 JavaIO流的使用和修饰器模式 文章目录 系列文章目录前言一、字节流&#xff1a; 1.FileInputStream(读取文件)2.FileOutputStream(写入文件) 二、字符流&#xff1a; 1..基础字符流:2.处理流&#xff1a;3.对象处理流&#xff1a;4.转换流&#xff1a; 三、修饰器…

【设计模式】SOLID 设计原则概述

SOLID 是面向对象设计中的五大原则&#xff0c;不管什么面向对象的语言&#xff0c; 这个准则都很重要&#xff0c;如果你没听说过&#xff0c;赶紧先学一下。它可以提高代码的可维护性、可扩展性和可读性&#xff0c;使代码更加健壮、易于测试和扩展。SOLID 代表以下五个设计原…

可发1区的超级创新思路:基于注意力机制的DSD-CNN时间序列预测模型(功率预测、交通流量预测、故障检测)

首先声明,该模型为原创!原创!原创! 一、应用场景 该模型主要用于时间序列数据预测问题,包含功率预测、电池寿命预测、电机故障检测等等 二、模型整体介绍(本文以光伏功率预测为例) DSD-CNN(Depthwise-Spacewise Separable CNN)结合通道注意力机制,通过以下创新提升…

wsl2配置xv6全解(包括22.04Jammy)

文章目录 获取xv6源代码Ubuntu20.04 Version安装指令成功测试参考MIT2021年官方文档 24.04 Version安装指令成功测试参考MIT2024年官方文档 Ubuntu 22.04没有官方文档&#xff1f; 配置大体流程1. 卸载原本qemu&#xff08;如果之前安装了&#xff09;2. clone qemu官方源代码&…

招聘面试季--一文顿悟,Java中字节流和字符流的区别及使用场景上的差异

‌一、核心区别‌ ‌特性‌‌字节流‌‌字符流‌‌数据单位‌以字节&#xff08;8-bit&#xff09;为单位处理数据&#xff08;如0xA1&#xff09;以字符&#xff08;16-bit Unicode&#xff09;为单位处理数据&#xff08;如A, 你&#xff09;‌基类‌InputStream / OutputSt…

车载以太网网络测试-16【传输层-UDP】

目录 1 摘要2 车载以太网传输层概述3 车载以太网UDP协议3.1 车载以太网UDP协议的作用3.2 UDP报文帧结构3.3 UDP协议的通信过程3.3.1 通信过程3.3.2 实例示例3.3.3 代码示例 4 总结 1 摘要 车载以太网的第五层是传输层&#xff0c;它在车载网络架构中扮演着至关重要的角色。主要…

深度强化学习中的深度神经网络优化策略:挑战与解决方案

I. 引言 深度强化学习&#xff08;Deep Reinforcement Learning&#xff0c;DRL&#xff09;结合了强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;和深度学习&#xff08;Deep Learning&#xff09;的优点&#xff0c;使得智能体能够在复杂的环境中学…

无人机点对点技术要点分析!

一、技术架构 1. 网络拓扑 Ad-hoc网络&#xff1a;无人机动态组建自组织网络&#xff0c;节点自主协商路由&#xff0c;无需依赖地面基站。 混合架构&#xff1a;部分场景结合中心节点&#xff08;如指挥站&#xff09;与P2P网络&#xff0c;兼顾集中调度与分布式协同。 2.…

MQ,RabbitMQ,MQ的好处,RabbitMQ的原理和核心组件,工作模式

1.MQ MQ全称 Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中 保存消息的容器。它是应用程序和应用程序之间的通信方法 1.1 为什么使用MQ 在项目中&#xff0c;可将一些无需即时返回且耗时的操作提取出来&#xff0c;进行异步处理&#xff0…

django怎么配置404和500

在 Django 中&#xff0c;配置 404 和 500 错误页面需要以下步骤&#xff1a; 1. 创建自定义错误页面模板 首先&#xff0c;创建两个模板文件&#xff0c;分别用于 404 和 500 错误页面。假设你的模板目录是 templates/。 404 页面模板 创建文件 templates/404.html&#x…

各类神经网络学习:(四)RNN 循环神经网络(下集),pytorch 版的 RNN 代码编写

上一篇下一篇RNN&#xff08;中集&#xff09;待编写 代码详解 pytorch 官网主要有两个可调用的模块&#xff0c;分别是 nn.RNNCell 和 nn.RNN &#xff0c;下面会进行详细讲解。 RNN 的同步多对多、多对一、一对多等等结构都是由这两个模块实现的&#xff0c;只需要将对输入…

深度学习篇---深度学习中的范数

文章目录 前言一、向量范数1.L0范数1.1定义1.2计算式1.3特点1.4应用场景1.4.1特征选择1.4.2压缩感知 2.L1范数&#xff08;曼哈顿范数&#xff09;2.1定义2.2计算式2.3特点2.4应用场景2.4.1L1正则化2.4.2鲁棒回归 3.L2范数&#xff08;欧几里得范数&#xff09;3.1定义3.2特点3…

星越L_灯光操作使用讲解

目录 1.开启前照灯 2左右转向灯、远近灯 3.auto自动灯光 4.自适应远近灯光 5.后雾灯 6.调节大灯高度 1.开启前照灯 2左右转向灯、远近灯 3.auto自动灯光 系统根据光线自动开启灯光

Stable Diffusion lora训练(一)

一、不同维度的LoRA训练步数建议 2D风格训练 数据规模&#xff1a;建议20-50张高质量图片&#xff08;分辨率≥10241024&#xff09;&#xff0c;覆盖多角度、多表情的平面风格。步数范围&#xff1a;总步数控制在1000-2000步&#xff0c;公式为 总步数 Repeat Image Epoch …

AI 生成 PPT 网站介绍与优缺点分析

随着人工智能技术不断发展&#xff0c;利用 AI 自动生成 PPT 已成为提高演示文稿制作效率的热门方式。本文将介绍几款主流的 AI PPT 工具&#xff0c;重点列出免费使用机会较多的网站&#xff0c;并对各平台的优缺点进行详细分析&#xff0c;帮助用户根据自身需求选择合适的工具…

使用Systemd管理ES服务进程

Centos中的Systemd介绍 CentOS 中的 Systemd 详细介绍 Systemd 是 Linux 系统的初始化系统和服务管理器&#xff0c;自 CentOS 7 起取代了传统的 SysVinit&#xff0c;成为默认的初始化工具。它负责系统启动、服务管理、日志记录等核心功能&#xff0c;显著提升了系统的启动速…

【一维前缀和与二维前缀和(简单版dp)】

1.前缀和模板 一维前缀和模板 1.暴力解法 要求哪段区间&#xff0c;我就直接遍历那段区间求和。 时间复杂度O(n*q) 2.前缀和 ------ 快速求出数组中某一个连续区间的和。 1&#xff09;预处理一个前缀和数组 这个前缀和数组设定为dp&#xff0c;dp[i]表示&#xff1a;表示…

在Windows和Linux系统上的Docker环境中使用的镜像是否相同

在Windows和Linux系统上的Docker环境中使用的镜像是否相同&#xff0c;取决于具体的运行模式和目标平台&#xff1a; 1. Linux容器模式&#xff08;默认/常见场景&#xff09; Windows系统&#xff1a; 当Windows上的Docker以Linux容器模式运行时&#xff08;默认方式&#xf…

植物来源药用天然产物的合成生物学研究进展-文献精读121

植物来源药用天然产物的合成生物学研究进展 摘要 大多数药用天然产物在植物中含量低微&#xff0c;提取分离困难&#xff1b;而且这些化合物一般结构复杂&#xff0c;化学合成难度大&#xff0c;还容易造成环境污染。基于合成生物学技术获得药用天然产物具有绿色环保和可持续发…

JavaScript |(五)DOM简介 | 尚硅谷JavaScript基础实战

学习来源&#xff1a;尚硅谷JavaScript基础&实战丨JS入门到精通全套完整版 笔记来源&#xff1a;在这位大佬的基础上添加了一些东西&#xff0c;欢迎大家支持原创&#xff0c;大佬太棒了&#xff1a;JavaScript |&#xff08;五&#xff09;DOM简介 | 尚硅谷JavaScript基础…