网络编程——epoll

2023-11-16

参考

  1. 《TCP/IP网络编程》 尹圣雨

epoll

epoll也是Linux下实现I/O复用的一种方法,其性能优于select。

基于select的I/O复用服务器的设计缺陷

  1. 调用select函数后,针对所有文件描述符的循环语句。调用select函数后,需要观察作为监视对象的fd_set变量的变化,找出变化的文件描述符,因此需要针对所有监视对象的循环语句。

  2. 作为fd_set变量会发生变化,调用select函数前需要复制并保存原有信息,并在每次调用select函数时传递新的监视对象。

其中,第二点对性能影响最大。因为,select函数是监视套接字的变化的,而套接字是由操作系统管理的,向操作系统传递数据将对程序造成很大负担,而且无法通过优化代码解决。

解决select函数的缺点,可以仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。Linux的处理方式是epoll,Windows则是IOCP。但是当服务器端接入者少,或程序应具有兼容性时,select还是较好的选择。

epoll的使用

epoll的优点与select的缺点相反:

  1. 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句
  2. 调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息

epoll服务器端实现需要3个函数:

  1. epoll_create:创建保存epoll文件描述符的空间
  2. epoll_ctl:向空间注册并注销文件描述符
  3. epoll_wait:与select函数类似,等待文件描述符发生变化

使用epoll前需要验证Linux内核版本,epoll是从Linux的2.5.44版内核开始引入的。可以通过如下命令验证:

cat /proc/sys/kernel/osrelease

另外,epoll函数的使用还利用了一种结构体:

struct epoll_event
{
	__uint32_t events;
	epoll_data_t data;
}

typedef union epoll_data
{
	void* ptr;
	int fd;
	__uint32_t u32;
	__uint64_t u64;
}epoll_data_t;

声明足够大的epoll_event结构体数组后,传递给epoll_wait函数,发生变化的文件描述符信息将被填入该数组。epoll_event结构体也可以在epoll例程中注册文件描述符,用于注册关注的事件。

epoll_create

#include <sys/epoll.h>

int epoll_create(int size);

成功时返回epoll文件描述符,失败时返回-1。调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”,该文件描述符主要用于区分epoll例程,需要终止时,也要调用close函数;通过参数size传递的值并非用来决定epoll例程的大小,而仅供操作系统参考。

Linux 2.6.8之后的内核将完全忽略传入epoll_create函数的size参数,因为内核会根据情况调整epoll例程的大小。

epoll_ctl

利用epoll_ctl在epoll例程内注册监视对象文件描述符。

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

成功时返回0,失败时返回-1。其中,epfd为用于注册监视对象的epoll例程的文件描述符;op用于指定监视对象的添加、删除或更改等操作;fd为需要注册的监视对象的文件描述符;event为监视对象的事件类型。

第二个参数传递的常量及含义:

  1. EPOLL_CTL_ADD:将文件描述符注册到epoll例程
  2. EPOLL_CTL_DEL:从epoll例程中删除文件描述符(此时第四个参数传递NULL,但Linux 2.6.9之前不行)
  3. EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况

epoll_event中的成员events中可以保存的常量及其所指的事件类型:

  1. EPOLLIN:需要读取数据的情况
  2. EPOLLOUT:输出缓冲为空,可以立即发送数据的情况
  3. EPOLLPRI:收到OOB数据的情况
  4. EPOLLDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用
  5. EPOLLERR:发生错误的情况
  6. EPOLLET:以边缘触发的方式得到事件通知
  7. EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件

epoll_wait

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

成功时返回发生事件的文件描述符数,失败时返回-1。其中epfd为epoll例程的文件描述符;events为保存发生事件的文件描述符集合的结构体地址值,其所指缓冲需要动态分配;maxevents为第二个参数中可以保存的最大事件数;timeout是以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件。

基于epoll的回声服务器端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *buf);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];
    
    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;
    
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
        error_handling("bind() error");
    }
    if (listen(serv_sock, 5) == -1)
    {
        error_handling("listen() error");
    }

    epfd = epoll_create(EPOLL_SIZE);                                                      // 创建例程
    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);                                    // 注册服务端

    while (1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1)
        {
            puts("epoll_wait() error");
            break;
        }

        for (i = 0; i < event_cnt; i++)
        {
            if (ep_events[i].data.fd == serv_sock)                                        // 说明有客户端连接
            {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                event.events = EPOLLIIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);                        // 注册客户端
                printf("connected client: %d \n", clnt_sock);
            }
            else
            {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if (str_len == 0)                                                         // 数据接收完毕
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf("closed client: %d \n", ep_events[i].data.fd);
                }
                else
                {
                    write(ep_events[i].data.fd, buf, str_len);
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    return 0;
}

