Linux —— Socket编程(三)

news2024/12/27 18:42:31

一、本章重点

1. tcp服务器实现思路,进一步了解和总结相关的接口

2. 了解日志和守护进程

二、tcp服务器核心思路

tcp版的服务器与udp的不同在于,udp是面向数据报传输数据,在数据传输中不需要建立与客户端的链接,直接用recvfrom和sendto这两个接口进行消息的收发,而tcp版的服务器则是面向字节流的,需要与客户端建立连接

1. 创建监听套接字listen_sock

这个监听套接字是用于监听的,监听就可以看做为等待客户端连接,监听套接字只负责与客户端建立起连接,然后剩下的任务交给其他的套接字去执行

            // 1. 创建ListenSock
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                std::cerr << "create socket error" << std::endl;
                exit(SOCKET_ERR);
            }

2. 创建好struct sockaddr_in ,并将其与套接字bind

这里和udp是一样的步骤

            // 2. bind
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind error" << std::endl;
                exit(BIND_ERR);
            }

3. 监听 -- listen

//?

            if (listen(_listensock, backlog) < 0)
            {
                std::cerr << "listen socket error" << std::endl;
                exit(LISTEN_ERR);
            }

4. 获取连接 -- accept

获取连接其实就是获取客户端的连接,它会返回一个套接字,后续的业务处理则是根据这个套接字去实现和完成的

// 1. 获取连接 -- accept
struct sockaddr_in client;
socklen_t len = sizeof(client);
int sock = accept(_listensock, (struct sockaddr *)&client, &len);
if (sock < 0)
{
   std::cerr << "sock error" << std::endl;
   exit(SOCKET_ERR);
}

5. 开展业务处理. 

具体开展的业务处理要采用多进程或者多线程的方式,主线程负责监听连接,而其他线程负责为客户端提供具体服务。tcp的读写可以直接使用以往的文件相关的读写函数,如read、write等等

                // 3. 开展业务处理 -- service
                // 测试
                std::cout << "连接成功:" << sock << " from " << _listensock << "," << client_ip << " - " << client_port << std::endl;
                // service(sock,client_ip,client_port);
                //  业务处理需要和监听同时进行,因为这里的业务处理是阻塞等待客户端的
                //  3.1 多进程方案 -- 如何处理进程等待的阻塞问题?
                // pid_t id = fork();
                // if (id < 0)
                // {
                //     close(sock);
                //     continue;
                //     ;
                // }
                // else if (id == 0) // child
                // {
                //     close(_listensock);
                //     service(sock, client_ip, client_port);
                //     exit(0);
                // }
                // close(sock); -- 线程方案中不能关闭这个
                // 进程等待 -- waitpid
                // 方案一:信号忽略 -- signal(SIGCHLD, SIG_IGN);(推荐)
                // 方案二:信号捕捉 -- signal(SIGCHLD, handler);(麻烦,不太推荐)
                // 方案三:轮询等待 -- WNOHANG(不太推荐)
                // 方案四:孤儿进程 -- if(fork() > 0) exit(0);(不太推荐,但思路很优秀,但是会对系统有负担)

                // pid_t ret = waitpid(id, nullptr, 0);
                // if (ret == id)
                //     std::cout << "wait child: " << id << " success" << std::endl;

                // 3.2 多线程方案 -- 线程池优化
                // pthread_t tid;
                // ThreadData* td = new ThreadData(sock,client_ip,client_port,this);
                // pthread_create(&tid,nullptr,threadRoutine,td);
                Task t(sock, client_ip, client_port, std::bind(&TcpServer::service, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));//?
                ThreadPool<Task>::getinstance()->pushTask(t);

三、Socket编程相关接口整理

1. 网络编程相关的头文件

#include<sys/types.h>   // 包含很多系统数据类型
#include<sys/socket.h>  // 包含了基本的 socket 函数和数据结构定义。
#include<netinet/in.h>  // 定义了 Internet 地址族相关的结构和函数。
#include<arpa/inet.h>   // 提供 IP 地址转换函数。

2. socket -- 创建套接字函数

int socket(int domain, int type, int protocol);

