Linux网络编程 --- 高级IO

news2024/11/18 11:35:35

前言 IO Input&&Output

read && write

1、在应用层read && write的时候,本质把数据从用户层写给OS --- 本质就是拷贝函数

2、IO = 等待  + 拷贝。

等的是:要进行拷贝,必须先判断读写事件成立。读写事件缓冲区空间满了没有,read的时候需要缓冲区有数据。

什么叫做高效的IO呢?

单位事件内我们拷贝的数据越多,越有效率。单位事件内,IO过程中,等的比重越小,IO效率就越高。几乎所有的提高IO效率的策略,本质就是减少等的时间。

一、五种IO模型

1、张三:钓鱼界的新手,一直盯着鱼钩。谁都不理,有鱼上钩,提起鱼竿。阻塞式钓鱼。

2、李四:每隔一小时定盯着鱼漂。不会卡在那里。会看手机什么的。非阻塞式钓鱼。非阻塞轮询

3、王五:鱼竿上放一个铃铛,不会卡在那里,会看手机什么的。鱼钩上有鱼了铃铛响了。信号驱动式IO

以上鱼上钩的概率不如下面的。

4、赵六:一卡车鱼竿。轮询每一个鱼竿。多路复用,多路转接。

以上的IO称为异步IO

5、田七:小王,桶,一个电话,鱼竿。田七就走了,小王开始钓鱼。田七就是钓鱼行为的发起者。田七的钓鱼方式称为异步IO。小王为操作系统。

阻塞IO vs 非阻塞IO  IO = 等待 + 拷贝。非阻塞不用一直等。他们等的方式不同!

同步IO vs 异步IO 张三、李四,王五,赵六都在等待,同步就是参不参与IO。参与就是同步,不参与只是发起IO就是异步。

同步IO 和线程同步?不一样。老婆和老婆饼的关系,没有任何关系。

其中多路复用式最有效率的。异步IO的代码逻辑比较混乱,已经有新的技术代替了,比如协程。

非阻塞接口

1、设置称为非阻塞,如果底层fd数据没有就绪,recv/read/write/send,返回值会以出错的形式返回。

2、a、真的出错了, b、底层没有就绪!

3、我们通过errno区分

如果errno == EWOULDBLOCK,当前不是错了, 而是fd中的数据没有就绪。
 

IO多路转接至select 

IO = 等待 + 拷贝

select:只负责等待,而且可以等待多个文件描述符。 

返回值:n  > 0 有n个fd就绪了

               n == 0 超时,没有错误,但是也没有fd就绪。

               n < 0 等待出错。 

第一个参数 nfds maxfd + 1。最大的等待文件描述符的值 + 1。

struct timeval timeout 给select设置等待方式。就是你设置的是5秒超时,那么就是进程会在select函数阻塞等待5秒,要是5秒中没有事件就绪,他会返回要是5秒中,有事件就绪了,他也会立即返回

timeout = {5,0};每隔5s,timeout一次。在5s之内没有文件描述符就绪,返回。

             = {0,0};立马返回,非阻塞的一种。

NULL:阻塞等待。

上面的timeout是输入输出型参数。等待了2秒,如果我们设置的是3秒。select会把这个三秒返回。

  fd_set 内核提供的一种数据类型。我们目前关心的fd上面的事件。读事件成立,写事件成立,异常事件。

fd_set* readfds 输入输出型参数,

输入时:用户告诉内核,我给你的一个或者多个fd,你要帮我关心fd上面的读事件,如果读时间就绪了,你要告诉我。

输出时:内核告诉用户,用户你让我关心的多个fd中,有哪些已经就绪了,用户赶紧读取吧。

从右向左比特位的位置,标识文件描述符编号,比特位的内容0,1是否需要内核关心。

其他的位图也是这样的。

返回时:比特位的内容0 or 1,用户关心的哪些fd,上面的读时间已经就绪了。

fd_set 是一张位图,让用户和内核传递fd是否就绪的信息的!

会有很多的位图操作。所以内核提供了很多的文件描述位图的操作。

 
直接写代码:

#include <iostream>
#include "log.hpp"
#include "Sock.hpp"
#include <sys/select.h>
#include <ctime>
static const int fd_num_max = sizeof(fd_set) * 8;
static const uint16_t defaultport = 8080;
int defaultfd = -1;
class selectserver
{
public:
    selectserver(uint16_t port = defaultport) : _port(port)
    {
        // 初始化数组
        for (int i = 0; i < fd_num_max; i++)
        {
            fd_array[i] = defaultfd;
        }
    }

