【Linux高性能服务器编程】I/O复用的高级应用

文章目录

  • 一、基于 select 的非阻塞 connect
  • 二、基于 poll 的聊天室程序
    • 2.1 客户端
    • 2.2 服务器
  • 三、基于 epoll 实现同时处理 TCP 和 UDP 服务


一、基于 select 的非阻塞 connect

connect系统调用的 man 手册中有如下一段内容:

EINPROGERESS
The socket is nonblocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).

这段话描述了 connect 出错时的一种 errno 值:EINPROGRESS。这种错误发生在对非阻塞了socket调用了connect,而连接又没有建立时。根据man文档的解释,在这种情况下我们可以调用selectpoll等函数来监听这个连接失败的socket上的可写事件。当selectpoll等函数返回后,再利用getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功,否则连接失败。

通过上面描述的非阻塞 connect 方式,我们就能同时发起多个连接并一起等待,下面是非阻塞connect的一种实现方式。

首先是设置文件描述符为非阻塞状态:

// 设置文件描述符为非阻塞
int setnonblocking(int fd)
{
    int old_opt = fcntl(fd, F_GETFL);
    int new_opt = old_opt | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_opt);
    // 返回以前的文件描述符状态
    return old_opt;
}

先使用fcntl函数获取并保存sockfd描述符的状态,然后再使用fcntl函数将其设置为非阻塞的,然后将原状态返回,便于在建立连接成功后恢复sockfd的状态。

实现unblock_connect函数:

// 非阻塞连接函数,参数是服务器IP、端口以及超时时间。成功返回处于连接状态的socket,失败则返回-1.
int unblock_connect(const std::string &ip, uint16_t port, int time)
{
    int ret = 0;
    struct sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
    addr.sin_port = htons(port);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 设置套接字描述符为非阻塞,并保存其原来的状态
    int fdopt = setnonblocking(sockfd);
    ret = connect(sockfd, (const sockaddr *)&addr, sizeof(addr));
    if (ret == 0)
    {
        // 连接成功,将sockfd恢复原来的状态,然后返回
        std::cout << "connect with server immediately!" << std::endl;
        fcntl(sockfd, F_SETFL, fdopt);
        return sockfd;
    }
    else if (errno != EINPROGRESS)
    {
        // 如果连接没有立即建立,那么只有errno为EINPROGRESS时才表示连接还在继续,否则出错返回
        std::cout << "unblock connect not support!" << std::endl;
        close(sockfd);
        return -1;
    }
    else
    {
        // 继续连接

        // 写文件描述符集
        fd_set writefds;
        FD_ZERO(&writefds);
        // 将sockfd添加到writefds中
        FD_SET(sockfd, &writefds);
        timeval timeout;
        timeout.tv_sec = time;
        timeout.tv_usec = 0;

        ret = select(sockfd + 1, nullptr, &writefds, nullptr, &timeout);
        if (ret <= 0)
        {
            // select超时或出错,立即返回
            std::cout << "connect timeout!" << std::endl;
            close(sockfd);
            return -1;
        }

        if (!FD_ISSET(sockfd, &writefds))
        {
            std::cout << "no events on sockfd found!" << std::endl;
            close(sockfd);
            return -1;
        }

        int error = 0;
        socklen_t len = sizeof(error);
        // 调用 getsockopt来获取并清除sockfd上的错误
        if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
        {
            std::cout << "get socket option failed!" << std::endl;
            close(sockfd);
            return -1;
        }

        // 错误码不为0表示连接出错
        if (error != 0)
        {
            std::cout << "conntion failed after select with the error: " << error << std::endl;
            close(sockfd);
            return -1;
        }

        // 连接成功,将sockfd恢复原来的状态,然后返回
        std::cout << "connect successfully after select with the sockfd: " << sockfd << std::endl;
        fcntl(sockfd, F_SETFL, fdopt);
        return sockfd;
    }
}

在该函数中,首先创建socket套接字,并将其设置为非阻塞的,然后调用connect函数,如果连接成功,恢复sockfd的原状态后返回;如果不能立即连接成功,只有返回的errnoEINPROGRESS才表示连接还在进行,否则直接出错返回。

当返回的errnoEINPROGRESS时,调用select多路复用系统调用对sockfd的写事件进行监听。监听成功后,调用getsockopt函数来获取并清除sockfd上的错误。如果错误码是0,则表示在调用select函数后建立连接成功,恢复sockfd的原状态后返回;否则出错返回。

