当前位置: 首页 > news >正文

使用多进程和多线程实现服务器并发【C语言实现】

在TCP通信过程中,服务器端启动之后可以同时和多个客户端建立连接,并进行网络通信,但是在一个单进程的服务器的时候,提供的服务器代码却不能完成这样的需求,先简单的看一下之前的服务器代码的处理思路,再来分析代码中的弊端:

// server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main()
{// 1. 创建监听的套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);// 2. 将socket()返回值和本地的IP端口绑定到一起struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(10000);   // 大端端口// INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址// 这个宏可以代表任意一个IP地址addr.sin_addr.s_addr = INADDR_ANY;  // 这个宏的值为0 == 0.0.0.0int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));// 3. 设置监听ret = listen(lfd, 128);// 4. 阻塞等待并接受客户端连接struct sockaddr_in cliaddr;int clilen = sizeof(cliaddr);int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);// 5. 和客户端通信while(1){// 接收数据char buf[1024];memset(buf, 0, sizeof(buf));int len = read(cfd, buf, sizeof(buf));if(len > 0){printf("客户端say: %s\n", buf);write(cfd, buf, len);}else if(len  == 0){printf("客户端断开了连接...\n");break;}else{perror("read");break;}}close(cfd);close(lfd);return 0;
}

在上面的代码中用到了三个会引起程序阻塞的函数,分别是:

  • accept():如果服务器端没有新客户端连接,阻塞当前进程/线程,如果检测到新连接解除阻塞,建立连接
  • read():如果通信的套接字对应的读缓冲区没有数据,阻塞当前进程/线程,检测到数据解除阻塞,接收数据
  • write():如果通信的套接字写缓冲区被写满了,阻塞当前进程/线程(这种情况比较少见)

如果需要和发起新的连接请求的客户端建立连接,那么就必须在服务器端通过一个循环调accept()函数,另外已经和服务器建立连接的客户端需要和服务器通信,发送数据时的阻塞可以忽略,当接收不到数据时程序也会被阻塞,这时候就会非常矛盾,被accept()阻塞就无法通信,被read()阻塞就无法和客户端建立新连接。因此得出一个结论,基于上述处理方式,在单线程/单进程场景下,服务器是无法处理多连接的,解决方案也有很多,常用的有三种:

  • 使用多线程实现
  • 使用多进程实现
  • 使用IO多路转接(复用)实现
  • 使用IO多路转接 + 多线程实现

1.使用多进程实现并发服务器

如果要编写多进程版的并发服务器程序,首先要考虑,创建出的多个进程都是什么角色,这样就可以在程序中对号入座了。在Tcp服务器端一共有两个角色,分别是:监听和通信,监听是一个持续的动作,如果有新连接就建立连接,如果没有新连接就阻塞。关于通信是需要和多个客户端同时进行的,因此需要多个进程,这样才能达到互不影响的效果。进程也有两大类:父进程和子进程,通过分析我们可以这样分配进程:

  • 父进程:
  • 负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数
  • 创建子进程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信
  • 回收子进程资源:子进程退出回收其内核PCB资源,防止出现僵尸进程
  • 子进程:负责通信,基于父进程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。
  • 发送数据:send() / write()
  • 接收数据:recv() / read()

在多进程版的服务器端程序中,多个进程是有血缘关系,对应有血缘关系的进程来说,还需要想明白他们有哪些资源是可以被继承的,哪些资源是独占的,以及一些其他细节:

  • 子进程是父进程的拷贝,在子进程的内核区PCB中,文件描述符也是可以被拷贝的,因此在父进程可以使用的文件描述符在子进程中也有一份,并且可以使用它们做和父进程一样的事情。
  • 父子进程有用各自的独立的虚拟地址空间,因此所有的资源都是独占的
  • 为了节省系统资源,对于只有在父进程才能用到的资源,可以在子进程中将其释放掉,父进程亦如此。
  • 由于需要在父进程中做accept()操作,并且要释放子进程资源,如果想要更高效一下可以使用信号的方式处理

具体实现

