Linux高性能服务器编程 学习笔记 第六章 高级IO函数

news2025/6/23 2:37:20

pipe函数用于创建一个管道,以实现进程间通信:
在这里插入图片描述
fd参数是一个包含两个int的数组。该函数成功时返回0,并将一对打开的文件描述符填入其参数指向的数组,如果失败,则返回-1并设置errno。

pipe函数创建的这两个文件描述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数组可以从fd[0]读出,并且fd[0]只能用于从管道读出数据,fd[1]只能用于往管道写入数据,不能反过来使用。如果要实现双向的数据传输,就应该使用两个管道。默认,这一对文件描述符都是阻塞的。如果我们用read系统调用读取一个空管道,则read函数将被阻塞,直到管道内有数据可读;如果我们用write系统调用往一个满的管道中写入数据,则write函数也被阻塞,直到管道有足够的空闲空间可用。如果应用进程将fd[0]和fd[1]都设为非阻塞的,则read和write函数会有不同行为。如果管道写端文件描述符fd[1]的引用计数减少到0,即没有任何进程需要往管道中写入数据,则针对该管道的读端文件描述符fd[0]的read操作将返回0,即读取到文件结束标记(EOF,End Of File);反之,如果管道的读端文件描述符fd[0]的引用计数减少至0,即没有任何进程需要从管道读取数据,则针对该管道的写端文件描述符fd[1]的write操作将失败,并引发SIGPIPE信号。

管道内部传输的数据是字节流,这和TCP字节流的概念相同。应用层进程能往一个TCP连接中写入多少字节的数据,取决于双方的接收通告窗口的大小和本端的拥塞窗口的大小,而管道本身拥有一个容量限制,它规定如果应用进程不将数据从管道读走,该管道最多能被写入多少字节数据。自Linux 2.6.11内核起,管道容量的大小默认是65535字节,我们可用fcntl函数修改管道容量。

socket的基础API中有一个socketpair函数,它能创建双向管道:
在这里插入图片描述
socketpair函数的前3个参数的含义与socket系统调用的前3个参数完全相同,但domain参数只能使用UNIX本地域协议族AF_UNIX,因为我们仅能在本地使用这个双向管道。fd参数和pipe系统调用的参数一样,但socketpair函数创建的这对文件描述符都是既可读又可写的。socketpair函数成功时返回0,失败时返回-1并设置errno。

有时我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接(比如CGI(Common Gateway Interface)编程,它一种用于创建交互式网络应用程序的技术,它是一种在Web服务器和其他计算机程序之间进行通信的标准方法,允许Web服务器调用外部程序来处理Web请求,并将结果发送回浏览器),这可通过以下用于复制文件描述符的dup或dup2函数来实现:
在这里插入图片描述
dup函数创建一个新文件描述符,该新文件描述符和原有文件描述符file_descriptor参数指向相同的文件、管道、网络连接,且dup函数返回的文件描述符总是取系统当前可用的最小整数值。dup2和dup函数类似,但它将新文件描述符设置为file_descriptor_two参数,如果新文件描述符之前已经打开,那么在重用之前,它将被关闭,关闭操作是静默的(即dup2函数不会报告关闭过程中的任何错误)。关闭和重用新文件描述符的步骤是原子的,这一点很重要,因为试图使用close函数和dup函数实现等效功能将会受到竞态条件的影响,在两个步骤之间,newfd可能会被重用,这种重用可能是因为主程序被分配了一个文件描述符的信号处理器中断,或者是因为并行线程分配了一个文件描述符。dup和dup2系统调用失败时返回-1并设置errno。

通过dup和dup2函数创建的文件描述符不继承原文件描述符的属性,如close-on-exec和non-blocking等。

以下程序使用dup函数实现了一个基本的CGI服务器:

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