main函数逻辑:

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        std::cout << "usage: " << argv[0] << " ip port" << std::endl;
        return -1;
    }

    const std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    int sockfd = unblock_connect(ip, port, 10);
    if(sockfd < 0)
    {
        std::cout << "unblock connect failed!" << std::endl;
        return -1;
    }
    else
    {
        std::cout << "连接成功,可以向服务器发起请求了..." << std::endl;
    }

    close(sockfd);
    return 0;
}

二、基于 poll 的聊天室程序

这里基于poll系统调用实现一个简单的聊天室程序,以阐述如何使用I/O复用技术来同时处理网络连接给用户输入。该聊天室程序能让所有的用户同时在线群聊,它分为客户端和服务端两个部分。

其中客户端有两个功能:一是从标准输入终端读入用户数据,并将用户数据发送至服务器;二是往标准输出终端打印服务器发送给它的数据。服务器的功能是接收客户端数据,并将接收到的数据发送给每一个登录到该服务器上的客户端(数据发送者除外)。

效果展示如下:

以下是客户端和服务端的代码。

2.1 客户端

客户端程序使用 poll 同时监听用户输入和网络连接,并利用 splice 函数将用户输入的内容直接定向到网络连接上以发送之,从而实现数据的零拷贝,提高了程序的运行效率。客户端代码如下:

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>

#define BUFFER_SIZE 1024

