select、poll、epoll的区别

news2024/9/20 18:53:24

select、poll、epoll均为linux中的多路复用技术。3种技术出现的顺序是select、poll、epoll,3个版本反应了多路复用技术的迭代过程。我们现在开发网络应用时, 一般都会使用多路复用,很少有用一个线程来监听一个fd的,其中epoll又是最常使用的。关于epoll的实现和常见问题可以参考epoll实现原理和常见问题总结。

当我们在使用epoll的时候,会想当然的认为这种技术是理所应当的,这就是多路复用技术应该的样子。殊不知,从select到poll再到epoll也经历了一定的过程,epoll并不是一开始就出现的。epoll为什么出现,这里边既有技术的进步,也有客观使用的需求,特别是随着互联网的发展,需要监听的fd越来越多,select和poll已经不能满足实际的使用需求,所以才产生了epoll。而现在,我们也可以想一想,epoll就是linux中多路复用技术最终的形态了吗,还有没有可以优化的空间呢?

本文首先记录三种多路复用技术的使用方式,然后总结3者的区别。3个例子,均使用tcp socket,客户端创建5个连接,服务端分别使用select,poll和epoll来监听事件。

1select

客户端代码:

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

#define PORT 12345
#define SERVER_IP "192.168.74.128"
#define NUM_CONNECTIONS 5
#define MESSAGE "Hello from client"

int main() {
    int client_fds[NUM_CONNECTIONS] = {0};
    struct sockaddr_in serv_addr;
    char buffer[1024] = {0};
    int i = 0;

    for (i = 0; i < NUM_CONNECTIONS; i++) {
        if ((client_fds[i] = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket creation error");
            exit(EXIT_FAILURE);
        }

        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(PORT);

        if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
            perror("invalid address or address not supported");
            exit(EXIT_FAILURE);
        }

        if (connect(client_fds[i], (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
            perror("connection failed");
            exit(EXIT_FAILURE);
        }
    }

    for (i = 0; i < NUM_CONNECTIONS; i++) {
       if (client_fds[i] > 0) {
           send(client_fds[i], MESSAGE, strlen(MESSAGE), 0);
           printf("data sent to client %d\n", i + 1);
       }
    }

    for (i = 0; i < NUM_CONNECTIONS; i++) {
        close(client_fds[i]);
    }

    return 0;
}

服务端代码:

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

#define SERVER_IP ("192.168.74.128")
#define PORT 12345
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024

int set_reuse_addr(int fd) {
  int so_reuse = 1;

  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &so_reuse, sizeof(so_reuse)) < 0) {
    printf("set reuse addr failed.\n");
    return -1;
  }
  return 0;
}

int main() {
    int server_fd;
    int new_fd;
    int max_fd;
    int activity;
    int i;
    int client_sockets[MAX_CLIENTS];
    struct sockaddr_in address;
    fd_set readfds;
    char buffer[BUFFER_SIZE];

    for (i = 0; i < MAX_CLIENTS; i++) {
        client_sockets[i] = 0;
    }

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    set_reuse_addr(server_fd);

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(SERVER_IP);
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }


    max_fd = server_fd;
    printf("waiting for connections...\n");
    while (1) {
        FD_ZERO(&readfds);
        FD_SET(server_fd, &readfds);

        for (i = 0; i < max_fd; i++) {
            int tmp_fd = client_sockets[i];
            if (tmp_fd > 0) {
                FD_SET(tmp_fd, &readfds);
            }

            if (tmp_fd > max_fd) {
                max_fd = tmp_fd;
            }
        }

        activity = select(max_fd + 1, &readfds, NULL, NULL, NULL);
        if ((activity < 0) && (errno != EINTR)) {
            perror("select error");
        }

        if (FD_ISSET(server_fd, &readfds)) {
            if ((new_fd = accept(server_fd, NULL, NULL)) < 0) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            printf("New connection, socket fd is %d\n", new_fd);
            if(new_fd > max_fd) {
              max_fd = new_fd;
            }
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_sockets[i] == 0) {
                    client_sockets[i] = new_fd;
                    break;
                }
            }
        }

        for (i = 0; i < max_fd; i++) {
            int tmp_fd = client_sockets[i];
            if (FD_ISSET(tmp_fd, &readfds)) {
                int valread = read(tmp_fd, buffer, BUFFER_SIZE);
                if (valread == 0) {
                    close(tmp_fd);
                    client_sockets[i] = 0;
                } else {
                    buffer[valread] = '\0';
                    printf("data from %d: %s\n", tmp_fd, buffer);
                }
            }
        }
    }

    return 0;
}