int main(int argc, char *argv[]) {
    if (argc <= 2) {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, 5);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        // 先关闭标准输出文件描述符STDOUT_FILENO,其值为1
        close(STDOUT_FILENO);
        // 复制socket文件描述符connfd,由于dup函数总是返回系统中最小的可用文件描述符
        // 因此dup参数实际返回的是1,即之前关闭的标准输出文件描述符的值
        // 这样服务器输出到标准输出的内容会直接发送到与客户连接对应的socket上
        dup(connfd);
        printf("abcd\n");
        close(connfd);
    }

    close(sock);
    return 0;
}

readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数将多块分散的内存数据一并写入文件描述符中,即集中写:
在这里插入图片描述
fd参数是被操作的目标文件描述符。vector参数的类型是iove结构数组,该结构体描述一块内存区。count参数是vector数组的长度。readv和writev函数在成功时返回读出/写入fd的字节数,失败则返回-1并设置errno。

当Web服务器解析完一个HTTP请求后,如果目标文档存在且客户具有读取该文档的权限,则服务器就需要发送一个HTTP应答来传输该文档,这个HTTP应答包括1个状态行、多个头部字段、一个空行和文档内容,其中前3部分内容可能被Web服务器放置在一块内存中,而文档内容则通常被读入到另一块单独的内存中(通过read或mmap函数),我们不需要手动把这两部分内容拼接到一起再发,而是可以使用writev函数将它们同时写出,如下代码所示:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <libgen.h>

#define BUFFER_SIZE 1024
// 定义两种HTTP状态码和状态信息
static const char *status_line[2] = {"200 OK", "500 Internal server error"};

int main(int argc, char *argv[]) {
    if (argc <= 3) {
        printf("usage: %s ip_address port_number filename\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);
    // 将目标文件作为程序的第三个参数传入
    const char *file_name = argv[3];

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, 5);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        // 用于保存HTTP应答的状态行、头部字段、一个空行的缓冲区
        char header_buf[BUFFER_SIZE];
        memset(header_buf, '\0', BUFFER_SIZE);
        // 用于存放目标文件内容
        char *file_buf;
        // 用于存放目标文件的属性,如是否是目录、文件大小等
        struct stat file_stat;
        // 标识文件是否是有效文件
        bool valid = true;
        // 用于记录缓冲区header_buf目前已经使用了多少字节的空间
        int len = 0;
        // 目标文件不存在
        if (stat(file_name, &file_stat) < 0) {
            valid = false;
        } else {
            // 目标文件是一个目录
            if (S_ISDIR(file_stat.st_mode)) {
                valid = false;
            // 有权限读取该文件
            } else if (file_stat.st_mode & S_IROTH) {
                int fd = open(file_name, O_RDONLY);
                // 动态分配存放文件的缓冲区
                file_buf = new char[file_stat.st_size + 1];
                memset(file_buf, '\0', file_stat.st_size + 1);
                if (read(fd, file_buf, file_stat.st_size) < 0) {
                    valid = false;
                }
            } else {
                valid = false;
            }
        }

        // 如果目标文件有效,发送正常的HTTP应答
        if (valid) {
            // snprintf函数返回格式化的字符串的长度
            ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n", "HTTP/1.1", status_line[0]);
            len += ret;
            ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "Content-Length: %d\r\n",
                           file_stat.st_size);
            len += ret;
            ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\r\n");
            // 利用writev函数将header_buf和file_buf的内容一并写出
            struct iovec iv[2];
            iv[0].iov_base = header_buf;
            iv[0].iov_len = strlen(header_buf);
            iv[1].iov_base = file_buf;
            iv[1].iov_len = file_stat.st_size;
            ret = writev(connfd, iv, 2);
        // 如果目标文件无效,则通知客户端服务器发生了内部错误
        } else {
            ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n", "HTTP/1.1", status_line[1]);
            len += ret;
            ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\r\n");
            send(connfd, header_buf, strlen(header_buf), 0);
        }
        close(connfd);
        delete []file_buf;
    }

    close(sock);
    return 0;
}