    // 初始化服务器,这里应该绑定监听端口,创建套接字
    void Init()
    {
        // 绑定监听套接字。
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
    }
    void Accepter()
    {
        // 接收套接字
        std::string clientip;
        uint16_t clientport = 0;
        int sockfd = _listensock.Accept(&clientip, &clientport); // 这里不会被阻塞
        if (sockfd < 0)
            return;
        lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sockfd);

        // 如果大于0 把新接收的套接字放入到辅助数组中
        int pos = 1; // fd_array[0] = listensock,所以要从1开始
        for (; pos < fd_num_max; pos++)
        {
            // 寻找-1位置
            if (fd_array[pos] != defaultfd)
                continue;
            else
                break;
        }
        if (pos == fd_num_max)
        {
            lg(Warning, "server is full, close %d now!", sockfd);
            close(sockfd);
        }
        else
        {
            fd_array[pos] = sockfd;
            printFd();
        }
    }
    void Recvr(int fd, int pos)
    {
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a messge: " << buffer << std::endl;
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
            fd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
    }
    void Dispatcher(fd_set &rfds)
    {
        // 在这里我们需要对事件进行处理,从listensock套接字里连接的文件描述符也需要被我们的select等待,我们需要一个辅助
        // 数组。
        for (int i = 0; i < fd_num_max; i++)
        {
            int fd = fd_array[i];
            //std::cout << fd << std::endl;
            if (fd == defaultfd)
                continue;
            if (FD_ISSET(fd, &rfds))
            {
                if (fd == _listensock.Fd())
                {
                    Accepter();
                    std::cout << "新文件描述符完毕" << std::endl;
                }
                else
                {
                    // 不是listen套接字那就是普通的套接字进行读。
                    std::cout << "开始交互" << std::endl;
                    Recvr(fd, i);
                    
                }
            }
        }
    }
    void Start()
    {
        // 这里应该为服务器提供服务。这里我们使用select接口,让文件描述符等待连接。
        // 服务器是一个死循环。
        // 在这里我们需要把listensock的文件描述符放入到我们的辅助数组里。
        int listensock = _listensock.Fd();
        fd_array[0] = listensock;
        std::cout << fd_array[0] << std::endl;
        for (;;)
        {
            // 这里需要对listensock套接字进行等待。等待是否有链接,有连接说明该文件描述符上有事件就绪。
            // 传入的参数需要有一个位图,这个位图是输入输出型参数,用户设置需要等待的文件描述符,内核把
            // 事件就绪的文件描述符放入到其中。每次事件就绪的文件描述符,就会覆盖原来的文件描述符的问题,所以我们
            // 需要在select之前对位图重新设置。
            fd_set rfds;
            FD_ZERO(&rfds);
            // 边循环边设置我们的文件描述符到位图中,然后求出文件描述符中最大的值。
            int maxfd = fd_array[0];
            for (int i = 0; i < fd_num_max; i++)
            {
                if (fd_array[i] == defaultfd)
                    continue;
                // 找到文件描述符设置到我们的描述符位图中
                FD_SET(fd_array[i], &rfds);
                if (maxfd < fd_array[i])
                {
                    maxfd = fd_array[i];
                    lg(Info, "max fd update, max fd is: %d", maxfd);
                }
            }

            // 把listensock文件描述符设置到位图中
            // FD_SET(_listensock.Fd(), &rfds);
            // 设置等待的事件
            // struct timeval timeout = {5, 0};
            struct timeval timeout = {0, 0};
            int n = select(/*文件描述符中值最大的一个 + 1 _listensock.Fd() + 1*/ maxfd + 1, &rfds, /*不关心写事件,只关心读事件*/ nullptr, nullptr, /*&timeout*/ nullptr);
            // 判断返回值 = 0代表没有事件就绪
            //  -1 等待错误
            //> 0 有n个文件描述符的写事件就绪
            switch (n)
            {
            case 0:
                std::cout << "time out,timeout: " << timeout.tv_sec << timeout.tv_usec << std::endl;
                break;
            case -1:
                std::cerr << "select err" << std::endl;
                break;
            default:
                // 等待成功需要做什么。
                std::cout << "进入事件派发器" << std::endl;
                Dispatcher(rfds);
                break;
            }
        }
    }
    void printFd()
    {
        std::cout << "online list:";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (fd_array[i] == defaultfd)
            {
                continue;
            }
            std::cout << fd_array[i] << " ";
        }
        std::cout << std::endl;
    }
    ~selectserver()
    {
        _listensock.Close();
    }