1.1select第一个参数为什么要传max_fd+1

select的系统调用如下,第一个参数n是需要监听的最大fd+1。为什么加1呢,因为要遍历到所有的fd,而fd是从0开始的,遍历的时候是一个for循环for(int i=0; i < n; i++),只有n为max_fd+1才能遍历到max_fd。假如要监听的fd是0、1、2、3、4、5、6、7、8、9、10,那么第一个参数就应该传11。注意这里的n不是表示select需要监听的fd的个数,而只是和最大fd有关,假如只需要监听一个fd,fd是10,那么n也应该传11。

SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,

        fd_set __user *, exp, struct __kernel_old_timeval __user *, tvp)

{

    return kern_select(n, inp, outp, exp, tvp);

}

在内核中select最终会调用到do_select,可以看到在该函数中,使用for循环来遍历监听的fd。

static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
    ...
	for (;;) {
        ...
		for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
			...
		}
		...
	}
    ...
}

1.21024限制

使用select最多只能监听1024个文件描述符,并且fd的取值范围需要在[0,1023]之内。如果你只需要监听一个fd,这个fd是1024,那么这个fd也是不能监听的。

从select的形参可以看到,select将要监听的fd放到3个fd_set中,分别是可读、可写、错误。fd_set是一个bitmap,实现如下,所以可以得出,一个fd_set中最多可以保存1024个fd。

#undef __FD_SETSIZE

#define __FD_SETSIZE    1024

typedef struct {

    unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];

} __kernel_fd_set;

1.3性能不友好

从select的使用过程可以看到,在使用select的时候,有以下几点对性能不友好:

①假如我们要监听100个fd,fd从0到99,那么在select的内核实现中,需要对每个fd进行判断,判断这个fd上有没有事件;当select返回之后,用户也需要遍历100个fd来判断是不是有事件。假如这个时候只有1个fd上有事件,那么有99次判断都是空转的,浪费cpu资源。

②从select的使用上可以看到,每次调用select之前,用户想要监听哪些fd的可读事件,哪些fd的可写事件,哪些fd的错误事件,都需要分别设置3个fd set,每次都要设置。内核也会设置这些fd,如果我们要监听一个fd的可读事件,假设fd是10,那么用户会将fd设置到inp中,内核中会判断这个文件是不是有可读事件,有事件则select返回之后,在inp中还包含10这个fd;否则,就不包含了。

2poll

poll服务端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>

#define SERVER_IP ("192.168.74.128")
#define PORT 12345
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int set_reuse_addr(int fd) {
  int so_reuse = 1;

  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &so_reuse, sizeof(so_reuse)) < 0) {
    printf("set reuse addr failed.\n");
    return -1;
  }
  return 0;
}

int main() {
    int server_fd;
	int new_socket;
	int i;
    struct sockaddr_in address;
    struct pollfd fds[MAX_CLIENTS + 1]; // 额外一个用于监听新连接的文件描述符
    int nfds = 1; // 初始只有服务器套接字
    char buffer[BUFFER_SIZE];
    int poll_result;

    // 创建服务器套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(SERVER_IP);
    address.sin_port = htons(PORT);

    set_reuse_addr(server_fd);

    // 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 初始化 pollfd 结构体
    memset(fds, 0, sizeof(fds));
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;

    printf("Waiting for connections...\n");

    while (1) {
        // 等待事件发生
        poll_result = poll(fds, nfds, -1); // 阻塞直到有事件发生
        if (poll_result < 0) {
            perror("poll error");
            exit(EXIT_FAILURE);
        }

        // 检查是否有新的连接请求
        if (fds[0].revents & POLLIN) {
            if ((new_socket = accept(server_fd, NULL, NULL)) < 0) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            printf("New connection, socket fd is %d\n", new_socket);

            // 将新连接添加到 pollfd 结构体中
            if (nfds < MAX_CLIENTS + 1) {
                fds[nfds].fd = new_socket;
                fds[nfds].events = POLLIN;
                nfds++;
            } else {
                printf("Maximum number of clients reached.\n");
                close(new_socket);
            }
        }

        // 处理所有客户端的事件
        for (i = 1; i < nfds; i++) {
            if (fds[i].revents & POLLIN) {
                int valread = read(fds[i].fd, buffer, BUFFER_SIZE);
                if (valread == 0) {
                    // 客户端断开连接
                    close(fds[i].fd);
                    // 将断开的套接字从 pollfd 数组中移除
                    fds[i] = fds[nfds - 1];
                    nfds--;
                } else {
                    // 打印客户端发来的消息
                    buffer[valread] = '\0';
                    printf("Client %d sent: %s\n", fds[i].fd, buffer);
                }
            }
        }
    }

    return 0;
}