using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "usage: " << argv[0] << " ip port" << endl;
        return -1;
    }

    const string ip = argv[1];
    uint16_t port = atoi(argv[2]);

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

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(sockfd >= 0);

    if (connect(sockfd, (const sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
        cerr << "connect failed!" << endl;
        close(sockfd);
        return -1;
    }

    pollfd fds[2];

    // 注册文件描述符0(标准输入) 和 sockfd 文件描述符的可读事件
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    fds[0].revents = 0;

    fds[1].fd = sockfd;
    fds[1].events = POLLIN | POLLRDHUP;
    fds[1].revents = 0;

    char read_buf[BUFFER_SIZE];
    int pipefd[2];
    int ret = 0;
    ret = pipe(pipefd);
    assert(ret != -1);


    while (true)
    {
        ret = poll(fds, 2, -1);
        if(ret < 0)
        {
            cout << "poll falied!" << endl;
            break;
        }

        if(fds[1].revents & POLLRDHUP)
        {
            cout << "server close the connection!" << endl;
        }
        else if(fds[1].revents & POLLIN)
        {
            memset(read_buf, '\0', BUFFER_SIZE);
            ssize_t s = recv(fds[1].fd, read_buf, BUFFER_SIZE - 1, 0);
            read_buf[s] = '\0';
            cout << read_buf << endl;
        }

        if(fds[0].revents & POLLIN)
        {
            //使用splice函数将用户输入的数据直接写到 sockfd 上(零拷贝)
            ret = splice(0, nullptr, pipefd[1], nullptr, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
            ret = splice(pipefd[0], nullptr, sockfd, nullptr, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
        }
    }

    close(sockfd);
    return 0;
}

2.2 服务器

服务器程序使用poll同时管理监听 socket 和连接 socket,并且使用牺牲空间换取时间的策略来提高服务器的性能。服务器代码如下:

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>

#define USER_LIMIT 5     // 最大用户数量
#define BUFFER_SIZE 1024 // 读缓冲区的大小
#define FD_LIMIT 65535   // 文件描述符数量限制

// 客户端数据
struct client_data
{
    sockaddr_in address;        // 客户端socket
    char *write_buf;            // 待写到客户端的数据的位置
    char read_buf[BUFFER_SIZE]; // 从客户端读入的数据
};

using namespace std;

int setnonblocking(int fd)
{
    int old_opt = fcntl(fd, F_GETFL);
    int new_opt = old_opt | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_opt);
    return old_opt;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "usage: " << argv[0] << " ip port" << endl;
        return -1;
    }

    string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    int ret = 0;
    sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    assert(listenfd >= 0);

    ret = bind(listenfd, (const sockaddr *)&addr, sizeof(addr));
    assert(ret != -1);

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

    // 创建users数组,分配FD_LIMIT个client_data对象。
    // 每个可能的socket连接都可以获得这样一个对象,并且socket的值可以直接原来索引socket对应的client_data对象,
    // 这是将socket和客户数据关联的简单而高效的方法。
    client_data *users = new client_data[FD_LIMIT];

    // 尽管分配了足够多的client_data对象,但为了提高poll的性能,仍然有必要限制用户数量
    pollfd fds[USER_LIMIT + 1];
    // 当前的用户数量
    int user_count = 0;
    // 初始化fds
    for (int i = 0; i <= USER_LIMIT; ++i)
    {
        fds[i].fd = -1;
        fds[i].events = 0;
    }

    // 添加listenfd到fds
    fds[0].fd = listenfd;
    fds[0].events = POLLIN | POLLERR;
    fds[0].revents = 0;

    while (true)
    {
        ret = poll(fds, user_count + 1, -1);
        if (ret < 0)
        {
            cerr << "poll failed!" << endl;
            break;
        }

        for (int i = 0; i <= user_count; ++i)
        {
            // 如果此时是监听套接字就绪,则处理新连接
            if ((fds[i].fd == listenfd) && (fds[i].revents & POLLIN))
            {
                sockaddr_in client_addr;
                socklen_t len = sizeof(client_addr);
                int connfd = accept(listenfd, (sockaddr *)&client_addr, &len);
                if (connfd < 0)
                {
                    cerr << "accept failed! error: " << errno << "msg: " << strerror(errno) << endl;
                    continue;
                }

                // 如果请求太多,则关闭该连接
                if (user_count >= USER_LIMIT)
                {
                    const string info = "too many users\n";
                    cout << info;
                    send(connfd, info.c_str(), info.size(), 0);
                    close(connfd);
                }

                // 对于新连接,同时修改users和fds数组,以保证users[connfd] 对应于 新连接文件描述符connfd的客户端数据
                ++user_count;
                users[connfd].address = client_addr;
                setnonblocking(connfd);
                fds[user_count].fd = connfd;
                fds[user_count].events = POLLIN | POLLERR | POLLRDHUP;
                fds[user_count].revents = 0;

                cout << "comes a new user, now have " << user_count << " users!" << endl;
            }
            // 如果connfd出错
            else if (fds[i].revents & POLLERR)
            {
                cout << "get an error from socket: " << fds[i].fd << endl;
                char errors[1024];
                memset(errors, '\0', sizeof(errors));
                socklen_t len = sizeof(errors);
                if (getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR, &errors, &len) < 0)
                {
                    cout << "get socket option failed!" << endl;
                }
                cout << "socket errors: " << string(errors) << endl;
                continue;
            }
            // 如果客户端关闭连接
            else if (fds[i].revents & POLLRDHUP)
            {
                // 客户端关闭连接,服务端也关闭连接,用户总数减1
                // 这里直接将数组中的最后一个用户替代当前用户
                users[fds[i].fd] = users[fds[user_count].fd];
                close(fds[i].fd);
                fds[i] = fds[user_count];
                --i;
                --user_count;
                cout << "a user left!" << endl;
            }
            // connfd 读事件就绪
            else if (fds[i].revents & POLLIN)
            {
                int connfd = fds[i].fd;
                memset(users[connfd].read_buf, '\0', BUFFER_SIZE);
                ret = recv(connfd, users[connfd].read_buf, BUFFER_SIZE-1, 0);
                if (ret < 0)
                {
                    // 如果读取出错,则关闭连接
                    if (errno != EAGAIN)
                    {
                        users[fds[i].fd] = users[fds[user_count].fd];
                        close(fds[i].fd);
                        fds[i] = fds[user_count];
                        --i;
                        --user_count;
                    }
                }
                if (ret == 0)
                {
                }
                else
                {
                    users[connfd].read_buf[ret] = '\0';
                    cout << "get " << ret << " bytes of clent data : " << users[connfd].read_buf << ". socket: " << connfd << endl;
                    // 如果收到客户端数据,则通知其他socket准备写数据
                    for (int j = 1; j <= user_count; ++j)
                    {
                        if (fds[j].fd == connfd)
                        {
                            continue;
                        }

                        fds[j].events |= ~POLLIN;
                        fds[j].events |= POLLOUT;
                        users[fds[j].fd].write_buf = users[connfd].read_buf;
                    }
                }
            }
            // connfd 写事件就绪
            else if (fds[i].revents & POLLOUT)
            {
                int connfd = fds[i].fd;
                if (!users[connfd].write_buf)
                {
                    continue;
                }

                ret = send(connfd, users[connfd].write_buf, strlen(users[connfd].write_buf), 0);
                users[connfd].write_buf = nullptr;

                // 写完数据要重新注册 fds[i] 上的可读事件
                fds[i].events |= ~POLLOUT;
                fds[i].events |= POLLIN;
            }
            else
            {
                //...
                // cout << "something happend!" << endl;
            }
        }
    }

    delete[] users;
    close(listenfd);
    return 0;
}