private:
    Sock _listensock;
    uint16_t _port;
    int fd_array[fd_num_max];
};

不能直接accept,检测并获取listensock上面的事件,新连接到来,等价于读事件就绪。

select,如果事件就绪,如果上层不处理,select会一直通知。select告诉你就绪了,接下来的一次读取,我们读取fd的时候,不会阻塞。

位图的大小为1024个比特位,所以一次可以等待1024个文件描述符。 

select的优点是:多路转接的方案。一个进程处理多个用户的连接。

select的缺点是:

1、等待的fd是有上限的。

2、输入输出型参数比较多,数据拷贝的频率比较高。

3、输入输出型参数比较多,每次都要对关心的fd进行事件重置。

4、使用第三方数组管理用户fd,用户层需要很多次遍历,内核中检测fd事件就绪,也要遍历。

IO多路转接之poll

#include <iostream>
#include "log.hpp"
#include "Sock.hpp"
#include <poll.h>
#include <ctime>
static const int fd_num_max = 64;
static const uint16_t defaultport = 8080;
int defaultfd = -1;
int non_event = 0;
class pollserver
{
public:
    pollserver(uint16_t port = defaultport) : _port(port)
    {
        // 初始化数组
        for (int i = 0; i < fd_num_max; i++)
        {
            _fd_events[i].fd = defaultfd;
            _fd_events[i].events = non_event;
            _fd_events[i].events = non_event;
        }
    }

    // 初始化服务器,这里应该绑定监听端口,创建套接字
    void Init()
    {
        // 绑定监听套接字。
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
    }
    void Accepter()
    {
        // 接收套接字
        std::string clientip;
        uint16_t clientport = 0;
        int sockfd = _listensock.Accept(&clientip, &clientport); // 这里不会被阻塞
        if (sockfd < 0)
            return;
        lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sockfd);

        // 如果大于0 把新接收的套接字放入到辅助数组中
        int pos = 1; // fd_array[0] = listensock,所以要从1开始
        for (; pos < fd_num_max; pos++)
        {
            // 寻找-1位置
            if (_fd_events[pos].fd != defaultfd)
                continue;
            else
                break;
        }
        if (pos == fd_num_max)
        {
            lg(Warning, "server is full, close %d now!", sockfd);
            close(sockfd);
        }
        else
        {
            _fd_events[pos].fd = sockfd;
            _fd_events[pos].events = POLLIN;
            _fd_events[pos].revents = non_event;
            printFd();
        }
    }
    void Recvr(int fd, int pos)
    {
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a messge: " << buffer << std::endl;
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            _fd_events[pos].fd= defaultfd; // 这里本质是从select中移除
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
             _fd_events[pos].fd= defaultfd; // 这里本质是从select中移除
        }
    }
    void Dispatcher()
    {
        // 在这里我们需要对事件进行处理,从listensock套接字里连接的文件描述符也需要被我们的select等待,我们需要一个辅助
        // 数组。
        for (int i = 0; i < fd_num_max; i++)
        {
            int fd = _fd_events[i].fd;
            //std::cout << fd << std::endl;
            if (fd == defaultfd)
                continue;
            //读事件就绪,且是监听sock
            if (_fd_events[i].revents & POLLIN)
            {
                if (fd == _listensock.Fd())
                {
                    Accepter();
                    std::cout << "新文件描述符完毕" << std::endl;
                }
                else
                {
                    // 不是listen套接字那就是普通的套接字进行读。
                    std::cout << "开始交互" << std::endl;
                    Recvr(fd, i);
                    
                }
            }
        }
    }
    void Start()
    {
        // 这里应该为服务器提供服务。这里我们使用select接口,让文件描述符等待连接。
        // 服务器是一个死循环。
        // 在这里我们需要把listensock的文件描述符放入到我们的辅助数组里。
        int listensock = _listensock.Fd();
        for (;;)
        {
            //把listenfd交给poll管理
            _fd_events[0].fd = listensock;
            _fd_events[0].events = POLLIN;
            _fd_events[0].revents = non_event;

            //设置超时事件3000毫秒
            int timeout = 3000;
            ssize_t n = poll(_fd_events,fd_num_max,timeout);
            switch (n)
            {
            case 0:
                std::cout << "timeout" << std::endl;
                break;
            case -1:
                std::cerr << "poll err" << std::endl;
                break;
            default:
                // 等待成功需要做什么。
                std::cout << "进入事件派发器" << std::endl;
                Dispatcher();
                break;
            }
        }
    }
    void printFd()
    {
        std::cout << "online list:";
        for (int i = 0; i < fd_num_max; i++)
        {
            if ( _fd_events[i].fd == defaultfd)
            {
                continue;
            }
            std::cout << _fd_events[i].fd << " ";
        }
        std::cout << std::endl;
    }
    ~pollserver()
    {
        _listensock.Close();
    }