下面是一个使用多进程实现的并发 TCP 服务器的示例代码,包含详细注释。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <ctype.h>// 处理SIGCHLD信号,避免僵尸进程
void sigchld_handler(int signo) {while (waitpid(-1, NULL, WNOHANG) > 0); //表示非阻塞地等待任意子进程终止。-1 表示等待任何子进程,NULL 表示不需要子进程的退出状态,WNOHANG 表示非阻塞。
}// 处理客户端通信
void handle_client(int cfd) {char buf[1024];int n;while ((n = read(cfd, buf, sizeof(buf))) > 0) {for (int i = 0; i < n; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, n);}close(cfd);
}int main() {// 创建监听套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if (lfd < 0) {perror("socket error");return -1;}// 绑定套接字struct sockaddr_in serv;bzero(&serv, sizeof(serv));serv.sin_family = AF_INET;serv.sin_port = htons(8888);serv.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(lfd, (struct sockaddr *)&serv, sizeof(serv)) < 0) {perror("bind error");return -1;}// 监听连接请求listen(lfd, 3); //backlog 参数限制的是等待被 accept 的已完成连接的队列长度,而不是服务器可以处理的总客户端连接数。// 设置SIGCHLD信号处理struct sigaction sa;sa.sa_handler = sigchld_handler;sigemptyset(&sa.sa_mask);           // 初始化信号屏蔽字为空。sa.sa_flags = SA_RESTART;           //设置信号处理之后自动重新启动被信号打断的系统调用。if (sigaction(SIGCHLD, &sa, NULL) < 0) {perror("sigaction error");return -1;}while (1) {struct sockaddr_in client;socklen_t len = sizeof(client);int cfd = accept(lfd, (struct sockaddr *)&client, &len);if (cfd < 0) {perror("accept error");continue;}// 打印客户端连接信息char sIP[16];memset(sIP, 0x00, sizeof(sIP));printf("Client connected: IP [%s], PORT [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));pid_t pid = fork();if (pid == 0) { // 子进程close(lfd); // 子进程关闭监听套接字handle_client(cfd); // 处理客户端通信printf("Client disconnected: IP [%s], PORT [%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));exit(0); // 子进程处理完成后退出} else if (pid > 0) { // 父进程close(cfd); // 父进程关闭与客户端通信的套接字} else {perror("fork error");close(cfd);}}close(lfd);return 0;
}

在上面的示例代码中,父子进程中分别关掉了用不到的文件描述符(父进程不需要通信,子进程也不需要监听)。如果客户端主动断开连接,那么服务器端负责和客户端通信的子进程也就退出了,子进程退出之后会给父进程发送一个叫做SIGCHLD的信号,在父进程中通过sigaction()函数捕捉了该信号,通过回调函数callback()中的waitpid()对退出的子进程进行了资源回收。

如果父进程调用accept() 函数没有检测到新的客户端连接,父进程就阻塞在这儿了,这时候有子进程退出了,发送信号给父进程,父进程就捕捉到了这个信号SIGCHLD, 由于信号的优先级很高,会打断代码正常的执行流程,因此父进程的阻塞被中断,转而去处理这个信号对应的函数callback(),处理完毕,再次回到accept()位置,但是这是已经无法阻塞了,函数直接返回-1,此时函数调用失败,错误描述为accept: Interrupted system call,对应的错误号为EINTR,由于代码是被信号中断导致的错误,所以可以在程序中对这个错误号进行判断,让父进程重新调用accept(),继续阻塞或者接受客户端的新连接。

2.使用多线程实现并发服务器

编写多线程版的并发服务器程序和多进程思路差不多,考虑明白了对号入座即可。多线程中的线程有两大类:主线程(父线程)和子线程,他们分别要在服务器端处理监听和通信流程。根据多进程的处理思路,就可以这样设计了:

  • 主线程:
  • 负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数
  • 创建子线程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信
  • 回收子线程资源:由于回收需要调用阻塞函数,这样就会影响accept(),直接做线程分离即可。
  • 子线程:负责通信,基于主线程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。
  • 发送数据:send() / write()
  • 接收数据:recv() / read()

在多线程版的服务器端程序中,多个线程共用同一个地址空间,有些数据是共享的,有些数据的独占的,下面来分析一些其中的一些细节:

  • 同一地址空间中的多个线程的栈空间是独占的
  • 多个线程共享全局数据区,堆区,以及内核区的文件描述符等资源,因此需要注意数据覆盖问题,并且在多个线程访问共享资源的时候,还需要进行线程同步。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <ctype.h>// 处理客户端通信的函数