三、基于 epoll 实现同时处理 TCP 和 UDP 服务

以上的两个服务器程序都只能监听一个端口,在实际应用中,有不少程序能够同时监听多个端口,比如超级服务inetd和 android 的调试服务adbd

bind系统调用的参数来看,一个socket只能与一个socket地址绑定,即一个socket只能监听一个端口。因此,如果一个程序想要监听多个端口,就必须创建多个socket,并将它们分别绑定到各个端口上。这样一来服务器程序就需要同时管理多个监听socket,I/O复用技术就有了用武之地了。另外,即使在同一个端口,如果服务器要同时处理该端口上的TCP和UDP请求,则也需要创建两个不同的socket:一个是流socket,另一个是数据报socket,如何把它们两个绑定到同一个端口上。

以下的回射服务器就能同时处理一个端口上的TCP和UDP请求:

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define MAX_EVENT_NUMBER 1024
#define TCP_BUFFER_SIZE 512
#define UDP_BUFFER_SIZE 1024

using namespace std;

int setnonblocking(int fd)
{
    int old_opt = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, old_opt | O_NONBLOCK);
    return old_opt;
}

void addfd(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "usage " << argv[0] << " ip port" << endl;
        return 1;
    }

    const string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    int ret = 0;
    sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip.c_str(), &address.sin_addr);
    address.sin_port = htons(port);

    // 创建 TCP socket,并将其绑定到端口 port 上
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);

    ret = bind(listenfd, (const sockaddr *)&address, sizeof(address));
    assert(ret != -1);

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

    // 创建 UDP socket,并将其绑定到端口 port 上
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip.c_str(), &address.sin_addr);
    address.sin_port = htons(port);

    int udpfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(listenfd >= 0);

    ret = bind(udpfd, (const sockaddr *)&address, sizeof(address));
    assert(ret != -1);

    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    assert(epollfd != -1);

    // 注册 TCP socket 和 UDP socket 上的可读事件
    addfd(epollfd, listenfd);
    addfd(epollfd, udpfd);

    while (true)
    {
        int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if (number < 0)
        {
            cerr << "epoll_wait failed!" << endl;
            break;
        }

        for (int i = 0; i < number; ++i)
        {
            int sockfd = events[i].data.fd;
            uint32_t revent = events[i].events;
            if (revent & EPOLLIN)
            {
                if (sockfd == listenfd)
                {
                    // listenfd就绪,处理新的TCP连接
                    sockaddr_in peer;
                    socklen_t len = sizeof(peer);
                    int connfd = accept(listenfd, (sockaddr *)&peer, &len);
                    assert(connfd >= 0);
                    addfd(epollfd, connfd);
                }
                else if (sockfd == udpfd)
                {
                    // udpfd读事件就绪,接收并回发数据
                    char buffer[UDP_BUFFER_SIZE];
                    memset(buffer, '\0', UDP_BUFFER_SIZE);

                    sockaddr_in peer;
                    socklen_t len = sizeof(peer);

                    ret = recvfrom(udpfd, buffer, UDP_BUFFER_SIZE - 1, 0, (sockaddr *)&peer, &len);
                    if (ret > 0)
                    {
                        buffer[ret] = '\0';
                        cout << "client by UDP -> server# " << buffer << endl;
                        sendto(udpfd, buffer, UDP_BUFFER_SIZE - 1, 0, (const sockaddr *)&peer, len);
                    }
                }
                else
                {
                    // 普通connfd 读事件就绪
                    char buffer[TCP_BUFFER_SIZE];
                    memset(buffer, '\0', TCP_BUFFER_SIZE);
                    while (true)
                    {
                        memset(buffer, '\0', TCP_BUFFER_SIZE);
                        ret = recv(sockfd, buffer, TCP_BUFFER_SIZE - 1, 0);
                        if (ret < 0)
                        {
                            if (errno == EAGAIN || errno == EWOULDBLOCK)
                                break;

                            close(sockfd);
                            break;
                        }
                        else if (ret == 0)
                        {
                            cout << "client quit!" << endl;
                            close(sockfd);
                        }
                        else
                        {
                            buffer[ret] = '\0';
                            cout << "client by TCP -> server# " << buffer << endl;
                            send(sockfd, buffer, TCP_BUFFER_SIZE - 1, 0);
                        }
                    }
                }
            }
            else
            {
                //...
            }
        }
    }

    return 0;
}