private:
    Sock _listensock;
    uint16_t _port;
    struct pollfd _fd_events[fd_num_max];
};

poll只负责等待。

timeout 证书毫秒。

struct pollfd

nfds_t  struct pollfd的数组大小。

将输入输出事件进行了分离。

poll的缺点:

1、遍历,用户层和内核也得遍历。效率问题。

IO多路转接之epoll

1、快速认识epoll的接口

epoll_create:

参数被忽略,设置成大于0就可以。返回值是一个文件描述符。

对epoll新增描述特定的读写事件

epoll_wait:

参数返回已经就绪的fd和事件。

已经就绪的fd的个数。

epoll_event结构体:

epoll_ctl

第一个参数epoll_create的返回值,第二个参数三个选项,第三个参数需要被控制的fd,第四个参数哪些事件被设置。

它不同于 select() 是在监听事件时告诉内核要监听什么类型的事件 , 而是在这里先注册要监听的事件类型 .
第一个参数是 epoll_create() 的返回值 (epoll 的句柄 ).
第二个参数表示动作,用三个宏来表示 .
第三个参数是需要监听的 fd.
第四个参数是告诉内核需要监听什么事 .

2、epoll的原理

用户只需要从就绪队列中获取就绪节点即可。上面的三套机制称为epoll模型。红黑树,就绪队列,回调。epoll模型被统一接入到了文件描述符表里,所以epoll_create的返回值是文件描述符。

epoll_create就是在创建struct_file,也就是创建epoll模型。

epoll_ctl 修改红黑树。

epoll_wait 中的events参数是输出型参数,把就绪队列中的节点,一个一个的放入到我们的epoll_event数组里。

优势:1、检测就绪O(1),获取就绪O(N)

           2、fd_event没有上限。

           3、这颗红黑树,就是select,poll自己维护的数组。

           4、返回值n,表示有几个fd就绪了,就绪事件是连续的!有返回值个。

3、快速写代码 --- echo server

#pragma once
#include <iostream>
#include <sys/epoll.h>
#include "log.hpp"
class Epoller
{
    static const int size = 128;
public:
    Epoller()
    {
        //创建epoll模型
        epollfd = epoll_create(/*这里的参数已经可以忽略了*/size);
        if(epollfd == -1)
        {
            lg(Error,"epoll create err %d %s",errno,strerror(errno));
        }
        else
        {
            lg(Info,"epoll create success %d",epollfd);
        }

    }
    //返回epoll中的就绪队列
    int waitEpoller(struct epoll_event revents[],int num)
    {
        int n = epoll_wait(epollfd,revents,num,-1);
        return n;
    }
    //通过系统调用把我们的文件描述符放入到内核中的红黑树中
    int updataEpoller(int sock,int oper,uint32_t event)
    {
        int n = 0;
        if(oper == EPOLL_CTL_DEL)
        {
            n = epoll_ctl(epollfd,oper,sock,nullptr);
            if(n!=0)
            {
                lg(Error,"epoll_ctl err");
            }
        }
        else
        {
            //EPOLL_CTL_ADD || EPOLL_CTL_MOD
            
            struct epoll_event ev;
            ev.data.fd = sock;
            ev.events = event;
            n = epoll_ctl(epollfd,oper,sock,&ev);
            if(n != 0)
            {
                lg(Error,"epoll_ctl err %d %s",errno,strerror(errno));
            }
        }
        return n;
    }
    ~Epoller()
    {
        if(epollfd >= 0)
        {
            close(epollfd);
        }
    }
private:
    int epollfd;
    int timeout{3000};
};
#include <iostream>
#include "Sock.hpp"
#include "log.hpp"
#include "nocopy.hpp"
#include "Epoller.hpp"
#include <memory>
int EVENT_IN = (EPOLLIN);
int EVENT_OUT = (EPOLLOUT);
static const uint16_t defaultport = 8080;
static const int defaultfd = -1;
class epollserver : public nocopy
{
    static const int num = 64;

public:
    epollserver(uint16_t port = defaultport) : _port(port), _listensockfd_ptr(new Sock()), _epoller_ptr(new Epoller())
    {}