void *handle_client(void *arg) {int cfd = *(int *)arg;free(arg);struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);//getpeername 函数获取与套接字 cfd 关联的远程(客户端)地址信息,并将其存储在 client_addr 结构体中。getpeername(cfd, (struct sockaddr *)&client_addr, &client_addr_len);    char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));int client_port = ntohs(client_addr.sin_port);printf("Client connected: IP [%s], PORT [%d], FD [%d]\n", client_ip, client_port, cfd);char buf[1024];int n;while ((n = read(cfd, buf, sizeof(buf))) > 0) {for (int i = 0; i < n; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, n);}printf("Client disconnected: IP [%s], PORT [%d], FD [%d]\n", client_ip, client_port, cfd);close(cfd);return NULL;
}int main() {// 创建监听套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if (lfd < 0) {perror("socket error");return -1;}// 绑定套接字struct sockaddr_in serv_addr;bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(8888);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("bind error");close(lfd);return -1;}// 监听连接请求if (listen(lfd, 5) < 0) {perror("listen error");close(lfd);return -1;}while (1) {struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int *cfd = malloc(sizeof(int));     //每次新建一个int类型的变量,保存不同的通信套接字*cfd = accept(lfd, (struct sockaddr *)&client_addr, &client_addr_len);if (*cfd < 0) {perror("accept error");free(cfd);continue;}// 创建线程处理客户端请求pthread_t tid;pthread_attr_t attr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程分离属性if (pthread_create(&tid, &attr, handle_client, cfd) != 0) {perror("pthread_create error");close(*cfd);free(cfd);}pthread_attr_destroy(&attr);}close(lfd);return 0;
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define PORT 8888
#define BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"int main() {int sock = 0, valread;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};char input_buffer[BUFFER_SIZE] = {0};char *hello = "Hello from client";int opt = 1;// 创建 TCP 套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");return -1;}// 设置服务器地址结构serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 将 IPv4 地址从文本转换为二进制形式if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");return -1;}// 连接服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");return -1;}printf("Connected to server\n");// 循环发送消息并接收响应while (1) {printf("Enter message to send (or 'exit' to quit): ");fgets(input_buffer, BUFFER_SIZE, stdin);// 去掉输入的换行符input_buffer[strcspn(input_buffer, "\n")] = 0;// 如果输入是 'exit',则退出循环if (strcmp(input_buffer, "exit") == 0) {break;}// 发送消息给服务器send(sock, input_buffer, strlen(input_buffer), 0);printf("Message sent to server: %s\n", input_buffer);// 接收服务器的响应valread = read(sock, buffer, BUFFER_SIZE);printf("Server response: %s\n", buffer);memset(buffer, 0, sizeof(buffer));}close(sock);return 0;
}

相关文章:

使用多进程和多线程实现服务器并发【C语言实现】

在TCP通信过程中&#xff0c;服务器端启动之后可以同时和多个客户端建立连接&#xff0c;并进行网络通信&#xff0c;但是在一个单进程的服务器的时候&#xff0c;提供的服务器代码却不能完成这样的需求&#xff0c;先简单的看一下之前的服务器代码的处理思路&#xff0c;再来分…...

深入理解Linux网络(三):TCP对象创建

深入理解Linux网络&#xff08;三&#xff09;&#xff1a;TCP对象创建 TCP对象创建inet_createsock_init_data TCP对象创建 常见的三句TCP编程&#xff1a; int main() {int sk socket(AF_INET, SOCK_STREAM, 0);connect(sk, ...)recv(sk, ...) }简单的两三⾏代码&#xff…...

windows server——4.安装DNS管理器

windows server——4.安装DNS管理器 一、准备二、安装DNS管理器1.打开服务器管理器2.添加dns服务器 三、验证 一、准备 windows server电脑&#xff08;已安装IIS&#xff09; 静态网站数据包 二、安装DNS管理器 1.打开服务器管理器 2.添加dns服务器 点击管理——添加角色和…...

速盾:金融行业服务器如何避免DDoS攻击?