启动程序,使用netstat -nltup | grep multiport命令就可以看到该程序那个同时处理TCP和UDP请求了:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

结构型模式-适配器模式

适配器模式 概述 如果去欧洲国家去旅游的话&#xff0c;他们的插座如下图最左边&#xff0c;是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑&#xff0c;手机在当地不能直接充电。所以就需要一个插座转换器&#xff0c;转换器第1面插入当地的插座&#x…

【java笔记】java多线程

目录 一、概念 1.1 什么是进程&#xff1f; 1.2 什么是线程&#xff1f; 1.3 什么事多线程&#xff1f; 1.4 进程和线程的关系 二、线程对象的生命周期 三、实现线程有两种方式 3.1 继承 java.lang.Thread&#xff0c;重写 run方法 3.2 实现 java.lang.Runnable 接口…

图像描述算法排位赛:SceneXplain 与 MiniGPT4 谁将夺得桂冠?

如果你对图像描述算法的未来感到好奇&#xff0c;本场“图像描述算法排位赛”绝对是你不能错过的&#xff01;在这场较量中&#xff0c;SceneXplain 和 MiniGPT-4 将会比试&#xff0c;谁将摘得这场比赛的桂冠&#xff1f; 背景介绍 在上篇文章中&#xff0c;我们介绍了图像描述…

【UE】倒计时归零时结束游戏

上一篇博客&#xff08;【UE】一个简易的游戏计时器&#xff09;完成了游戏时间每秒1的功能&#xff0c;本篇博客在此基础上完成倒计归零时结束游戏的功能 效果 步骤 1. 打开“ThirdPersonGameMode”&#xff0c;将剩余的秒数和分钟数的默认值分别设置为1和59 在事件图表中添…

Java设计模式-day02

4&#xff0c;创建型模式 4.2 工厂模式 4.2.1 概述 需求&#xff1a;设计一个咖啡店点餐系统。 设计一个咖啡类&#xff08;Coffee&#xff09;&#xff0c;并定义其两个子类&#xff08;美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】&#xff09;&#xff1b;再设…

Linux: 进程间通信机制

文章目录 1. 前言2. 进程间通信机制2.1 管道2.1.1 匿名管道2.1.2 popen() 和 pclose()2.1.3 命名管道 FIFO 2.2 消息队列2.3 共享内存2.4 信号量2.5 网络套接字2.6 UNIX套接字2.7 信号 3. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给…

Javaee Spring的AOP简介

一.Spring的AOP简介 1.1 什么是AOP AOP 为 Aspect Oriented Programming 的缩写&#xff0c;意思为面向切面编程&#xff0c;是通过预编译方式和运行期动态代 理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续&#xff0c;是软件开发中的一个热点&#xff0c;也是…

shell脚本控制

shell脚本编程系列 处理信号 Linux利用信号与系统中的进程进行通信&#xff0c;通过对脚本进行编程&#xff0c;使其在收到特定信号时执行某些命令&#xff0c;从而控制shell脚本的操作。 Linux信号 shell脚本编程会遇到的最常见的Linux系统信号如下表所示&#xff1a; 在默…

【获奖案例巡展】信创先锋之星——浙江省某市区视频能力中心

为表彰使用大数据、人工智能等基础软件为企业、行业或世界做出杰出贡献和巨大创新的标杆项目&#xff0c;星环科技自2021年推出了“新科技 星力量” 星环科技科技实践案例评选活动&#xff0c;旨在为各行业提供更多的优秀产品案例&#xff0c;彰显技术改变世界的力量&#xff0…

Cycling 74 Max for Mac:音乐可视化编程软件