    bool Init()
    {
        // 对套接字进行创建绑定监听
        _listensockfd_ptr->Socket();
        _listensockfd_ptr->Bind(_port);
        _listensockfd_ptr->Listen();
        return true;
    }
    void Accepter()
    {
        // 接收套接字
        std::string clientip;
        uint16_t clientport = 0;
        int sockfd = _listensockfd_ptr->Accept(&clientip, &clientport); // 这里不会被阻塞
        if (sockfd < 0)
            return;
        lg(Info, "accept success, %s: %d,", clientip.c_str(), clientport);

        if (sockfd > 0)
        {
            //std::cout << "Accept 放入内核中" << std::endl;
            _epoller_ptr->updataEpoller(sockfd, EPOLL_CTL_ADD, EVENT_IN);
            lg(Info, "get a new link, client info@ %s:%d %d", clientip.c_str(), clientport,sockfd);
        }
    }
    void Recvr(int fd)
    {
        // demo
        char buffer[1024];
        
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "get a messge: " << buffer << std::endl;


            std::string echo_string = "server say#";
            echo_string += buffer;
            write(fd, echo_string.c_str(), echo_string.size());
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            _epoller_ptr->updataEpoller(fd, EPOLL_CTL_DEL,0); // 这里本质是从select中移除
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
            _epoller_ptr->updataEpoller(fd, EPOLL_CTL_DEL,0); // 这里本质是从select中移除
        }
    }
    void Dispatcher(struct epoll_event revents[], int num)
    {
        for (int i = 0; i < num; i++)
        {
            int fd = revents[i].data.fd;
            //std::cout << fd << std::endl;
            uint32_t events = revents[i].events;
            if (events & EVENT_IN)
            {
                if (fd == _listensockfd_ptr->Fd())
                {
                    Accepter();
                }
                else
                {
                    Recvr(fd);
                }
            }
        }
    }
    void Start()
    {
        // 我们只需要把listenfd放入到红黑树中
        _epoller_ptr->updataEpoller(_listensockfd_ptr->Fd(), EPOLL_CTL_ADD, EVENT_IN);
        struct epoll_event revents[num];
        // for(int i = 0;i<num;i++)
        // {
        //     revents[i].data.fd = defaultfd;
        //     revents[i].events = 0;
        // }
        for (;;)
        {
            // 在开始阶段我们只有listensock,所以我们可以把listensock等待数据的就绪。
            // 获取内核就绪事件的fd,获取完成后,查看是否是读事件就绪。
            int n = _epoller_ptr->waitEpoller(revents, num);
            if (n > 0)
            {
                lg(Debug, "event happened, fd is : %d", revents[0].data.fd);
                Dispatcher(revents, n);
            }
            else if (n == 0)
            {
                std::cout << "time out" << std::endl;
            }
            else
            {
                std::cerr << "epoll err" << std::endl;
            }
        }
    }
    ~epollserver()
    {
    }

private:
    std::shared_ptr<Sock> _listensockfd_ptr;
    std::shared_ptr<Epoller> _epoller_ptr;
    uint16_t _port;
};

epoll的工作模式:LT和ET。

水平触发模式 LEVEL Triggered

事件到来,但是上层不处理,一直通知

边缘触发模式Edge Triggered

数据或者连接,从无到有,从有到多,变化的时候,才会通知我们一次。ET的通知效率更高。倒逼程序员,每次通知,都必须把本轮数据全部取走。循环读取,读取出错证明缓冲区没有数据了 ->fd默认是阻塞的 -> ET,所有的fd必须是non_block。不仅如此ET的IO效率也是更高的!通知一次必须把缓冲区的数据全部读走,以为着tcp回向对方通告一个更大的窗口,从而从概率上让对方一次给我发送更多的的数据。