domain:指定通信域,常见的有AF_INET(IPv4网络协议)AF_INET6(IP6网络协议)

type:指定套接字类型,常见的有SOCK_STREAM(面向字节流,对应TCP)和SOCK_DGRAM(面向数据报,对应UDP)

protocol:通常设置为0,表示使用默认协议。

返回值: 一般是文件文件描述符fd,若失败则返回小于0的值

3. struct sockaddr_in

sin_family:第一行的宏转换后就是这个,它指定的是地址族,对于IPv4就设置为AF_INET

sin_port:端口号,要以网络字节序去表示,所以这里一般在填写的时候,还要配合网络字节序的接口htons

sin_addr:里面封装的内容实际就是IP地址,可以使用inet_addr 或 inet_pton函数将点分十进制的 IP 地址字符串转换为合适的格式并存储在 sin_addr.s_addr中。当然这里由于我们用的是云服务器,前面提到云服务器的IP地址不能填唯一确定的,而是这里需要填INADDR_ANY

4. bind  绑定套接字和struct sockaddr的函数接口

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

sockfd : 是通过socket函数创建的套接字描述符。

addr:该结构体的解释在知识点三和知识点四有讲解,对于 IPv4 通常是struct sockaddr_in类型,对于 IPv6 是struct sockaddr_in6类型。这个地址结构包含了要绑定的 IP 地址和端口号等信息。

addrlen:结构体的大小

返回值说明:成功返回0,失败返回-1,并将错误码进行设置

5. recvfrom -- 从套接字中获取信息

int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

sockfd:套接字描述符

buf:接受数据的数据缓冲区指针,用于接受数据的

len:数据缓冲区的大小

flags:一般设置为0,表示默认的接收行为,也可以设置一些标志位来影响接收操作的行为,例如 MSG_PEEK(查看数据但不实际读取,数据仍留在接收队列中)、MSG_WAITALL(尽可能等待接收完整的请求数据量)等,但这些标志位的使用相对较少

from:是一个struct sockaddr*类型的结构体指针,它用来获取发送方的地址信息

fromlen:是一个指向整数的指针,用于指定 from 所指向的地址结构的长度。在调用 recvfrom 之前,应该将 *fromlen 设置为 sizeof(struct sockaddr)(或对应的具体地址结构的大小);当 recvfrom 返回时,fromlen 会被修改为实际存储在 from 中的地址结构的长度

返回值说明:成功返回收到的字节数,返回值为0说明对端已经关闭了连接,返回-1表示接受数据发生了错误

6. sendto -- 向套接字中写入信息

int sendto(int sockfd, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);

sockfd:是通过socket函数创建的套接字描述符

buf:要向套接字发送的数据所在的缓冲区指针

len:指定要发生内容的长度

flags: 一般设置为 0,表示默认的发送行为。也可以设置一些标志位来影响发送操作的行为,不过这些标志位的使用相对较少

to:要发送到的目的地的struct sockadd的指针

tolen:to的大小

7. 网络字节序接口

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);//将源主机中32位长整形转换成网络字节序要求的大端格式
uint16_t htons(uint16_t hostshort);//将源主机中16位短整形转换成网络字节序要求的大端格式
uint32_t ntohl(uint32_t netlong);//将网络中32位长整形(大端)转换成当前主机的格式
uint16_t ntohs(uint16_t netshort);//将网络中16位短整形(大端)转换成当前主机的格式

8. listen 监听

int listen(int sockfd, int backlog);

它用于将一个套接字标记为被动套接字,也就是将其设置为用于监听连接请求的状态。这个套接字通常是由socket函数创建并经过bind函数绑定到一个本地地址和端口之后使用。

  • sockfd:是由socket函数创建并经过bind绑定后的套接字描述符。
  • backlog:指定了在拒绝连接之前,操作系统可以暂存的未完成连接请求的最大数量。它实际上定义了连接请求队列的长度。当有多个客户端同时尝试连接服务器时,这些连接请求会被放入这个队列中等待服务器处理。如果队列已满,新的连接请求可能会被拒绝(具体行为可能因操作系统而异)。
  • 返回值:如果listen函数调用成功,则返回 0;如果失败,则返回 -1,并设置相应的错误代码,可以通过perror等函数查看错误信息