以上代码中,我们省略了HTTP请求的接收和解析,因为我们关心的重点是HTTP应答的发送。我们直接将目标文件作为第3个参数传递给服务器程序,客户telnet到该服务器上即可获得该文件。

sendfile函数在两个描述符之间直接传递数据(完全在内核中操作),例如从文件到网络套接字,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这称为零拷贝(零拷贝旨在减少数据在内存之间复制的次数,有时人们会将零拷贝保留用于描述在完全不涉及数据复制的情况下的数据传输,sendfile函数虽然减少了用户空间的数据拷贝,但仍然存在内核空间的数据传输,因此有些人可能认为它是半零拷贝(Partial Zero Copy))。sendfile函数定义:
在这里插入图片描述
in_fd参数是待读出内容的文件描述符。out_fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为NULL,则使用读入文件流默认的起始位置。count参数指定在文件描述符in_fd参数和out_fd参数之间传输的字节数。sendfile函数成功时返回传输的字节数,失败则返回-1并设置errno。sendfile函数的man手册明确指出,in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket或管道,而out_fd参数则必须是一个socket,由此可见,sendfile函数几乎是专门为在网络上传输文件而设计的。以下程序利用sendfile函数将服务器上的一个文件传送给客户:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <libgen.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    if (argc <= 3) {
        printf("usage: %s ip_address port_number filename\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);
    const char *file_name = argv[3];

    int filefd = open(file_name, O_RDONLY);
    assert(filefd > 0);
    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, 5);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        sendfile(connfd, filefd, NULL, stat_buf.st_size);
        close(connfd);
    }

    close(sock);
    return 0;
}

以上代码中,我们将目标文件作为第3个参数传递给服务器进程,客户telnet到该服务器上即可获得该文件。相比以上使用writev函数发送文件的程序,以上代码没有为目标文件分配任何用户空间缓存,也没有执行读取文件的操作,但同样实现了文件发送,效率要高很多。

mmap函数用于申请一段内存空间,我们可将这段内存作为进程间通信的共享内存,也可将文件直接映射到其中。munmap函数释放由mmap函数创建的这段内存:
在这里插入图片描述
start参数允许用户使用某个特定的地址作为这段内存的起始地址,如果它是NULL,则系统自动分配一个地址。length参数指定内存段的长度。prot参数设置内存段的访问权限,它可以是以下几个值的按位或:
1.PROT_READ,内存段可读。

2.PROT_WRITE,内存段可写。

3.PROT_EXEC,内存段可执行。

4.PROT_NONE,内存段不能被访问。

flags参数控制内存段内容被修改后程序的行为,它可被设置为下表中的某些值(只列出了常用的值)的按位或(其中MAP_SHARED和MAP_PRIVATE是互斥的):
在这里插入图片描述
fd参数是被映射文件对应的文件描述符,它一般通过open系统调用获得。offset参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。

mmap函数成功时返回指向目标内存区域的指针,失败则返回MAP_FAILED并设置errno。munmap函数成功时返回0,失败则返回-1并设置errno。

splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作:
在这里插入图片描述
fd_in参数是待输入数据的文件描述符,如果fd_in是一个管道文件描述符,那么off_in参数必须被设置为NULL,否则,off_in参数表示从输入数据流的何处开始读取数据,此时,若off_in被设置为NULL,则表示从输入数据流的当前偏移位置读入,若off_in不为NULL,则它指出具体的偏移位置。fd_out/off_out参数的含义与fd_in/off_in参数相同,不过用于输出数据流。len参数指定移动数据的长度。flags参数控制数据如何移动,它可被设为下表值的按位或:
在这里插入图片描述
SPLICE_F_GIFT标志是用于vmsplice函数(一个Linux系统调用,用于在用户空间和内核空间之间传输数据,通常用于高性能的数据传输操作)的。