void error_handling(char *buf)
{
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

条件触发和边缘触发

条件触发和边缘触发的区别在于发生事件的时间点。条件触发方式中,只要输入缓冲中还剩有数据,就将以事件方式再次注册;边缘触发中输入缓冲收到数据时仅注册1次该事件。

epoll默认以条件触发工作,可以将调用read函数时使用的缓冲大小缩减验证这一点。select函数也是以条件触发的方式工作的。

实现边缘触发的服务器端

边缘触发方式中,接收数据时仅注册1次该事件。一旦发生相关事件,就应该读取输入缓冲中的全部数据。因此需要验证输入缓冲是否为空。为此,需要使用errno变量,read函数发现输入缓冲中没有数据可读时返回-1,同时在errno中保存EAGAIN常量。

为了访问errno变量,需要引入error.h头文件,因此次头文件中有errno变量的extern声明。

另外,边缘触发方式下,以阻塞方式工作的read&write函数有可能引起服务器的长时间停顿。因此,边缘触发方式中一定要采用非阻塞read&write函数。为此,需要将套接字变成非阻塞模式。Linux提供了更改或读取文件属性的方法fcntl()

#include <fcntl.h>
int fcntl(int filedes, int cmd, ...);

成功时返回cmd参数相关值,失败时返回-1。其中filedes为属性更改目标的文件描述符;cmd用于表示函数调用的目的

fcntl具有可变参数的形式。如果向第二个参数传递F_GETFL,可以获得第一个参数所指的文件描述符属性(int型)。反之,如果传递F_SETFL,可以更改文件描述符的属性。可以按如下方式将文件(套接字)改为非阻塞模式:

int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag|O_NONBLOCK);