9. accept 连接

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

它用于从处于监听状态的套接字的连接请求队列中取出一个连接请求,并创建一个新的套接字来与客户端进行通信。这个新的套接字与原来监听的套接字不同,它是专门用于和客户端进行数据交互的。

  • sockfd:仍然是最初由socket函数创建并经过bindlisten操作的监听套接字描述符。
  • addr:是一个指向sockaddr结构体的指针,用于存储客户端的地址信息。如果不需要获取客户端地址信息,可以将其设置为NULL
  • addrlen:是一个指向socklen_t类型变量的指针,它用于在函数调用前传入addr所指向的结构体的长度,在函数返回时,它会被设置为实际存储客户端地址信息所占用的长度。
  • 返回值:如果accept函数调用成功,则返回一个新的套接字描述符,这个描述符用于与客户端进行后续的通信;如果失败,则返回 -1,并设置相应的错误代码,可以通过perror等函数查看错误信息。

四、日志

往往在项目工程中,我们会有需要不同的调试信息,以及可能一些需要标记或者记录的信息,例如用户连接成功的信息,用户的消息记录等等,这些我们之前都是直接打印在屏幕上的,但实际在工程项目中,我们都需要将这些信息打印到文件中进行管理,这里我们简单实现一下应该粗糙的日志。

1. 可变参数

在写日志代码前,还需要对可变参数有一些了解,像C语言中的printf的实现就是用可变参数的方式实现,那么我们要如何使用呢?

#include <cstdarg> // 头文件

void func(const char *format,...) // 可变参数前,至少要有一个确定的参数format

va_list p; // p变量本质是一个char*的指针

va_start(p, format); // p指向可变参数部分的起始地址

int a = va_arg(p, int);  // 根据类型提取参数

va_end(p); // p = NULL;

以上是对于可变参数的预备知识,我们要用可变参数的是为了让打印日志信息函数中,可以让外部传入的日志信息可以是多样的,像printf一样使用,所以可以用到一个函数帮助我们

int vsnprintf(char *str, size_t size, const char *format, va_list ap);

str是数据缓冲区

size是缓冲区大小

format是一个格式化字符串,用于指定输出的格式,其格式规则与printf函数中的格式化字符串类似,包含普通字符和格式转换说明符(如%d%f%s等)

ap是一个va_list类型的变量,用于传递可变参数列表。它通常是由va_start宏初始化后得到的

2. 参考代码

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>

// 日志是有日志等级的

const std::string filename = "log/tcpserver.log";

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal,
    Uknown
};

static std::string toLevelString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warning:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    default:
        return "Uknown";
    }
}

static std::string getTime()
{
    time_t curr = time(nullptr);
    struct tm *tmp = localtime(&curr);
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon+1, tmp->tm_mday,
             tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    return buffer;
}

// 日志格式: 日志等级 时间 pid 消息体
// logMessage(DEBUG, "hello: %d, %s", 12, s.c_str()); // DEBUG hello:12, world
void logMessage(int level, const char *format, ...)
{
    char logLeft[1024];
    std::string level_string = toLevelString(level);
    std::string curr_time = getTime();
    snprintf(logLeft, sizeof(logLeft), "[%s] [%s] [%d] ", level_string.c_str(), curr_time.c_str(), getpid());

    char logRight[1024];
    va_list p;
    va_start(p, format);
    vsnprintf(logRight, sizeof(logRight), format, p);
    va_end(p);

    // 打印
    // printf("%s%s\n", logLeft, logRight);

    // 保存到文件中
    FILE *fp = fopen(filename.c_str(), "a");
    if(fp == nullptr)return;
    fprintf(fp,"%s%s\n", logLeft, logRight);
    fflush(fp); //可写也可以不写
    fclose(fp);


    // 预备
    //  va_list p; // char *
    //  int a = va_arg(p, int);  // 根据类型提取参数
    //  va_start(p, format); //p指向可变参数部分的起始地址
    //  va_end(p); // p = NULL;
}

五、守护进程

1. 概念

要理解守护进程,我们先需要知道一些关于Liunx系统的概念。