2.1poll的进步

①fd没有1024限制,可以将服务端代码中的MAX_CLIENTS改大,比如改成1500,同时也将客户端NUM_CONNECTIONS改成1500,这样就可以使用poll监听最大为1500的fd。这样的测试,首先要修改ulimit,默认情况下,系统限制每个进程可以打开的fd为1024个,如果我们想将这个参数调大,比如调到2048,那么可以通过ulimit -n 2048来修改。

②poll接口使用简化,首先在使用poll的时候不需要把fd放到不同的fd_set(读、写、错误)中,使用一个结构体struct pollfd来描述要监听的事件,其中events表示用户要监听的事件类型,POLLIN读事件,POLLOUT表示写事件,其它事件表示错误;其次用户要监听的事件和实际存在的事件进行了隔离,revents表示返回的事件,这样就不需要每次调用poll之前先要对每个fd进行设置了,只需要一开始设置一次即可。

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

3epoll

poll相对于select,在易用性上有一些进步,但是使用poll的时候,仍然需要对所有fd进行遍历,也就是说即使这次poll,一个fd上没有事件,那么仍然需要进行一次判断,导致了cpu资源的浪费。

server服务端:

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

#define SERVER_IP ("192.168.74.128")
#define PORT 12345
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024

int main() {
    int server_fd;
	int client_fd;
	int epoll_fd;
	int nfds;
	int i;
    struct sockaddr_in address;
    struct epoll_event ev, events[MAX_EVENTS];
    char buffer[BUFFER_SIZE];
    
    // 创建服务器套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(SERVER_IP);;
    address.sin_port = htons(PORT);

    // 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 5) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 创建 epoll 实例
    if ((epoll_fd = epoll_create1(0)) == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 添加监听套接字到 epoll
    ev.events = EPOLLIN;
    ev.data.fd = server_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
        perror("epoll_ctl");
        exit(EXIT_FAILURE);
    }

    printf("Waiting for connections...\n");

    while (1) {
        nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == server_fd) {
                // 新的连接
                while ((client_fd = accept(server_fd, NULL, NULL)) != -1) {
                    printf("New connection, socket fd is %d\n", client_fd);
                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = client_fd;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
                        perror("epoll_ctl");
                        exit(EXIT_FAILURE);
                    }
                }
                if (client_fd == -1) {
                    perror("accept");
                }
            } else {
                // 处理客户端数据
                int fd = events[i].data.fd;
                int bytes_read = read(fd, buffer, BUFFER_SIZE);
                if (bytes_read == 0) {
                    // 客户端断开连接
                    close(fd);
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                } else if (bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    printf("Received message from client %d: %s\n", fd, buffer);
                } else {
                    perror("read");
                }
            }
        }
    }

    close(server_fd);
    close(epoll_fd);
    return 0;
}

3.1epoll的进步

epoll解决了select和poll都存在的问题,epoll_wait返回之后,返回的结果中fd都是有事件的,不会造成cpu空转。也就是说如果select、poll、epoll都监听100个fd,而此时只有一个fd有事件,那么select和poll需要对100个fd进行判断,epoll只需要对这一个有事件的fd进行处理即可。

4区别

selectpollepoll
fd个数限制有限制无限制无限制
对所有fd进行遍历全部遍历全部遍历只需要遍历有事件的fd
目标事件和返回事件是不是共享共享,每次都要设置不共享不共享

在fd个数限制方面,select有限制,poll和epoll均没有限制。

对所有fd进行遍历方面,select和poll都需要对所有fd进行遍历,有事件则处理,没事件,则这次空转;epoll返回的结果一定是有事件的。