Cycling 74 Max是一款音乐、视觉、互动艺术等领域中广泛使用的编程语言和应用软件&#xff0c;它允许用户创作和控制实时音频和视频效果、交互式应用程序和媒体艺术品等。 Max将程序设计和可视化编程相结合&#xff0c;通过简单的拖拽和连接方式&#xff0c;用户可以将各种功能…

基于springboot的大学生租房系统源码论文数据库

3.1系统功能 现在无论是在PC上还是在手机上&#xff0c;相信全国所有地方都在进行大学生租房管理。随着经济的不断发展&#xff0c;系统管理也在不断增多&#xff0c;大学生租房系统就是其中一种&#xff0c;很多人会登录到相关的租房系统查看租房信息&#xff0c;还能查看房屋…

微信小程序开发--利用和风天气API实现天气预报小程序

本来是参照《微信小程序开发实战》做一个天气预报小程序的&#xff0c;实际运行的时候提示错误&#xff0c;code 400&#xff0c;参数错误。说明问题应该出在查询API的语句上&#xff0c;没有返回结果。 查阅后才知道&#xff0c;可能书籍出版时间较早&#xff0c;现在的和风获…

类对象

一、类初识 类&#xff1a;表示一种事物所具有的共同特征和行为 对象&#xff1a;一个类的实例 如下图&#xff0c;通过狗这个类进行详解 这是一个Dog类 对象&#xff1a;斗牛犬、小猎犬、牧羊犬 类中的属性&#xff1a;breed(品种)、size(大小)、color(颜色)、age(年龄)、 …

安全常见基础名词概念

一、域名 1、域名&#xff1a;相当网站的名字&#xff0c;互联网上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时标识计算机的电子方位。 2、网域名系统&#xff08;Domain Name System&#xff09;有时也简称为域名&#xff08;DNS&#xff09;&#xff0c;是互…

探索【Stable-Diffusion WEBUI】的插件:骨骼姿态(OpenPose)

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;骨骼姿态&#xff08;OpenPose&#xff09;系列插件&#xff08;二&#xff09;插件&#xff1a;PoseX&#xff08;三&#xff09;插件&#xff1a;Depth Lib&#xff08;四&#xff09;插件&#xff1a;3D …

响应式开发(HTML5CSS3)实现媒体查询的功能案例

目录 前言 一、媒体查询知识点 二、实现功能的尺寸 三、代码部分 1.不带嵌套的媒体查询功能 1.1.代码段 1.2.运行结果 2.带嵌套的媒体查询功能 2.1.代码段 2.2.运行结果 2.2.3视频效果 前言 1.本文讲解的响应式开发技术&#xff08;HTML5CSS3Bootstrap&#xff09…

Auto-GPT免费尝鲜之初体验-使用攻略和总结

Auto-GPT免费尝鲜之初体验-使用攻略和总结 写在前面的废话一、部署 Auto-GPT二、试运行 Auto-GPT三、我踩过的坑四、后续探索 写在前面的废话 ChatGPT 的交互模式&#xff0c;是和一个 “人” 对话聊天。 如果你想了解更多ChatGPT和AI绘画的相关知识&#xff0c;请参考&#…

ArcGIS Pro用户界面

目录 1 功能区 1.1 快速访问工具栏 1.2 自定义快速访问工具栏 1.3 自定义功能区选项 1.3.1 添加组和命令 1.3.2 添加新选项卡 2 视图 3 用户界面排列 ​编辑 4 窗格 4.1 内容窗格 4.2 目录窗格 4.3 目录视图&#xff08;类似ArcCatalog&#xff09; 4.4 浏览对话框…

python:面向对象编程(知识点+代码)

文章目录 一、类和对象1、对象属性的默认值设置2、对象属性的添加、修改与删除3 、类属性 二、类的继承 引言&#xff1a;面向对象编程时一门编程语言重要的功能&#xff0c;我们之前所学的 c&#xff0c;java都为面向对象编程语言&#xff0c;这里给大家拓展一下&#xff0c;…

什么是CDN加速?CDN加速有哪些作用?

一、什么是 CDN CDN 的全称是 Content Delivery Network&#xff0c;即内容分发网络。CDN 是在现有 Internet 基础上增加一层新的网络架构&#xff0c;通过部署边缘服务器&#xff0c;采用负载均衡、内容分发、调度等功能&#xff0c;使用户可以就近访问获取所需内容&#xff…