首先要理解关于进程、进程组、会话,这三个概念

  1. 进程是基础
    进程是计算机系统中正在运行的程序的实例,是操作系统进行资源分配和调度的基本单位。它拥有自己独立的内存空间和系统资源,有自己的生命周期。
  2. 进程组是相关进程的集合
    进程组由一个共同的祖先进程创建的多个相关进程组成。这些进程在某些行为上具有一致性,比如共享一个进程组 ID,对某些信号的处理方式相同。一个进程可以属于一个进程组。
  3. 会话是包含多个进程组的逻辑概念
    会话通常与一个控制终端相关联,当用户登录系统时创建。一个会话可以包含多个不同类型的进程组,这些进程组中的进程对于某些信号的处理方式是相关的。会话支持作业控制操作。
  4. 总体关系
    一个进程可以属于一个进程组,一个进程组可以属于一个会话。从范围上看,会话包含多个进程组,进程组包含多个相关进程。

我们现在使用的Xshell去登录远端的Linux服务器,每次登录其实是在整个Linux系统上启动了一个会话,而bash进程作为首个登入的进程,同时也是对应进程组的组长

而守护进程就是,我们要将原先我们在该会话中写的进程,将其独立出去作为一个单独的会话层面的进程,让它在后台运行,一般的服务器都是如此,这样就不会因为我们会话关闭而服务器挂掉。

守护进程要实现还有几个条件:

  1. 与控制终端脱离
    • 守护进程需要与任何控制终端脱离关系。这是因为如果守护进程依赖于控制终端,当终端关闭时(例如用户退出登录),守护进程可能会收到一些信号(如 SIGHUP)导致其异常终止。通常通过调用setsid函数来创建一个新的会话,使守护进程成为新会话的首进程,从而脱离原来的控制终端。
  2. 改变工作目录
    • 守护进程一般会改变其工作目录到根目录(/)或者其他合适的目录。这是为了避免因为工作目录所在的文件系统被卸载等原因导致守护进程出现问题。例如,如果守护进程的工作目录在一个用户可卸载的文件系统上,当该文件系统被卸载时,守护进程可能无法正常访问其工作目录中的文件。
  3. 重设文件描述符
    • 守护进程需要关闭所有不需要的文件描述符。在进程创建时,会继承父进程的一些文件描述符,如果不关闭这些不必要的文件描述符,可能会导致资源浪费以及潜在的问题。例如,守护进程可能会因为某个打开的文件描述符指向一个已经不存在的文件(如终端相关的文件描述符)而出现错误。通常会关闭标准输入、标准输出和标准辅助输入输出等文件描述符(0、1、2),并可以根据需要重新打开一些文件描述符用于日志记录等用途。
  4. 设置合适的文件权限掩码
    • 守护进程通常会设置合适的文件权限掩码(umask)。文件权限掩码决定了新创建文件的默认权限。通过设置合适的文件权限掩码,可以确保守护进程创建的文件具有合适的权限,避免出现安全问题或文件无法正常使用的情况。
  5. 忽略某些信号
    • 守护进程需要忽略一些信号,比如 SIGHUP 信号。因为守护进程已经与控制终端脱离,如果不忽略 SIGHUP 信号,当终端关闭时,守护进程可能会收到该信号并错误地终止。同时,守护进程也可能需要忽略其他一些不相关的信号,以确保其稳定运行。

2. 参考代码

#pragma once
// 1. setsid();
// 2. setsid(), 调用进程,不能是组长!我们怎么保证自己不是组长呢?
// 3. 守护进程a. 忽略异常信号 b. 0,1,2要做特殊处理 c. 进程的工作路径可能要更改 /

#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "log.hpp"
#include "err.hpp"

//守护进程的本质:是孤儿进程的一种!
void Daemon()
{
    // 1. 忽略信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    // 2. 让自己不要成为组长
    if (fork() > 0)
        exit(0);
    // 3. 新建会话,自己成为会话的话首进程
    pid_t ret = setsid();
    if ((int)ret == -1)
    {
        logMessage(Fatal, "deamon error, code: %d, string: %s", errno, strerror(errno));
        exit(SETSID_ERR);
    }
    // 4. 可选:可以更改守护进程的工作路径
    // chdir("/")
    // 5. 处理后续的对于0,1,2的问题
    int fd = open("/dev/null", O_RDWR);
    if (fd < 0)
    {
        logMessage(Fatal, "open error, code: %d, string: %s", errno, strerror(errno));
        exit(OPEN_ERR);
    }
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}