随着金融行业的数字化和网络化进程加快&#xff0c;服务器成为金融机构不可或缺的一部分。然而&#xff0c;服务器面临的安全威胁也在不断增加&#xff0c;其中之一就是DDoS攻击。DDoS&#xff08;Distributed Denial of Service&#xff09;攻击是通过向目标服务器发送大量无法…...

谷粒商城实战笔记-38-前端基础-Vue-指令-单向绑定双向绑定

文章目录 一&#xff0c;插值表达式注意事项1&#xff1a;不适合复杂的逻辑处理注意事项2&#xff1a;插值表达式支持文本拼接注意事项3&#xff1a;插值表达式只能在标签体中 二&#xff0c;v-html和v-textv-textv-html区别总结&#xff1a;最佳实践 三&#xff0c;v-model复选…...

MyPostMan 迭代文档管理、自动化接口闭环测试工具(自动化测试篇)

MyPostMan 是一款类似 PostMan 的接口请求软件&#xff0c;按照 项目&#xff08;微服务&#xff09;、目录来管理我们的接口&#xff0c;基于迭代来管理我们的接口文档&#xff0c;文档可以导出和通过 url 实时分享&#xff0c;按照迭代编写自动化测试用例&#xff0c;在不同环…...

https和http有哪些区别?

在今天的互联网世界中&#xff0c;我们经常听到关于HTTPS和HTTP的术语。它们都是超文本传输协议&#xff08;HTTP&#xff09;的变种&#xff0c;但它们之间存在着重要的区别。本篇博客将深入探讨HTTPS与HTTP之间的差异以及为什么HTTPS在现代网络中变得如此重要。 目录 1. HT…...

Bubbliiiing 的 Retinaface rknn python推理分析

Bubbliiiing 的 Retinaface rknn python推理分析 项目说明 使用的是Bubbliiiing的深度学习教程-Pytorch 搭建自己的Retinaface人脸检测平台的模型&#xff0c;下面是项目的Bubbliiiing视频讲解地址以及源码地址和博客地址&#xff1b; 作者的项目讲解视频&#xff1a;https:…...

Web前端-Web开发HTML基础8-nav

一. 基础 1. 写一个导航标签&#xff0c;里面是两个超链接&#xff0c;分别指向https://baidu.com和https://huawei.com/cn&#xff1b; 2. 写一个导航标签&#xff0c;里面是三个超链接&#xff0c;分别指向https://baidu.com、https://huawei.com/cn和https://www.nowcoder.c…...

如何建设和维护数据仓库:深入指南

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; V: LAF20151116 进行更多交流学习 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来&#xff…...

海思arm-hisiv400-linux-gcc 交叉编译rsyslog 记录心得

需要编译rsyslog,参考海思3536平台上rsyslog交叉编译、使用-CSDN博客和rsyslog移植&#xff08;亲测成功&#xff09;_rsyslog交叉编译-CSDN博客 首先下载了要用到的一些库的源码&#xff0c;先交叉编译这些库 原来是在centos6上交叉编译的&#xff0c;结果编译时报缺少软件要…...

IDEA工具中Java语言写小工具遇到的问题

一&#xff1a;读取excel时遇到 org/apache/poi/ss/usermodel/WorkbookProvider 解决办法&#xff1a; 在pom.xml中把poi的引文包放在最前面即可&#xff08;目前就算放在最后面也不报错了&#xff0c;不知道为啥&#xff09; 二&#xff1a;本地maven打包时&#xff0c;没有…...

2-38 基于matlab的蚁群算法优化无人机uav巡检

基于matlab的蚁群算法优化无人机uav巡检&#xff0c;巡检位置坐标可根据需求设置&#xff0c;从基地出发&#xff0c;返回基地&#xff0c;使得路径最小。可设置蚁群数量&#xff0c;信息素系数。输出最佳路线长度。程序已调通&#xff0c;可直接运行。 2-38 蚁群算法优化无人…...

解决selenium打印保存为PDF时图片未加载成功的问题

使用selenium打印网页时&#xff0c;如果程序运行很快的话&#xff0c;可能会导致图片没有加载成功即进行了保存&#xff0c;出现这个问题最初的思考是在执行打印任务时使用js进行强制等待&#xff0c;后发现实现效果并不好。在加载页面时使用自动下滑的方式将网页拉到底&#…...

如何将PDF转换成可以直接编辑的CAD图纸?