使用splice函数时,fd_in和fd_out必须至少有一个是管道描述符。splice函数调用成功时返回移动字节的数量,它可能返回0,表示没有数据需要移动,这发生在从管道中读取数据(此时fd_in是管道文件描述符),而该管道中没有任何数据时。splice函数失败时返回-1并设置errno,常见的errno如下表:
在这里插入图片描述
下面使用splice函数实现一个零拷贝的回射服务器,它将客户端发送的数据原样返回给客户端:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <libgen.h>

int main(int argc, char *argv[]) {
    if (argc <= 2) {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(sock, 5);
    assert(ret != -1);

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    if (connfd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        int pipefd[2];
        // 创建管道
        ret = pipe(pipefd);
        assert(ret != -1);
        // 将connfd上流入的客户数据定向到管道中
        ret = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
        // 将管道的输出定向到客户的连接文件描述符connfd中
        ret = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        assert(ret != -1);
        close(connfd);
    }

    close(sock);
    return 0;
}

以上代码通过splice函数将客户端的内容读入pipefd[1]中,然后再使用splice函数从pipefd[0]中读出该内容到客户端,从而实现了简单高效的回射服务,整个过程未执行recv/send函数,因此也未涉及用户空间和内核空间之间的数据拷贝。

tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作,它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作:
在这里插入图片描述
该函数的参数含义与splice函数的参数相同(但fd_in和fd_out必须都是管道文件描述符)。tee函数成功时返回在两个文件描述符之间复制的数据字节数,返回0表示没有复制任何数据。tee函数失败时返回-1并设置errno。

以下程序利用splice和tee函数,实现了Linux下tee程序(同时输出数据到终端和文件)的基本功能:

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("usage: %s <file>\n", argv[0]);
        return 1;
    }
    int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
    assert(filefd > 0);

    int pipefd_stdout[2];
    int ret = pipe(pipefd_stdout);
    assert(ret != -1);

    int pipefd_file[2];
    ret = pipe(pipefd_file);
    assert(ret != -1);

    // 将标准输入内容输入管道pipefd_stdout
    ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    // 将管道pipefd_stdout的输出重定向到文件描述符fd上,从而将标准输入的内容写入文件
    ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK);
    assert(ret != -1);
    // 将管道pipefd_file的输出重定向到文件描述符filefd上,从而将标准输入的内容写入文件
    ret = splice(pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);
    // 将管道pipefd_stdout的输出重定向到标准输出,去内容和写入文件的内容完全一致
    ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);

    close(filefd);
    close(pipefd_stdout[0]);
    close(pipefd_stdout[1]);
    close(pipefd_file[0]);
    close(pipefd_file[1]);
    return 0;
}

fcntl函数正如其名字file control描述的那样,提供了对文件描述符的各种控制,另一个常见的控制文件描述符属性和行为的系统调用是ioctl,且ioctl函数比fcntl函数能执行更多的控制,但控制文件描述符的常用属性和操作,fcntl函数是POSIX规范指定的首选方法:
在这里插入图片描述
fd参数是被操作的文件描述符,cmd参数指定执行何种类型的操作,根据操作类型的不同,该函数可能还需要第三个可选参数。fcntl函数支持的常用操作及其参数见下表:
在这里插入图片描述
在这里插入图片描述
fcntl函数成功时返回值见上表最后一列,失败时返回-1并设置errno。

网络编程中,fcntl函数通常用来将一个文件描述符设为非阻塞的:

int setnonblocking(int fd) {
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;    // 返回旧的状态标志,以便日后恢复该状态标志
}

SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联才能使用。当被关联的文件描述符可读或可写时,系统将触发SIGIO信号;当被关联的文件描述符(必须是一个socket)上有带外数据可读时,系统将触发SIGURG信号。将信号和文件描述符关联的方法是使用fcntl函数为目标文件描述符指定宿主进程或进程组,则被指定的宿主进程或进程组将捕获到这两个信号。使用SIGIO时,还需要用fcntl函数设置套接字描述符的O_ASYNC标志(异步IO标志,但SIGIO信号模型并非真正意义上的异步IO模型)。

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

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