目标事件和返回的事件是不是共享,select是共享的,每次调用select之间,都需要设置3个fdset(读、写、错误),select返回之后,再判断是不是有事件;poll中是隔离开的,用户监听的事件是events,返回的事件是revents,不需要每次调用poll之前都设置events;epoll是隔离开的,用户监听的事件通过epoll_ctl设置,返回的事件是epoll_wait中的第二个参数。

现在,当我们开发网络应用时,几乎全部选择epoll,epoll的优点是显而易见的。但是epoll也是有缺点的,只不过epoll的缺点相对于优点来说,可以接受。epoll会占用一个fd,也就是会消耗一个fd资源;epoll在内核态的实现增加了多个数据结构,比如一个红黑树来管理所有需要监听的fd,一个双向链表来管理已有的事件等。

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

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

相关文章

鸿蒙开发5.0【Picker的受限权限适配方案】

Picker由系统独立进程实现&#xff0c;应用可以通过拉起Picker组件&#xff0c;用户在Picker上选择对应的资源&#xff08;如图片、文档等&#xff09;&#xff0c;应用可以获取Picker返回的结果。 类型受限权限使用的picker音频ohos.permission.READ_AUDIO&#xff0c;ohos.p…

【无人机设计与控制】 四轴飞行器的位移控制

摘要 本文介绍了一种四轴飞行器的位移控制方法&#xff0c;并通过Simulink模型进行仿真和验证。该方法通过PID控制器对飞行器的位移进行精确调节&#xff0c;以实现飞行器在三维空间中的稳定定位和路径跟踪。通过参数调节&#xff0c;能够适应不同的飞行任务需求&#xff0c;确…

UnLua环境搭建

一、环境搭建 1、下载UnLua工程&#xff1a;https://github.com/Tencent/UnLua 2、复制Plugins/UnLua目录下的插件到自己的项目中 3、重新生成自己的VS工程 4、打开VS工程的项目名.Build.cs文件&#xff0c;引用UnLua插件,重新编译工程 PublicDependencyModuleNames.AddRan…

【Nacos】Nacos快速上手使用(注册中心)【详解】

文章目录 1.基本介绍2. 使用Nacos服务注册中心2.1创建Nacos提供者集群 8001&#xff0c;80022.2创建Nacos消费者集群 8887 1.基本介绍 Nacos(Dynamic Naming and Configuration Service)是服务中心的另外一种实现。从注册中心的功能实现角度&#xff0c;与Eureka等价&#xff…

HumanNeRF:Free-viewpoint Rendering of Moving People from Monocular Video 翻译

HumanNeRF&#xff1a;单目视频中运动人物的自由视点绘制 引言。我们介绍了一种自由视点渲染方法- HumanNeRF -它适用于一个给定的单眼视频ofa人类执行复杂的身体运动&#xff0c;例如&#xff0c;从YouTube的视频。我们的方法可以在任何帧暂停视频&#xff0c;并从任意新的摄…

Python批量读取身份证信息录入系统和重命名

前言 大家好&#xff0c; 如果你对自动化处理身份证图片感兴趣&#xff0c;可以尝试以下操作&#xff1a;从身份证图片中快速提取信息&#xff0c;填入表格并提交到网页系统。如果你无法完成这个任务&#xff0c;我们将在“Python自动化办公2.0”课程中详细讲解实现整个过程。…

教师节送什么礼物给老师好 送礼送什么显高档又实用

教师节送什么礼物给老师好 送礼送什么显高档又实用 教师节即将到来&#xff0c;许多学生和家长都在思考如何表达对老师的感激之情。选择一份合适的礼物不仅能够表达心意&#xff0c;还能让老师感受到学生的关心和尊重。那么&#xff0c;送什么礼物给老师既显高档又实用呢&#…

字节跳动笔试题:自动校对程序:解决王大锤的拼写错误

字节跳动面试题:自动校对程序:解决王大锤的拼写错误 引言问题描述数据范围输入输出描述算法与数据结构伪代码C 代码实现代码解释测试用例边界情况复杂度分析结论后记引言 王大锤,一个出版社的编辑,每天面对海量的英文稿件,不胜其烦。然而,凭借他蓝翔技校挖掘机和程序设计…

[情商-13]:语言的艺术:何为真实和真相,所谓真相,就是别人想让你知道的真相!洞察谎言与真相!