PDF图纸是为了让用户更好的阅览CAD文件&#xff0c;但是&#xff0c;当我们想要对其进行编辑的时候&#xff0c;PDF图纸就是一个麻烦了。那么PDF转换成CAD后可以编辑吗&#xff1f;如何将PDF转换成可以直接编辑的CAD图纸呢&#xff1f;本篇给你答案。 1、启动迅捷CAD编辑器&…...

【STM32】理解时钟树(图示分析)

文章目录 时钟系统什么是时钟时钟树简化图示类比示例时钟树详解时钟源系统时钟配置各总线时钟外设时钟 时钟系统 什么是时钟 时钟在电子和计算机系统中指的是生成周期性信号的电路或设备&#xff0c;这种周期性信号用于同步系统内的各种操作。时钟信号通常是方波&#xff0c;…...

动态内存四个函数

文章目录 1. malloc2. calloc3. realloc4. free 在C语言中&#xff0c;malloc、calloc、realloc 和 free 是用于动态内存管理的标准库函数&#xff0c;它们定义在 <stdlib.h> 头文件中。以下是这些函数的用法&#xff1a; 1. malloc malloc 函数用于在堆区分配指定大小…...

DevExpress WPF中文教程 - 为项目添加GridControl并将其绑定到数据

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…...

高性能分布式IO系统BL205 OPC UA耦合器

边缘计算是指在网络的边缘位置进行数据处理和分析&#xff0c;而不是将所有数据都传送到云端或中心服务器&#xff0c;这样可以减少延迟、降低带宽需求、提高响应速度并增强数据安全性。 钡铼BL205耦合器就内置边缘计算功能&#xff0c;它不依赖上位机和云平台&#xff0c;就能…...

live555 rtsp服务器实战之doGetNextFrame

live555关于RTSP协议交互流程 live555的核心数据结构值之闭环双向链表 live555 rtsp服务器实战之createNewStreamSource live555 rtsp服务器实战之doGetNextFrame live555搭建实时播放rtsp服务器 注意&#xff1a;该篇文章可能有些绕&#xff0c;最好跟着文章追踪下源码&…...

Nginx系列-3 servername优先级和location优先级和常用正则表达式

1.正则表达式和分组 由于Nginx配置文件中经常出现正则表达式&#xff0c;因此本章节专门对常见的正则表达式进行简单介绍。 [1] 开始与结束 ^表示匹配输入字符串的开始 $表示匹配输入字符串的结束[2] 匹配次数 ?表示匹配0次或者1次 表示匹配1次或多次 *表示匹配0从或多次…...

python—爬虫爬取电影页面实例

下面是一个简单的爬虫实例&#xff0c;使用Python的requests库来发送HTTP请求&#xff0c;并使用lxml库来解析HTML页面内容。这个爬虫的目标是抓取一个电影网站&#xff0c;并提取每部电影的主义部分。 首先&#xff0c;确保你已经安装了requests和lxml库。如果没有安装&#x…...

实现图片拖拽和缩小放大功能。

1. 前言 不知道各位前端小伙伴蓝湖使用的多不多&#xff0c;反正我是经常在用&#xff0c;ui将原型图设计好后上传至蓝湖&#xff0c;前端开发人员就可以开始静态页面的的编写了。对于页面细节看的不是很清楚可以使用滚轮缩放后再拖拽查看&#xff0c;还是很方便的。于是就花了…...

昇思25天学习打卡营第18天|munger85

DCGAN生成漫画头像 首先肯定是下载训练数据&#xff0c;而这些训练数据就是一些卡通头像。后来我们会看到这个具体的头像 就像其他的数据集目录一样&#xff0c;它是由一些目录和这个目录下面的文件组成的数据集。 有相当多的图片。所以可以训练出来比较好的效果。 图片的处理…...

nginx配置文件说明

Nginx的配置文件说明 Nginx配置文件的主要配置块可以分为三个部分&#xff1a;全局配置块&#xff08;events和http块&#xff09;&#xff0c;events块和http块。这三个部分共同定义了Nginx服务器的整体行为和处理HTTP请求的方式。 全局配置块&#xff1a; 包含了影响Nginx服…...

用不同的url头利用Python访问一个网站,把返回的东西保存为txt文件