ET vs LT ? LT可不可以将所有的fd设置成为non_block,然后循环读取呢,通知第一次的时候,就全部取走,不就和ET一样了吗?

LT和ET的本质区别是添加就绪队列的方式,LT是次次都添加,而ET只添加一次。

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

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

相关文章

Kafka+PostgreSql,构建一个总线服务

之前开发的系统&#xff0c;用到了RabbitMQ和SQL Server作为总线服务的传输层和存储层&#xff0c;最近一直在看Kafka和PostgreSql相关的知识&#xff0c;想着是不是可以把服务总线的技术栈切换到这个上面。今天花了点时间试了试&#xff0c;过程还是比较顺利的&#xff0c;后续…

破解AI生成检测:如何用ChatGPT降低论文的AIGC率

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 降低论文的“AIGC率”是个挑战&#xff0c;但有一些策略可以尝试。使用ChatGPT逐步调整和改进内容&#xff0c;使其更加自然和原创&#xff0c;降低AI检测工具识别出高“AIGC率”的概率…

专访阿里云:AI 时代服务器操作系统洗牌在即,生态合作重构未来

编者按&#xff1a;近日&#xff0c;2024 龙蜥操作系统大会已于北京圆满举办。大会期间&#xff0c;CSDN 采访了阿里云基础软件部资深技术总监、龙蜥社区技术委员会主席杨勇&#xff0c;前瞻性宏观解读面向 AI 智算时代&#xff0c;服务器操作系统面临的挑战与机遇。以下为采访…

云曦2024秋考核

真正的hacker 进去以后一眼就能看出来&#xff0c;是ThinkphpV5漏洞&#xff0c;只是版本不能确定&#xff0c;一开始考核的时候是&#xff0c;抓包看了php的版本&#xff0c;是7.23&#xff0c;是手注了几个尝试出来的&#xff08;后面才发现报错信息里面就有&#xff09;。漏…

记录word转xml文件踩坑

word文件另存为xml文件后&#xff0c;xml文件乱码 解决方法&#xff1a; 1.用word打开.docx文件 2.另存为xml文件 3.点击工具 -> Web选项 -> 编码&#xff0c;选择UTF-8 4.点击确定 5.使用notpad打开xml文件 6.使用xml tool进行xml格式化即可。

【免费资料推荐】数据资产管理实践白皮书(6.0版)

荐言&#xff1a;随着数字经济的快速发展&#xff0c;数据已成为企业最重要的资产之一。为有效管理和利用数据资产&#xff0c;各行业纷纷推出数据管理框架和标准。数据资产管理实践白皮书&#xff08;6.0版&#xff09;由中国信息通信研究院联合相关企业共同编写&#xff0c;是…

利士策分享,细品礼仪之美:在日常中优雅相处的艺术

利士策分享&#xff0c;细品礼仪之美&#xff1a;在日常中优雅相处的艺术 在当今这个快节奏、高压力的社会里&#xff0c;人与人之间的交往似乎被简化成了快餐式的信息交流。 然而&#xff0c;根植于文化深处的礼仪之花&#xff0c;依然是促进社会和谐、深化人际关系的宝贵财富…

python使用Pandas读取excel的行列内容

我的Excel文件名称是“测试.xlsx” 首先读取excle的文件内容 import pandas as pd dfpd.read_excel(测试.xlsx) #这个会直接默认读取到这个Excel的第一个sheet print(df)可以看看输出的是什么&#xff1a; 2. df.loc[0]&#xff0c;表示读取Excel的第一行&#xff08;这里…

docker容器中的内存占用高的问题分析

文章目录 问题描述原因分析分析1分析2验证猜想 结论和经验 问题描述 运维新增对某服务的监控后发现&#xff1a;内存不断上涨的现象。进一步确认&#xff0c;是因为有多个导出日志操作导致的内存上涨问题。 进一步的测试得出的结果是&#xff1a;容器刚启动是占用内存约为50M…

白话:大型语言模型中的幻觉(Hallucinations)