相关文章

用Python爬取短视频列表

短视频是一款备受欢迎的短视频分享平台&#xff0c;每天都有大量精彩的视频内容等待我们去探索。在本文中&#xff0c;我们将分享如何使用Python爬取短视频的视频列表&#xff0c;让您能够发现更多有趣的视频。 一、安装必要的库 在开始之前&#xff0c;确保已安装以下库&…

Unity——对象池

对象池是一种朴素的优化思想。在遇到需要大量创建和销毁同类物体的情景时&#xff0c;可以考虑使用对象池技术优化游戏性能。 一、为什么要使用对象池 在很多类型的游戏中都会创建和销毁大量同样类型的物体。例如&#xff0c;飞行射击游戏中有大量子弹&#xff0c;某些动作游戏…

java用easyexcel按模版导出

首先在项目的resources下面建一个template包&#xff0c;之后在下面创建一个模版&#xff0c;模版格式如下&#xff1a; 名称为 financeReportBillStandardTemplateExcel.xlsx&#xff1a; {.fee}类型的属性值&#xff0c;是下面实体类的属性&#xff0c;要注意这里面的格式&a…

iPhone15拉胯,国产手机用折叠屏大反攻!

文 | 智能相对论 作者 | K星 iPhone偷懒式的15代机型发布&#xff0c;让市场大跌眼镜&#xff0c;虽然在莫名的宗教虔诚下依旧卖得很好&#xff0c;但苹果走下神坛之巅已经板上钉钉。 但是&#xff0c;苹果从最高处摔落&#xff0c;却依旧在神坛之上&#xff0c;iPhone15拉胯…

【STM32笔记】HAL库定时器捕获配置、操作及通用函数定义

【STM32笔记】HAL库定时器捕获配置、操作及通用函数定义 文章目录 定时器捕获设置输入捕获滤波器设置输入捕获极性设置输入捕获映射关系设置输入捕获分频器 定时器配置定时器捕获函数全局变量定时器回调和定时器捕获回调频率计算 附录&#xff1a;Cortex-M架构的SysTick系统定…

css自学框架之二级下拉菜单

下拉菜单是我们开发中最常见&#xff0c;最常用的&#xff0c;今天我们就自学二级下来菜单。首先看一下最终效果&#xff1a; 一、css代码 .arrow-down::before {content: ""; width: 10px;height: 10px;border: solid ;border-width: 2px 2px 0 0;border-color: …

BIGEMAP在土地规划中的应用

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 1.使用软件一般都用于套坐标&#xff0c;比如我们常见的&#xff08;kml shp CAD等土建规划图纸&#xff09;以及一些项目厂区红线&#xff0c;方便于项目选址和居民建…

软考复习 -- 计算机网络

1 网络互连设备 物理层&#xff1a;中继器和集线器&#xff08;多路中继器&#xff09;数据链路层&#xff1a;网桥和交换机&#xff08;多端口网桥&#xff09;网络层&#xff1a;路由器应用层&#xff1a;网关 2 广播域和冲突域 3 协议簇 4 网际层协议 4 TCP和UDP 4.1 TC…

零基础学前端(五)HTML+CSS实战:模仿百度网站首页

1. 该篇适用于从零基础学习前端的小白 2. 初学者不懂代码得含义也要坚持模仿逐行敲代码&#xff0c;以身体感悟带动头脑去理解新知识 一、实战&#xff1a;将百度网站首页补全 上一篇零基础学前端&#xff08;三&#xff09;重点讲解 HTML-CSDN博客我们已经将顶部两侧内容已经…

人机交互——对话管理