这个需要调用requests模块&#xff08;相当于c的头文件&#xff09; import requests 还需要一个User-Agent头&#xff08;这个意思就是告诉python用的什么系统和浏览器&#xff09; Google Chrome&#xff08;Windows&#xff09;: Mozilla/5.0 (Windows NT 10.0; Win64; x64…...

一文掌握Prometheus实现页面登录认证并集成grafana

一、接入方式 以保护Web站点的访问控制&#xff0c;如HTTP 服务器配置中实现安全的加密通信和身份验证&#xff0c;保护 Web 应用程序和用户数据的安全性。 1.1 加密密码 通过httpd-tools工具包来进行Web站点加密 yum install -y httpd-tools方式一&#xff1a;通过htpasswd生…...

欢迎来到 Mint Expedition:Web3 和 NFT 的新时代开始

7 月 15 日&#xff0c;Mint Expedition 正式开启&#xff0c;作为 Mint 生态系统的旗舰项目&#xff0c;将彻底变革 Web3 和 NFT 去中心化应用&#xff01; Mint Expedition 是 Mint 的最新航程&#xff0c;延续了 Mint Forest 的成功。Mint Forest 吸引了超过 41.4 万独立用…...

针对环境构图的全局一致性扫描点云数据对齐(Graph SLAM)

本算法是一个经典的&#xff0c;针对SLAM&#xff08;simultaneous localization and mapping 即时定位与地图构建&#xff09;问题而提出的算法。该算法的提出者是Feng Lu和Evangelos Milios&#xff0c;他们在本算法中开创了通过全局优化方程组以减少约束引入的误差来进一步优…...

Matlab学习笔记01 - 基本数据类型

Matlab学习笔记01 - 基本数据类型 1、数据类型转换2、矩阵2.1 访问单个矩阵元素2.2 访问多个矩阵元素2.3 矩阵转置 3、字符与字符串4、数值与字符串5、元胞数组 1、数据类型转换 十进制转十六进制字符串‘FF’ >> hex2dec(3ff)ans 1023十进制转十六进制字符串 >>…...

平面设计有哪些网站/南宁网站seo外包

http://www.cnblogs.com/bakari/p/3562244.html perties类的操作 知识学而不用&#xff0c;就等于没用&#xff0c;到真正用到的时候还得重新再学。最近在看几款开源模拟器的源码&#xff0c;里面涉及到了很多关于Properties类的引用&#xff0c;由于Java已经好久没用了&#x…...

厦门注册公司流程/苏州seo关键词排名

马上就要到中秋节了&#xff0c;提前祝大家中秋节快乐&#xff0c;最近比较忙&#xff0c;考虑到粉丝一直要求我更新文章&#xff0c;我今天就加班更新一下文章。实时数仓如何做数据分层我不喜欢搞什么花里胡哨的词汇&#xff0c;让粉丝听着挠头&#xff0c;我就想用大白话分享…...

怎么做电影网站不违法吗/阜新网络推广

一、闭包闭包从形式上来说是在外部函数中定义内部函数&#xff0c;并且内部函数引用了外部函数的变量&#xff0c;此变量叫做自由变量。或者说是将组成函数的语句和这些语句的执行环境打包在一起。闭包满足的条件&#xff1a;必须有一个内嵌函数内嵌函数必须使用外部函数的变量…...

网站域名被劫持/西安seo霸屏

到目前为止我们所接触过的角色都是可以接收消息的&#xff0c;而消息的类型也是五花八门&#xff0c;如String、元组、case类/自定义消息等。然而发送消息的行为在感觉上与我们日常编程工作中所使用的常规函数调用还是有很大区别的&#xff0c;为了弥合二者之间的鸿沟&#xff…...

wordpress自定义表/建站公司排名

logstash java 版本问题配置logstash收集应用日志时出现报错&#xff0c;说是找不到JAVA_HOME环境变量&#xff0c;但是明明已经设置了logstash要求java 1.8以上&#xff0c;查看生产环境&#xff1a;[rootlocalhost ~]# echo ${JAVA_HOME}/usr/local/jdk-12[rootlocalhost ~]#…...

公司销售网站怎么做/长沙网站推广seo

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/wo541075754/article/details/81734770 在互联网中的每一刻&#xff0c;你可能都在享受着Base64带来…...