大型语言模型&#xff08;LLM&#xff09;可是自然语言处理和人工智能的一大步。它们能做的事情可多了&#xff0c;比如生成听起来挺靠谱的文本&#xff0c;翻译语言&#xff0c;总结文档&#xff0c;甚至写诗。但你知道吗&#xff0c;这些模型有时候会出现 “幻觉&#xff08;…

音视频开发常见的开源项目

FFmpeg 地址&#xff1a;https://ffmpeg.org/介绍&#xff1a;FFmpeg 是一个非常强大的开源多媒体框架&#xff0c;它可以用来处理视频和音频文件。它支持多种格式的转换、编码、解码、转码、流处理等。FFmpeg 包括了 libavformat、libavcodec、libavutil、libswscale、libpos…

Matlab求解微分方程(解析解与数值解)

matlab求解微分方程解析解和数值解 Matlab求微分方程解析解例题1例题2例题3 Matlab求微分方程数值解一阶微分方程例题一例题二 高阶微分方程例题 Matlab求微分方程解析解 dsolve(eqns,conds,options) eqns:微分方程&#xff08;组&#xff09;、conds&#xff1a;初值条件、opt…

萌宠宜家商城系统

摘 要 随着现在经济的不断发展和信息技术性日益完善和优化&#xff0c;传统式数据信息的管理升级成手机软件存放、梳理和数据信息集中统一处理的管理方式。本萌宠物宜家商城系统软件起源于这个环境中&#xff0c;能够帮助管理者在短期内进行庞大数据信息。使用这个专业软件能够…

【开源免费】基于SpringBoot+Vue.JS购物商城网站(JAVA毕业设计)

本文项目编号 T 032 &#xff0c;文末自助获取源码 \color{red}{T032&#xff0c;文末自助获取源码} T032&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

文字识别多功能工具箱 | eSearch v13.1.6

eSearch v13.1.6 是一款开源的截屏识屏搜索工具&#xff0c;它提供了丰富的功能&#xff0c;包括截屏、OCR识别、搜索翻译、贴图、以图搜图和录屏等一体化实用工具。该软件基于 Electron 框架开发&#xff0c;适用于 Linux、Windows 和 macOS 平台。 软件的主要特点和功能包括…

Lua发邮件:实现自动化邮件发送教程指南!

Lua发邮件高级技巧有哪些&#xff1f;如何利用Lua发送电子邮件&#xff1f; 自动化邮件发送是一个非常实用的功能&#xff0c;广泛应用于各种场景&#xff0c;如通知、提醒、报告生成等。Lua作为一种轻量级脚本语言&#xff0c;因其简洁和高效而受到广泛欢迎。AokSend将详细介…

金钥匙系列:Kubernetes (K8s) 服务集群技术栈学习路线

维护Kubernetes (K8s) 服务集群是一个复杂且多层次的技术任务&#xff0c;涉及容器化技术、集群管理、网络、安全、监控等多个领域。为了成为一名优秀的K8s集群维护工程师&#xff0c;技术栈需要广泛且深入。本文将为你详细介绍从零开始到深入掌握K8s集群维护的职业技术栈学习路…

在 Mac 上安装双系统会影响性能吗,安装双系统会清除数据吗?

在 Mac 系统安装并使用双系统已经成为了许多用户办公的选择之一&#xff0c;双系统可以让用户在 Mac 上同时运行 Windows 或其他操作系统。然而&#xff0c;许多用户担心这样做会对 Mac 的性能产生影响。 接下来将给大家介绍 Mac 装双系统会影响性能吗&#xff0c;Mac装双系统…

【Hue导入Hive文件类型数据(自动建表)】

1、进入Hue访问界面&#xff0c;点击要导入表的schema&#xff0c;点击号&#xff0c;上传要导入的文件。 2、本次测试文件数据用逗号分隔&#xff0c;也可根据文件分隔符选择具体格式 3、点击下一步&#xff0c;可自定义表名&#xff0c;以及选择字段数据类型&#xff0c;定…

【PyCharm】常用快捷键

此篇文章内容会不定期更新&#xff0c;仅作为学习过程中的笔记记录 PyCharm的所有快捷键&#xff0c;其实均可以自定义&#xff0c;在位于Settings -> Keymap的目录下&#xff08;如图&#xff09;&#xff0c;可以自行改写为自己熟悉的键位组合。 若更改为PyCharm已存在的键…