​人机交互中的对话管理主要是指在人机交互过程中&#xff0c;对交互的对话内容和流程进行管理&#xff0c;以实现自然、流畅、高效的交互效果。对话管理包括对话状态追踪、对话策略优化等多个方面。 对话状态追踪是指对当前对话的状态进行跟踪&#xff0c;例如对用户输入的语…

Observability:检测 OpenTelemetry 的推荐指南

作者&#xff1a;Bahubali Shetti OpenTelemetry (OTel) 正在稳步获得广泛的行业采用。 作为主要的云原生计算基金会 (CNCF) 项目之一&#xff0c;其提交数量与 Kubernetes 一样多&#xff0c;它正在获得主要 ISV 和云提供商的支持&#xff0c;为该框架提供支持。 许多来自金融…

ChatGLM GPT原理介绍

图解GPT 除了BERT以外,另一个预训练模型GPT也给NLP领域带来了不少轰动,本节也对GPT做一个详细的讲解。 OpenAI提出的GPT-2模型(https://openai.com/blog/better-language-models/) 能够写出连贯并且高质量的文章,比之前语言模型效果好很多。GPT-2是基于Transformer搭建的,相…

【校招VIP】前端JS之深拷贝和浅拷贝

考点介绍 js中的浅拷贝和深拷贝&#xff0c;只是针对复杂数据类型(Objcet&#xff0c;Array)的复制问题。简单来讲浅拷贝和深拷贝都可以实现在原有对象的基础上再生成一份的作用。但是根据新生成的对象能否影响到原对象可以分为浅拷贝和深拷贝。 前端JS之深拷贝和浅拷贝-相关题…

ArcGIS 10.3软件安装包下载及安装教程!

【软件名称】&#xff1a;ArcGIS 10.3 【安装环境】&#xff1a;Windows 【下载链接 】&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1K5ab7IHMYa23HpmuPkFa1A 提取码&#xff1a;oxbb 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 软件解压码点击原文…

Flink--4、DateStream API(执行环境、源算子、基本转换算子)

星光下的赶路人star的个人主页 注意力的集中&#xff0c;意象的孤立绝缘&#xff0c;便是美感的态度的最大特点 文章目录 1、DataStream API1.1 执行环境&#xff08;Execution Environment&#xff09;1.1.1 创建执行环境 1.2 执行模式&#xff08;Execution Mode&#xff09;…

安装gpu版本的paddle

安装gpu版本的paddle python -m pip install paddlepaddle-gpu2.3.2.post111 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html以上支持cuda11.1版本 其他需求可查阅文档在这里

redis如何清空当前缓存和所有缓存

Windows环境下使用命令行进行redis缓存清理 redis安装目录下输入cmdredis-cli -p 端口号flushdb 清除当前数据库缓存flushall 清除整个redis所有缓存keys * 查看所有key值del key 删除指定索引的值 注意&#xff1a; 我们清空缓存的时候&#xff0c;需要确保redis-…

【ONE·Linux || 进程间通信】

总言 进程间通信&#xff1a;简述进程间通信&#xff0c;介绍一些通信方式&#xff0c;管道通信&#xff08;匿名、名命&#xff09;、共享内存等。 文章目录 总言1、进程间通信简述2、管道2.1、简介2.2、匿名管道2.2.1、匿名管道的原理2.2.2、编码理解&#xff1a;用fork来共…

GIT提示Another git process seems to be running in this repository

解决方法 1、进入项目里面的.git文件里面找到index.lock删除即可。

【漏洞复现】易思智能物流无人值守系统文件上传

本文由掌控安全学院 - 江月 投稿 【产品介绍】 易思无人值守智能物流系统是一款集成了人工智能、机器人技术和物联网技术的创新产品。它能够自主完成货物存储、检索、分拣、装载以及配送等物流作业&#xff0c;帮助企业实现无人值守的智能物流运营&#xff0c;提高效率、降低…