目录 前言&#xff1a; 一、说话的真实程度分级 二、说谎动机分级&#xff1a;善意谎言、中性谎言、恶意谎言 三、小心&#xff1a;所谓真相&#xff1a;只说对自己有利的真相 四、小心&#xff1a;所谓真相&#xff1a;就是别人想让你知道的真相 五、小心&#xff1a;所…

Redis 事务:支持回滚吗?深入解析

今天我们要来探讨一个关于 Redis 事务的重要问题&#xff1a;Redis 事务支持回滚吗&#xff1f;这个问题在 Redis 的使用中经常被提及&#xff0c;对于正确理解和使用 Redis 事务至关重要。那么&#xff0c;让我们一起深入解析这个问题吧&#xff01; 一、Redis 事务简介 在了…

Linux命令分享 三 (ubuntu 16.04)

1、‘>’ >>输出重定向 用法&#xff1a;命令 参数 > 文件 ls > a.txt ‘>’ 将一个命令的结果不输出到屏幕上&#xff0c;输出到文件中&#xff0c;如果文件不存在就创建文件&#xff0c;如果存在就覆盖文件。 ls >> a.txt ‘>>’ 如果文件不存…

数据结构与算法02 - 复杂度

1、空间复杂度 空间复杂度指的是临时占用存储空间大小的量度&#xff1b;空间复杂度计算的是变量的个数&#xff0c;也采用大O渐进表示法&#xff1b;由于函数在运行的时候所需要的栈空间&#xff08;存储参数、局部变量、一些寄存器信息等&#xff09;在编译器已经确定好了&a…

BERN2(生物医学领域)命名实体识别与命名规范化工具

BERN2: an advanced neural biomedical named entity recognition and normalization tool 《Bioinformatics》2022 1 摘要 NER和NEN&#xff1a;在生物医学自然语言处理中&#xff0c;NER和NEN是关键任务&#xff0c;它们使得从生物医学文献中自动提取实体&#xff08;如疾病…

modelsim仿真流程

modelsim仿真流程 1、建立工程 project new "../prj" test.mpf2、添加rtl文件 project addfile "../test.v" verilog3、建立仿真库 vlib work4、编译rtl到仿真库中 vlog -sv -sv09compat defineT133 incdir"../rtl" test.v -work work5、加载…

【Python】6.基础语法(6)文件

文章目录 1. 文件是什么2. 文件路径3. 文件操作3.1 打开文件3.2 关闭文件3.3 写文件3.4 读文件 4. 关于中文的处理5. 使用上下文管理器 1. 文件是什么 变量是把数据保存到内存中。如果程序重启/主机重启, 内存中的数据就会丢失。 要想能让数据被持久化存储, 就可以把数据存储…

openGauss 之索引回表

一. 前言 ​ 在openGauss中如果表有索引信息&#xff0c;查询的谓词条件中又包含索引列&#xff0c;openGauss支持通过索引信息快速拿到需要访问元组的位置信息&#xff0c;然后直接到该位置上取出元组数据&#xff0c;称之为回表查询。如下所示&#xff0c;利用索引索引…

JS中this指向问题

首先&#xff0c;this的绑定和定义的位置无关&#xff0c;它的指向只和调用方式有关&#xff0c;this只有在运行时才知道指向谁。 一&#xff0c;默认绑定 默认绑定&#xff0c;也可以说是独立函数调用&#xff0c;这时this指向window。 function foo() {console.log(this) …

DataGrip数据迁移

第一步 第二步 第三步 第四步 选择你刚刚到处的文件即可

海信发布以旧换新举措,补贴力度、补贴链路、服务体验全面升级

9月7日&#xff0c;由中国家用电器商业协会主办的“海信全国十城联动以旧换新”发布会在北京举行。 据「TMT星球」了解&#xff0c;活动以“品质换新就选海信”为主题&#xff0c;旨在贯彻政府加大消费品以旧换新的战略部署&#xff0c;为我国家电行业绿色化、智能化、高端化高…

知名AIGC人工智能专家培训讲师唐兴通谈AI大模型数字化转型数字新媒体营销与数字化销售

在过去的二十年里&#xff0c;中国企业在数字营销领域经历了一场惊心动魄的变革。从最初的懵懂无知到如今的游刃有余&#xff0c;这一路走来&#xff0c;既有模仿学习的艰辛&#xff0c;也有创新突破的喜悦。然而&#xff0c;站在人工智能时代的门槛上&#xff0c;我们不禁要问…