总结

本篇文章主要是整理了Socket编程常见的接口,还有对tcp服务器相关的概念知识点

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

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

相关文章

GEE数据集:1996 年到 2020 年全球红树林观测数据集(JAXA)(更新)

目录 简介 数据集说明 数据集 代码 代码链接 结果 引用 许可 网址推荐 0代码在线构建地图应用 机器学习 简介 全球红树林观测 这项研究使用了日本宇宙航空研究开发机构&#xff08;JAXA&#xff09;提供的 L 波段合成孔径雷达&#xff08;SAR&#xff09;全球mask…

银河麒麟服务器:更新软件源

银河麒麟服务器&#xff1a;更新软件源 1、使用场景2、操作步骤3、注意事项 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 1、使用场景 当需要安装最新软件或修改软件源配置后&#xff0c;需更新软件源以获取最新软件包信息。 2、操作步…

<<迷雾>> 第5章 从逻辑学到逻辑电路(2)--非门 示例电路

一个应用非门的例子 info::操作说明 鼠标单击开关切换开合状态 primary::在线交互操作链接 https://cc.xiaogd.net/?startCircuitLinkhttps://book.xiaogd.net/cyjsjdmw-examples/assets/circuit/cyjsjdmw-ch05-05-not-gate-sample.txt 原图 一个自带电源的常闭触点继电器属于…

基于定制开发与2+1链动模式的商城小程序搭建策略

摘要&#xff1a;本文探讨商城小程序的搭建策略&#xff0c;对比自主组建团队和第三方开发两种方式&#xff0c;强调以第三方开发模式为主的优势。阐述在第三方开发模式下&#xff0c;结合定制开发和21链动模式&#xff0c;如何搭建一款有助于企业商业模式创新与智能商业升级的…

化工企业大文件传输软件该怎么选?

化工行业里&#xff0c;数据的迅速、安全传递对于企业的研发、生产和供应链管理是至关重要的。随着数据量的不断增长和网络环境的日益复杂&#xff0c;传统的文件传输方法已经无法满足化工企业的需求。接下来&#xff0c;我将带领大家一起探讨化工企业在进行大文件传输时所面临…

linux驱动编程——标准、混杂、中断

一、优化——自动申请设备号、自动创建节点 设备号类型&#xff1a;①主设备号 ②子设备号 类型&#xff1a;unsigned int <>dev_t 12 major &#xff08;主设备号&#xff09; 20 minor &#xff08;子设备号&#xff09;<区…

【课程总结】day29:大模型之深入了解Retrievers解析器