通过第一条语句获取之前设置的属性信息,通过第二条语句在此基础上添加非阻塞O_NONBLOCK标志。调用read&write函数时,无论是否存在数据,都会形成非阻塞文件(套接字)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
void error_handling(char *buf);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];
    
    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;
    
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }
    
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
        error_handling("bind() error");
    }
    if (listen(serv_sock, 5) == -1)
    {
        error_handling("listen() error");
    }

    epfd = epoll_create(EPOLL_SIZE);                                                      // 创建例程
    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    setnonblockingmode(serv_sock);
    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);                                    // 注册服务端

    while (1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1)
        {
            puts("epoll_wait() error");
            break;
        }

        puts("return epoll_wait");
        for (i = 0; i < event_cnt; i++)
        {
            if (ep_events[i].data.fd == serv_sock)                                        // 说明有客户端连接
            {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                setnonblockingmode(clnt_sock);                                            // 将套接字改为非阻塞模式
                event.events = EPOLLIIN|EPOLLET;                                          // 将事件注册方式改为边缘触发
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);                        // 注册客户端
                printf("connected client: %d \n", clnt_sock);
            }
            else
            {
                while(1)
                {
                    str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                    if (str_len == 0)                                                         // 数据接收完毕
                    {
                        epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                        close(ep_events[i].data.fd);
                        printf("closed client: %d \n", ep_events[i].data.fd);
                        break;
                    }
                    else if (str_len < 0)
                    {
                        if (errno == EAGAIN)                                                 // 读取了输入缓冲中的全部数据
                        {
                            break;
                        }
                    }
                    else
                    {
                        write(ep_events[i].data.fd, buf, str_len);
                }
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    return 0;
}

void setnonblockingmode(int fd)
{
    int flag = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

条件触发与边缘触发孰优孰劣

边缘触发可以分离接收数据和处理数据的时间点。即使输入缓冲收到数据(注册相应事件),服务器端也能决定读取和处理这些数据的时间点,这样就给服务器端的实现带来巨大的灵活性

条件触发虽然也可以区分数据接收和处理,但在输入缓冲收到数据的情况下,如果不读取(延迟处理),则每次调用epoll_wait函数时都会产生相应事件。而且事件数也会累加,服务器无法承受

条件触发和边缘触发的区别主要应该从服务器端实现模型的角度谈论。从实现模型的角度看,边缘触发更有可能带来高性能,但不能简单地认为,只要使用边缘触发就一定能提高速度

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

网络编程——epoll 的相关文章

  • 一个网工(网络工程师)七年的职业血泪史....

    前言 一个工作了七年的老网工 上家公司待了五年 现在这家公司也快三年了 分享一些我自己学习网络安全路上的一些经历 也算是帮大家少走些弯路 一 如何学习网络安全 1 不要试图以编程为基础去学习网络安全 不要以编程为基础再开始学习网络安全 一般
  • 广告竞价策略:激发广告变现潜能的关键

    在数字化时代 广告已经成为企业推广品牌 产品和服务的关键手段之一 为了最大程度地发挥广告的效果 广告竞价策略成为广告主和数字营销专业人士关注的焦点 通过巧妙运用竞价策略 广告主可以在激烈的市场竞争中脱颖而出 实现广告变现的潜能 admaoy
  • 如何使用内网穿透实现iStoreOS软路由公网远程访问局域网电脑桌面

    文章目录 简介 一 配置远程桌面公网地址 二 家中使用永久固定地址 访问公司电脑 具体操作方法是 简介 软路由 是PC的硬件加上路由系统来实现路由器
  • 改善python程序的91建议记录

    使用else子句简化循环 异常处理 案例1 执行sql异常时处理 def save db obj try save attr1 db execute a sql stmt obj attr1 save attr2 db execute an
  • Linux 系统日志及其归档

    主要记录Linux 系统需要关注的日志文件 以及日志归档服务 rsyslogd 系统日志服务 rsyslogd 日志服务 rsyslogd reliable and extended syslogd 可靠 可扩展的系统日志服务 Rsyslo
  • 基于成本和服务质量考虑的不确定性下,电动汽车充电网络基础设施需求预测和迭代优化的分层框架研究(Python代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Python代码 数据
  • 基于成本和服务质量考虑的不确定性下,电动汽车充电网络基础设施需求预测和迭代优化的分层框架研究(Python代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Python代码 数据
  • 【CTF必看】从零开始的CTF学习路线(超详细),让你从小白进阶成大神!

    最近很多朋友在后台私信我 问应该怎么入门CTF 个人认为入门CTF之前大家应该先了解到底 什么是CTF 而你 学CTF的目的又到底是什么 其次便是最好具备相应的编程能力 若是完全不具备这些能力极有可能直接被劝退 毕竟比赛的时候动不动写个脚本
  • Python爬虫实战:IP代理池助你突破限制,高效采集数据

    当今互联网环境中 为了应对反爬虫 匿名访问或绕过某些地域限制等需求 IP代理池成为了一种常用的解决方案 IP代理池是一个包含多个可用代理IP地址的集合 可以通过该代理池随机选择可用IP地址来进行网络请求 IP代理池是一组可用的代理IP地址
  • 如何使用Imagewheel搭建一个简单的的私人图床无公网ip也能访问

    文章目录 1 前言 2 Imagewheel网站搭建 2 1 Imagewheel下载和安装 2 2 Imagewheel网页测试 2 3 cpolar的安装和注册 3 本地网页发布 3 1 Cpolar临时数据隧道
  • 基于java的物业管理系统设计与实现

    基于java的物业管理系统设计与实现 I 引言 A 研究背景和动机 物业管理系统是指对物业进行管理和服务的系统 该系统需要具备对物业信息 人员信息 财务信息等进行管理的能力 基于Java的物业管理系统设计与实现的研究背景和动机主要体现在以下
  • 基于java的物业管理系统设计与实现

    基于java的物业管理系统设计与实现 I 引言 A 研究背景和动机 物业管理系统是指对物业进行管理和服务的系统 该系统需要具备对物业信息 人员信息 财务信息等进行管理的能力 基于Java的物业管理系统设计与实现的研究背景和动机主要体现在以下
  • 短信系统搭建主要因素|网页短信平台开发源码

    短信系统搭建主要因素 网页短信平台开发源码 随着移动互联网的快速发展 短信系统已成为企业和个人进行信息传递的重要工具 建立一个高效可靠的短信系统对于企业来说非常重要 下面我们将介绍一些影响短信系统搭建的主要因素 1 平台选择 在搭建短信系统
  • 内网安全:隧道技术详解

    目录 隧道技术 反向连接技术 反向连接实验所用网络拓扑图及说明 网络说明 防火墙限制说明 实验前提说明 实战一 CS反向连接上线 拿下Win2008 一 使用转发代理上线创建监听器 二 上传后门执行上线 隧道技术 SMB协议 SMB协议介绍
  • ESP10B 锁定连接器

    ESP10B 锁定连接器 ESP10B 电机新增内容包括双极型号标准 NEMA 尺寸 17 23 和 34 的步进电机现在包括输出扭矩范围从 61 盎司英寸到 1291 盎司英寸的双极型号 该电机配有带锁定连接器的尾缆 可轻松连接 每转可步
  • 服务器中E5和I9的区别是什么,如何选择合适的配置

    随着科技的进步 服务器处理器的性能在不断攀升 其中 Intel的E5和I9系列处理器在业界具有广泛的影响力 而当我们在选择服务器的时候会有各种各样的配置让我们眼花缭乱不知道该怎么去选择 下面我跟大家分享一下E5跟I9有什么区别 方便我们在选
  • 【安全】网络安全态势感知

    文章目录 一 态势感知简介 1 概念 2 形象举例 3 应具备的能力 二 为什么要态势感知 为什么网络安全态势感知很重要 三 态势感知系统的功能 四 如何评估态势感知的建设结果 五 什么是态势感知的三个层级 四 业界的态势感知产品 1 安全
  • poll() 超时为 0 时会做什么?

    我正在看poll man page http man7 org linux man pages man2 poll 2 html 它告诉我的行为poll 当超时参数传入正值和负值时 它没有告诉我如果超时会发生什么0 有任何想法吗 看着epo
  • 一次性*level*触发的epoll():EPOLLONESHOT是否意味着EPOLLET?

    是否可以使用epoll一击level 触发模式 我在搜索时没有找到任何相关信息 看来每个人都使用边缘触发模式 当 的时候EPOLLONESHOT标记被选中并且您已经为套接字提取了一个事件 那么该套接字不会像许多人想象的那样从 epoll 中
  • C:epoll和多线程

    我需要创建专门的 HTTP 服务器 为此我计划使用 epoll sycall 但我想利用多个处理器 核心 但我无法提出架构解决方案 ATM我的想法如下 使用自己的epoll描述符创建多个线程 主线程接受连接并将它们分配给线程epoll 但还

随机推荐

  • 无监督聚类评价指标

    无监督聚类评价指标 文章目录 无监督聚类评价指标 SEE SC和CH 寻找k 评价指标 轮廓系数法 SC 评价指标 CH系数法 无监督聚类算法结果好坏的评价指标 Compactness 紧密性 CP Separation 间隔性 SP Da
  • 三子棋小游戏(纯C)

    N子棋 以三子棋为例 一 代码的初步框架 二 棋盘的初始化与棋盘的打印 玩家下棋与电脑下棋 输赢的判断 完整代码展现 一 代码的初步框架 我们接下来都是对game 的封装 逐步的完善 二 棋盘的初始化与棋盘的打印 上图为棋盘的打印 我们注释
  • VMware Workstation 无法连接到虚拟机。请确保您有权运行该程序、访问该程序使用的所有目录以及访问所有临时文件目录。 未能将管道连接到虚拟机: 系统找不到指定的文件。...

    安装好之后不能运行虚拟机 网上的办法说以管理员方式运行 每次点太麻烦 所以打开了设置 永久配置 一键开启 哈哈具体如下 右键vmware的属性 更改所有用户设置 这里打上勾 确定保存 ok 转载于 https www cnblogs com
  • PostgreSQL数据库用户规划

    在SQL标准里 同一个模式下的对象是不能被不同的用户拥有的 而且有些数据库系统不允许创建和它们的所有者不同名的模式 如Oracle数据库 实际上 在那些只实现了标准中规定的基本模式的数据库系统里 模式和用户的概念几乎是一样的 比如Oracl
  • webpack5进阶-学习笔记

    学习连接 https www bilibili com video BV1964y1k7Hm p 19 spm id from pageDriver 1 区分环境打包 1 1 通过环境变量区分 执行webpack命令时可携带环境变量 并在w
  • 计算机视觉项目实战-背景建模与光流估计(目标识别与追踪)

    欢迎来到本博客 本次博客内容将继续讲解关于OpenCV的相关知识 作者简介 目前计算机研究生在读 主要研究方向是人工智能和群智能算法方向 目前熟悉python网页爬虫 机器学习 计算机视觉 OpenCV 群智能算法 深度学习等内容 以后可能
  • vue--组件开发

    目录 一 button 组件开发 1 1 整体目标 1 2 确定组件API 1 3 编写测试基础Button 1 4 完成type配置 1 5 完成size配置 1 6 完成事件绑定 1 7 总结 二 Editor编辑器组件开发 2 1 确
  • 你还分不清谐波失真、总谐波失真、总谐波失真加噪声吗?

    原文来自公众号 工程师看海 公众号后台回复获取资料 THD 什么是信号失真 时域上测量系统的输出波形应该与输入波形精确一致 只是幅值放大 时间延迟 这称为不失真测量 通常放大电路的输入信号是复杂的多频信号 如果放大电路对信号的不同频率分量的
  • C#配置文件读取保存

    提示 文中分为txt和注册表两种方式 二选一即可 1 安装 txt 注册表 2 引用 txt using ApeFree DataStore using ApeFree DataStore Adapters using ApeFree Da
  • jsoup解析HTML,爬取小说实例

    1 java 的 File separator 斜杠 2 jsoup解析标签 element的text 方法直接取出两个标签中间的文本 import java io File import java io FileNotFoundExcep
  • 【解决】CSS下拉菜单不会显示的问题

    导航栏的下拉菜单不会显示 但按 F5 刷新的一瞬间又能看见下拉菜单的内容 但就是不会显示出来 一开始以为是 js 代码写错或者 css 动画函数的影响 后面找到一篇博客 说这是老生常谈的问题 对于小白确实很难找到问题关键 折腾一晚上终于发现
  • 踩坑日记:Invalid prop: type check failed for prop “border“. Expected Boolean, got String withvalue “fals

    这个错误我这里报了很多 错误的意思 border这个属性 预期的布尔值 得到的字符串值为 false 下面是我的代码 我们把border这个属性前面加上一个 冒号 就好了
  • torch显存分析——如何在不关闭进程的情况下释放显存

    torch显存分析 如何在不关闭进程的情况下释放显存 1 基本概念 allocator和block 2 torch cuda的三大常用方法 3 可以释放的显存 4 无法释放的显存 5 清理 显存钉子户 一直以来 对于torch的显存管理 我
  • SQL Server错误代码及解释(二)

    2001 指定的驱动程序无效 2002 窗口样式或类别属性对此操作无效 2003 不支持请求的图元操作 2004 不支持请求的变换操作 2005 不支持请求的剪切操作 2010 指定的颜色管理模块无效 2011 制定的颜色文件配置无效 20
  • 如何模拟微信小程序请求code

    官方文档有写到获取小程序Code 调用接口获取登录凭证 code 通过凭证进而换取用户登录态信息 包括用户在当前小程序的唯一标识 openid 微信开放平台帐号下的唯一标识 unionid 若当前小程序已绑定到微信开放平台帐号 及本次登录的
  • 在使用uni-app开发小程序时@tap和click的区别

    在HbuilderX中 两者都是点击时触发事件 不同的是 click是组件被点击时触发 会有约300ms的延迟 内置处理优化了 tap是手指触摸离开时触发 没有300ms的延迟 但是会有事件穿透 编译到小程序端 click会被转换成 tap
  • 如何解决使用 django-ftpserver 上传中文文件名乱码的问题

    Django ftpserver 是一个基于 django 的 FTP 服务器应用 它可以让我们轻松地在 django 项目中集成 FTP 服务 它使用 pyftpdlib 库来实现 FTP 协议的功能 最近 我在使用 django ftp
  • Python实现简单时钟(七段管数码结构)

    首先 全部代码如下 import turtle datetime time def drawgap turtle penup turtle fd 5 def drawline draw drawgap turtle pendown if d
  • 利用 uDig 生成 GeoServer 可用的 SLD 渲染文件

    利用 uDig 生成 GeoServer 可用的 SLD 渲染文件 导读 GeoServer 不像 ArcGIS 那样有完整的体系可以完成地图的数据整理 配图 发布整个过程 虽然它也提供了网页版的代码界面去设计通用的 SLD 格式对 WMS
  • 网络编程——epoll

    参考 TCP IP网络编程 尹圣雨 epoll epoll也是Linux下实现I O复用的一种方法 其性能优于select 基于select的I O复用服务器的设计缺陷 调用select函数后 针对所有文件描述符的循环语句 调用select