前言 在上一章【课程总结】day28:大模型之深入探索RAG流程中,我们对RAG流程中 文档读取(LOAD) -> 文档切分(SPLIT) -> 向量化(EMBED) -> 存储(STORE) 进行了深入了解,本章将接着深入了解 解析(Retrieval) 的使用 解析器简介 简介:在 RAG(Retrieval-Augmented G…

墙绘产品交易平台:SpringBoot技术实现

4 系统设计 墙绘产品展示交易平台的设计方案比如功能框架的设计&#xff0c;比如数据库的设计的好坏也就决定了该系统在开发层面是否高效&#xff0c;以及在系统维护层面是否容易维护和升级&#xff0c;因为在系统实现阶段是需要考虑用户的所有需求&#xff0c;要是在设计阶段没…

矩阵奇异值

一、ATA 任给一个矩阵A&#xff0c;都有&#xff1a; ATA 为一个对称矩阵 例子&#xff1a;A为一个mn的矩阵&#xff0c;A的转置为一个nm的矩阵 对称矩阵的重要性质如下&#xff1a; ① 对称矩阵的特征值全为实数&#xff08;实数特征根&#xff09; ② 任意一个n阶对称矩阵…

思科dhcp的配置

以路由器为例 让pc3 自动获取ip地址并获取的网段为172.16.4.100-172.16.4.200 配置如下&#xff1a; R1(config)#interface GigabitEthernet0/2 R1(config)#ip address 172.16.4.254 255.255.255.0 R1(config)# no shutdown R1(config)#ip dhcp pool 4_pool //创建dhcp地址池…

实际有库存却提示可用量不足保存不了杂发单

财务要统计研发费用&#xff0c;成本的金额。研发人员没有足够的意识配合。开立请购单时兴之所致&#xff0c;任性自由。想弄一个项目号就弄一个。不开心就没有项目号啦。哪管他人死活。 U9的逻辑&#xff0c;请购单如果带入项目号&#xff08;客制化的功能&#xff09;&#x…

c语言200例 067

大家好&#xff0c;欢迎来到无限大的频道 今天给大家带来的是c语言200例 题目要求&#xff1a; 设计一个共用体类型&#xff0c;使其成员包含多种数据类型&#xff0c;根据不同的数据类型&#xff0c;输出不同的结果 要设计一个共用体&#xff08;union&#xff09;类型&…

如何判断主机字节序

测试代码: #include <stdio.h> void byteorder() {union{short value;char union_bytes[sizeof(short)];//union_bytes数组}test;test.value 0x0102;if((test.union_bytes[0] 1) && (test.union_bytes[1]2)){printf("big endian\n");}else if((test…

初识Java反序列化漏洞

目录 为什么需要序列化&#xff1f; 序列化与反序列化基础案例 Serializable 接口 序列化对象 反序列化对象 Java 反序列化漏洞 readObject() 序列化&#xff1a;将对象的状态信息转换为可以存储或传输的形式的过程&#xff0c;即将对象转换为字节序列。反序列化&#x…

如何突破科技服务领域的客户管理困境?

在知识产权与科技服务领域&#xff0c;企业面临着独特的客户管理需求和挑战&#xff0c;这些挑战不仅要求高度的专业性和精细化操作&#xff0c;还涉及复杂的法律流程、数据保密性以及不断变化的客户需求。传统的客户管理方式&#xff0c;如纸质档案、简单的电子表格或人工处理…

[教程]Crystal源码下载及编译

描述&#xff1a; 随着 Crystal Source 代码的更新&#xff0c;用于构建源代码和编译它们的指南已经过时&#xff0c;这导致了很多混淆和寻求帮助。 本指南将是一个完整的分步指南&#xff0c;从下载 Visual Studio 到启动到您的服务器。 此外&#xff0c;请确保下载此存储库中…

如何使用 Python 读取数据量庞大的 excel 文件

使用 pandas.read_excel 读取大文件时&#xff0c;的确会遇到性能瓶颈&#xff0c;特别是对于10万行20列这种规模的 .xlsx 文件&#xff0c;常规的 pandas 方法可能会比较慢。 要提高读取速度&#xff0c;关键是找到更高效的方式处理 Excel 文件&#xff0c;特别是在 Python 的…

Docker版MKVtoolnix的安装及中文显示

本文是应网友 kkkhi 要求折腾的&#xff0c;只研究了 MKVtoolnix 的安装及中文显示&#xff0c;未涉及到软件的使用&#xff1b; 什么是 MKVtoolnix &#xff1f; MKVToolnix 是一款功能强大的多媒体处理工具&#xff0c;用于在 Linux、其他 Unix 系统和 Windows 上创建、修改和…

uniapp/vue项目 import 导入文件时提示Module is not installed,‘@/views/xxx‘路径无法追踪

文章目录 背景解决方案1.IDE配置2.alias&#xff08;别名&#xff09;配置webpackvue-clivite 3.检查 jsconfig.json 或 tsconfig.json 写在最后 前往闪闪の小窝以获得更好的阅读和评论体验 背景 Vue3在我自学Vue的时候看过一点&#xff0c;实操过一点&#xff0c;但是太久没用…

css 下拉框展示:当hover的时候展示下拉框 z-index的用法解释

代码如下&#xff1a; <template><div class"outer"><div class"left"></div><div class"aTest2"><div class"box">显示方框</div><div class"aTest3"></div></…