epoll 百万并发

1、epoll

  • epoll是Linux内核提供的一种高效的事件通知机制,用于I/O多路复用。它允许一个线程监视多个文件描述符(如套接字),并在这些文件描述符上有事件发生时通知应用程序。epoll相比于传统的selectpoll机制,提供了更高的性能和可扩展性,特别适用于高并发和高吞吐量的网络服务器。

1.1、epoll 的基本概念

  1. 文件描述符(File Descriptor, fd):在 Linux 系统中,每个打开的文件或套接字都有一个唯一的文件描述符。epoll 通过文件描述符来监听特定的事件。
  2. epoll 实例:通过 epoll_createepoll_create1 创建的一个 epoll 实例,用于管理和监听一组文件描述符。
  3. 事件(Event):表示文件描述符上的特定操作,如读(EPOLLIN)、写(EPOLLOUT)、错误(EPOLLERR)等。
  4. 事件监听:通过 epoll_ctl 函数向 epoll 实例注册或修改文件描述符的事件监听。
  5. 事件通知:通过 epoll_wait 函数等待事件的发生,当注册的事件发生时,epoll_wait 会返回,应用程序可以进行相应的处理。

1.2、创建epoll实例

  • epoll_create

    • 创建一个新的epoll(7)实例。
  • 函数原型
    • int epoll_create(int size);
      
    • size:这是一个历史遗留参数,最初用于告诉内核预期要监听的文件描述符的数量。从Liunx2.6.8开始,这个参数在 Linux 内核中已经被忽略,但为了兼容性,仍然需要传递一个大于 0 的值。

  • 返回值
    • 成功时返回一个非负的文件描述符(epfd),表示新创建的 epoll 实例。

    • 失败时返回 -1,并设置 errno 以指示错误。

  • epoll_create1

    • 创建一个新的epoll(7)实例。
  • 函数原型
    • int epoll_create1(int flags);
      
    • flags:这是一个位掩码参数,用于指定额外的选项。目前只有一个标志位有效:

      • EPOLL_CLOEXEC:在调用 exec 系列函数时自动关闭 epoll 实例的文件描述符,避免文件描述符泄漏到子进程中。
  • 返回值
    • 成功时返回一个非负的文件描述符(epfd),表示新创建的 epoll 实例。

    • 失败时返回 -1,并设置 errno 以指示错误。

  • 总结

    • 如果你不需要额外的选项,并且希望兼容较旧的 Linux 系统,可以使用 epoll_create

    • 如果你需要使用 EPOLL_CLOEXEC 选项,或者你确定你的代码只会在支持 epoll_create1 的较新 Linux 系统上运行,那么使用 epoll_create1 是更好的选择。

1.3、epoll_ctl

  • epoll_ctl是一个系统调用,向epoll实例中添加、删除或修改文件描述符及相关事件。

  • 函数原型
    • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
      
    • epfdepoll 实例的文件描述符(通过 epoll_createepoll_create1 创建)。

    • op:操作类型,指定要执行的操作。可以是以下三种之一:

      • EPOLL_CTL_ADD:将目标文件描述符 fd 添加到 epoll 实例中,并关联事件 event
      • EPOLL_CTL_MOD:修改与目标文件描述符 fd 关联的事件。
      • EPOLL_CTL_DEL:从 epoll 实例中删除目标文件描述符 fd
    • fd:目标文件描述符,通常是一个套接字描述符。

    • event:指向 epoll_event 结构的指针,描述要监听的事件。

      • epoll_event 结构

        • typedef union epoll_data {
              void        *ptr;
              int          fd;
              uint32_t     u32;
              uint64_t     u64;
          } epoll_data_t;
          
          struct epoll_event {
              uint32_t     events;      // 事件类型
              epoll_data_t data;        // 用户数据
          };
          
        • events:这是一个位掩码,指定要监听的事件类型。常见的事件类型包括:

          • EPOLLIN:可读事件。
          • EPOLLOUT:可写事件。
          • EPOLLRDHUP:对端关闭连接或关闭写入(用于边缘触发模式)。
          • EPOLLPRI:紧急数据到达(通常用于带外数据)。
          • EPOLLERR:错误事件(总是会触发,无需显式指定)。
          • EPOLLHUP:挂断事件(总是会触发,无需显式指定)。
          • EPOLLET:启用边缘触发模式(默认是水平触发模式)。
          • EPOLLONESHOT:一次性事件,触发后自动禁用。
        • data:这是一个联合体,用于存储用户数据。通常情况下,fd 字段会被使用,用于存储文件描述符。

  • 返回值
    • 成功时返回 0。

    • 失败时返回 -1,并设置 errno 以指示错误。

  • 示例
    • int sockfd = socket(AF_INET, SOCK_STREAM, 0);
      
      struct epoll_event ev;
      ev.events = EPOLLIN;  // 监听可读事件
      ev.data.fd = sockfd;  // 存储文件描述符
      
      // 添加事件
      epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
      
      // 修改事件
      ev.events = EPOLLOUT;
      epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
      
      // 删除事件
      epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL)
      
  • 总结
    • epoll_ctl 是管理 epoll 实例的核心函数,通过它可以灵活地添加、修改和删除文件描述符及其相关事件。理解并熟练使用 epoll_ctl 对于编写高性能的网络服务器应用程序至关重要。通过合理配置 epoll_event 结构,可以实现对特定事件的高效监听和处理。

1.4、epoll_wait

  • epoll_wait 是系统调用, Linux 系统中用于等待 epoll 实例中的文件描述符事件发生的函数。通过 epoll_wait,应用程序可以阻塞等待多个文件描述符上的事件,直到有事件发生或超时。

  • 函数原型
    • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
      
    • epfdepoll 实例的文件描述符(通过 epoll_createepoll_create1 创建)。

    • events:指向 epoll_event 数组的指针,用于存储返回的事件。

    • maxeventsevents 数组的大小,表示最多可以返回的事件数量。

    • timeout:超时时间,单位为毫秒。可以设置为以下值:

      • -1:无限期等待,直到有事件发生。
      • 0:立即返回,无论是否有事件发生。
      • >0:等待指定的毫秒数,超时后返回。
  • 返回值
    • 成功时返回发生的事件数量(即 events 数组中实际存储的事件数量)。
    • 超时返回 0。
    • 失败时返回 -1,并设置 errno 以指示错误。

1.5、触发方式

  • epoll 机制中,事件触发模式有两种:水平触发(Level Triggered,LT)和边沿触发(Edge Triggered,ET)。这两种触发模式决定了 epoll 如何通知应用程序文件描述符上的事件。理解这两种触发模式的差异对于优化 I/O 多路复用系统的性能至关重要。

  • 水平触发(Level Triggered,LT)
    • 水平触发epoll 的默认触发模式。在这种模式下,只要文件描述符上存在未处理的事件,epoll 就会持续通知应用程序。具体来说:

      • 如果文件描述符处于可读状态(例如接收缓冲区中有数据可读),epoll 会持续通知应用程序,直到应用程序处理完所有数据。
      • 如果文件描述符处于可写状态(例如发送缓冲区有空闲空间可写),epoll 会持续通知应用程序,直到应用程序写入足够的数据。
    • 优点

      • 易于使用,不易发生漏读或漏写的情况。

      • 对应用程序的编写要求较低,开发者无需担心一次性读取或写入所有数据。

    • 缺点

      • 可能会产生不必要的通知,增加系统的开销。

      • 如果应用程序处理事件的速度较慢,可能会导致大量的通知积压。

  • 边沿触发(Edge Triggered,ET)
    • 边沿触发 模式下,epoll 仅在文件描述符的状态发生变化时通知应用程序。具体来说:

      • 如果文件描述符从不可读变为可读(例如接收缓冲区中有新数据到达),epoll 会通知应用程序。

      • 如果文件描述符从不可写变为可写(例如发送缓冲区有空闲空间),epoll 会通知应用程序。

    • 优点

      • 大大减少了不必要的通知,提高了系统的效率。

      • 适用于高并发、高性能的场景,能够更好地利用系统资源。

    • 缺点

      • 编程复杂度较高,应用程序必须一次性读取或写入所有可用数据,否则可能会漏掉事件。
      • 如果应用程序未能及时处理事件,可能导致事件丢失(例如一次读取未能读取所有数据,剩余数据将不再通知)。
  • 使用示例
水平触发模式(默认)

在水平触发模式下,以下代码每调用一次 epoll_wait 就会返回可读事件,直到所有的数据都被读取:

struct epoll_event ev;
ev.events = EPOLLIN;  // 默认是水平触发
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

while (1) {
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < nfds; i++) {
        if (events[i].events & EPOLLIN) {
            char buf[1024];
            ssize_t num_bytes = read(events[i].data.fd, buf, sizeof(buf));
            // 处理读取的数据
        }
    }
}
边沿触发模式

在边沿触发模式下,需要在 epoll_event 中显式设置 EPOLLET 标志:

struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;  // 启用边沿触发
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

while (1) {
    int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < nfds; i++) {
        if (events[i].events & EPOLLIN) {
            char buf[1024];
            while (1) {
                ssize_t num_bytes = read(events[i].data.fd, buf, sizeof(buf));
                if (num_bytes < 0) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
                        break;  // 数据读取完毕
                    }
                    perror("read");
                    break;
                } else if (num_bytes == 0) {
                    // 对端关闭连接
                    break;
                }
                // 处理读取的数据
            }
        }
    }
}
  • 总结

    • 水平触发(LT):默认模式,容易使用,不易漏读或漏写。

    • 边沿触发(ET):效率高,适用于高并发场景,但编程复杂度较高,需要一次性处理所有事件。

      • 选择哪种触发模式取决于具体的应用需求和性能考虑。对于大多数应用来说,水平触发模式已经足够,但在需要极致性能的场景下,边沿触发模式可能更适合。

1.6、epoll简单示例

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/epoll.h>
#include <string.h>
#include <unistd.h>

int main()
{
    // 创建tcp套接字
    int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(2000);

    // 绑定地址
    if(-1 == bind(tcp_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)))
    {
        perror("bind");
        return -1;
    }

    // 监听连接
    listen(tcp_socket, 20);

    // 创建epoll实例
    int epfd = epoll_create(1);

    struct epoll_event ev;
    ev.data.fd = tcp_socket;
    ev.events = EPOLLIN;
    
    // 添加epoll监听事件
    epoll_ctl(epfd, EPOLL_CTL_ADD, tcp_socket, &ev);

    int nready = 0;
    struct epoll_event events[20] = {0};
    char buffer[1024] = {0};
    for(;;)
    {
        // 等待事件到来
        nready = epoll_wait(epfd, events, 20, -1);
        for(int i = 0; i < nready; i++)
        {
            // 如果事件可读
            if(events[i].events & EPOLLIN)
            {
                // 判断是否是连接到来
                if(events[i].data.fd == tcp_socket)
                {
                    int client_fd = accept(tcp_socket, NULL, NULL);
                    ev.data.fd = client_fd;
                    // 添加epoll监听事件
                    epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
                    continue;
                }
                else
                {
                    memset(buffer, 0, 1024);
                    int len = recv(events[i].data.fd, buffer, 1024, 0);
                    if(0 == len)
                    {
                        printf("%d:连接断开\n", events[i].data.fd);
                        // 删除监听事件
                        epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                        close(events[i].data.fd);
                        continue;
                    }
                    else if(-1 == len)
                    {
                        perror("recv");
                        break;
                    }
                    printf("recv:%s\n", buffer);
                }

            }
        }
    }
    close(tcp_socket);
    close(epfd);
    return 0;
}

2、百万并发(仅连接)

1.1、Reactor

  • 在网络编程中,Reactor模式(反应器模式)是一种用于事件驱动架构的设计模式。它的主要目的是处理多个并发输入/输出(I/O)操作,通常用于构建高性能、可扩展的网络服务器。Reactor模式的核心思想是将I/O操作的事件处理和实际的业务逻辑分离,以提高系统的并发处理能力和响应速度。

1.2、并发实现

  • 头文件
    • reactor.h

    • #ifndef _REACTOR_H_
      #define _REACTOR_H_
      
      #define PORT 1        // 端口
      #define IP INADDR_ANY    // IP
      
      #define EVENT_SIZE 10    // epoll一次返回事件的大小
      #define CONN_SIZE 1048576
      
      #define EPOLLIN_BUFFER_LEN  1024
      #define EPOLLOUT_BUFFER_LEN  1024
      
      typedef int(*CALLBACK)(int);
      
      // 存储连接数据
      struct conn{
          int fd;
      
          // EPOLLIN
          int in_len;
          char epollin_buffer[EPOLLIN_BUFFER_LEN];
          CALLBACK epollin_cb;
      
          // EPOLLOUT
          int out_len;
          char epollout_buffer[EPOLLOUT_BUFFER_LEN];
          CALLBACK epollout_cb;
      
      };
      
      #endif
      
      • conn用于存储连接的fd接收发送的数据,因为fd是唯一的,所有可以使用fd作为conn数组的下标
  • 设置事件
    • 向epoll添加事件,或修改现有事件

    • int set_event(int fd, int event, int flag)
      {
          struct epoll_event ev;
          ev.data.fd = fd;
          ev.events = event;
          // flag不等于0,添加事件,否则改变。
          if (flag)
          {
              epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
          }
          else
          {
              epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
          }
      }
      
  • 注册事件
    • 当有连接到来时,需要向epoll实例中注册事件。

    • int event_register(int fd, int event)
      {
          if(fd < 0)
              return -1;
          conn_list[fd].fd = fd;
          conn_list[fd].epollin_cb = recv_cb;
          conn_list[fd].epollout_cb = send_cb;
      
          conn_list[fd].in_len = 0;
          conn_list[fd].out_len = 0;
      
          memset(conn_list[fd].epollin_buffer, 0, EPOLLIN_BUFFER_LEN);
          memset(conn_list[fd].epollout_buffer, 0, EPOLLOUT_BUFFER_LEN);
      
          set_event(fd, event, 1);
          return 0;
      }
      
  • 接收连接
    • 接收客户端的连接,并向epoll中添加该连接

    • int accept_cb(int fd)
      {
          int new_fd = accept(fd, NULL, NULL);
          if (-1 == new_fd)
          {
              perror("accept");
          }
          // printf("%d : 建立连接\n", new_fd);
          event_register(new_fd, EPOLLIN);
          if ((new_fd % 1000) == 0)
          {
              struct timeval current;
              gettimeofday(&current, NULL);
      
              int time_used = TIME_SUB_MS(current, begin);
              memcpy(&begin, &current, sizeof(struct timeval));
      
              printf("连接完成: %d, 用时: %d\n", new_fd, time_used);
          }
      }
      
  • 接收数据
    • 接收客户端发送的数据,并将事件修改为EPOLLOUT,向客户端发送一条数据

      • 因为已经建立的连接fd一直都是处于可写状态,所有将事件设置为EPOLLOUT即可立即向客户端发送数据
    • int recv_cb(int fd)
      {
          printf("等待数据到来\n");
          conn_list[fd].in_len  = 0;
          memset(conn_list[fd].epollin_buffer, 0, EPOLLIN_BUFFER_LEN);
          int len = recv(fd, conn_list[fd].epollin_buffer, EPOLLIN_BUFFER_LEN, 0);
          if(-1 == len)
          {
              perror("recv");
              return -1;
          }
          if(0 == len)
          {
              printf("%d : 连接关闭!\n", fd);
              epoll_ctl(fd, EPOLL_CTL_DEL, fd, NULL);
              close(fd);
              return 0;
          }
          // printf("recv_cb:%s\n", conn_list[fd].epollin_buffer);
          conn_list[fd].in_len = len;
          set_event(fd, EPOLLOUT, 0);
      }
      
  • 发送数据
    • 当事件处于EPOLLOUT时,发送数据

    • int send_cb(int fd)
      {
          conn_list[fd].out_len = conn_list[fd].in_len;
          memset(conn_list[fd].epollout_buffer, 0, EPOLLOUT_BUFFER_LEN);
          memcpy(conn_list[fd].epollout_buffer, conn_list[fd].epollin_buffer, conn_list[fd].in_len);
          if (-1 == send(fd, conn_list[fd].epollout_buffer, conn_list[fd].out_len, 0))
          {
              perror("send");
          }
          set_event(fd, EPOLLIN, 0);
      }
      
  • 初始化服务器
    • 成功返回fd

    • int init_server(int port)
      {
          // 创建TCP套接字
          int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
          if(-1 == tcp_socket)  // 错误判断
          {
              perror("socket");
          }
      
           // 端口复用
          int val = 1;
          if(-1 == setsockopt(tcp_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)))
          {   
              perror("setsockopt()");
              return -1;
          }
      
          // 设置监听地址
          struct sockaddr_in server_addr;
          server_addr.sin_family = AF_INET;
          server_addr.sin_addr.s_addr = htonl(IP);  // 监听所有可用的网络接口(即所有IP地址)
          server_addr.sin_port = htons(port);
      
          // 服务端绑定地址
          if(-1 == bind(tcp_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)))
          {
              perror("bind");
              close(tcp_socket);
              return -1;
          }
          
          // 监听
          listen(tcp_socket, 20);
          return tcp_socket;
      }
      
  • main函数
    • 主函数一直处于循环状态

    • int main()
      {
          int nready;
          struct epoll_event ev;
          struct epoll_event events[EVENT_SIZE] = {0};
      
          // 初始化套接字,创建套接字、绑定地址,监听套接字
          int tcp_socket = init_server();
          if (-1 == tcp_socket)
          {
              return -1;
          }
      
          conn_list[tcp_socket].fd = tcp_socket;
          conn_list[tcp_socket].epollin_cb = accept_cb;
      
          epfd = epoll_create(1);
          ev.data.fd = tcp_socket;
          ev.events = EPOLLIN;
          if(-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, tcp_socket, &ev))
          {
              perror("epoll_ctl");
              return -1;
          }
      
          gettimeofday(&begin, NULL);   // 获取时间
          // 主循环
          for(;;)
          {
              nready = epoll_wait(epfd, events, EVENT_SIZE, -1);
              if(-1 == nready)
              {
                  perror("epoll_wait");
                  return -1;
              }
              for(int i = 0; i < nready; i++)
              {
                  int fd = events[i].data.fd;
                  if(EPOLLIN & events[i].events)
                  {
                      conn_list[fd].epollin_cb(fd);
                  }
                  if(EPOLLOUT & events[i].events)
                  {
                      conn_list[fd].epollout_cb(fd);
                  }
              }
          }
          close(epfd);
      
          return 0;
      }
      
  • 整体程序
    • reactor.c

    • #include <stdio.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <netinet/tcp.h>
      #include <sys/epoll.h>
      #include <string.h>
      #include <unistd.h>
      #include <sys/time.h>
      
      #include "reactor.h"
      
      #define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
      
      struct conn conn_list[CONN_SIZE] = {0};
      int epfd;
      int recv_cb(int);
      int send_cb(int);
      int set_event(int, int, int);
      struct timeval begin;
      
      int event_register(int fd, int event)
      {
          if(fd < 0)
              return -1;
          conn_list[fd].fd = fd;
          conn_list[fd].epollin_cb = recv_cb;
          conn_list[fd].epollout_cb = send_cb;
      
          conn_list[fd].in_len = 0;
          conn_list[fd].out_len = 0;
      
          memset(conn_list[fd].epollin_buffer, 0, EPOLLIN_BUFFER_LEN);
          memset(conn_list[fd].epollout_buffer, 0, EPOLLOUT_BUFFER_LEN);
      
          set_event(fd, event, 1);
          return 0;
      }
      
      int set_event(int fd, int event, int flag)
      {
          struct epoll_event ev;
          ev.data.fd = fd;
          ev.events = event;
          // flag不等于0,添加事件,否则改变。
          if (flag)
          {
              epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
          }
          else
          {
              epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
          }
      }
      
      int accept_cb(int fd)
      {
          int new_fd = accept(fd, NULL, NULL);
          if (-1 == new_fd)
          {
              perror("accept");
          }
          // printf("%d : 建立连接\n", new_fd);
          event_register(new_fd, EPOLLIN);
          if ((new_fd % 1000) == 0)
          {
              struct timeval current;
              gettimeofday(&current, NULL);
      
              int time_used = TIME_SUB_MS(current, begin);
              memcpy(&begin, &current, sizeof(struct timeval));
      
              printf("连接完成: %d, 用时: %d\n", new_fd, time_used);
          }
      }
      
      int recv_cb(int fd)
      {
          conn_list[fd].in_len  = 0;
          memset(conn_list[fd].epollin_buffer, 0, EPOLLIN_BUFFER_LEN);
          int len = recv(fd, conn_list[fd].epollin_buffer, EPOLLIN_BUFFER_LEN, 0);
          if(-1 == len)
          {
              perror("recv");
              return -1;
          }
          if(0 == len)
          {
              printf("%d : 连接关闭!\n", fd);
              epoll_ctl(fd, EPOLL_CTL_DEL, fd, NULL);
              close(fd);
              return 0;
          }
          // printf("recv_cb:%s\n", conn_list[fd].epollin_buffer);
          conn_list[fd].in_len = len;
          set_event(fd, EPOLLOUT, 0);
      }
      
      int send_cb(int fd)
      {
          conn_list[fd].out_len = conn_list[fd].in_len;
          memset(conn_list[fd].epollout_buffer, 0, EPOLLOUT_BUFFER_LEN);
          memcpy(conn_list[fd].epollout_buffer, conn_list[fd].epollin_buffer, conn_list[fd].in_len);
          if (-1 == send(fd, conn_list[fd].epollout_buffer, conn_list[fd].out_len, 0))
          {
              perror("send");
          }
          set_event(fd, EPOLLIN, 0);
      }
      
      
      int init_server(int port)
      {
          // 创建TCP套接字
          int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
          if(-1 == tcp_socket)  // 错误判断
          {
              perror("socket");
          }
      
           // 端口复用
          int val = 1;
          if(-1 == setsockopt(tcp_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)))
          {   
              perror("setsockopt()");
              return -1;
          }
      
          // 设置监听地址
          struct sockaddr_in server_addr;
          server_addr.sin_family = AF_INET;
          server_addr.sin_addr.s_addr = htonl(IP);  // 监听所有可用的网络接口(即所有IP地址)
          server_addr.sin_port = htons(port);
      
          // 服务端绑定地址
          if(-1 == bind(tcp_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)))
          {
              perror("bind");
              close(tcp_socket);
              return -1;
          }
          
          // 监听
          listen(tcp_socket, 20);
          return tcp_socket;
      }
      
      
      int main()
      {
          int nready;
          struct epoll_event ev;
          struct epoll_event events[EVENT_SIZE] = {0};
      
          // 初始化套接字,创建套接字、绑定地址,监听套接字
          int tcp_socket = init_server(PORT);
          if (-1 == tcp_socket)
          {
              return -1;
          }
      
          conn_list[tcp_socket].fd = tcp_socket;
          conn_list[tcp_socket].epollin_cb = accept_cb;
      
          epfd = epoll_create(1);
          ev.data.fd = tcp_socket;
          ev.events = EPOLLIN;
          if(-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, tcp_socket, &ev))
          {
              perror("epoll_ctl");
              return -1;
          }
      
          gettimeofday(&begin, NULL);   // 获取时间
          // 主循环
          for(;;)
          {
              nready = epoll_wait(epfd, events, EVENT_SIZE, -1);
              if(-1 == nready)
              {
                  perror("epoll_wait");
                  return -1;
              }
              for(int i = 0; i < nready; i++)
              {
                  int fd = events[i].data.fd;
                  if(EPOLLIN & events[i].events)
                  {
                      conn_list[fd].epollin_cb(fd);
                  }
                  if(EPOLLOUT & events[i].events)
                  {
                      conn_list[fd].epollout_cb(fd);
                  }
              }
          }
          close(epfd);
      
          return 0;
      }
      
  • 客户端程序

    • 服务端的实现是本片文章的重点内容,在此不对客户端进行讨论

    • epoll_client.c

    • 
      
      #include <stdio.h>
      #include <string.h>
      #include <stdlib.h>
      
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <sys/epoll.h>
      #include <errno.h>
      #include <netinet/tcp.h>
      #include <arpa/inet.h>
      #include <netdb.h>
      #include <fcntl.h>
      #include <sys/time.h>
      #include <unistd.h>
      
      #define MAX_BUFFER		128
      #define MAX_EPOLLSIZE	(384*1024)
      #define MAX_PORT		1
      
      #define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
      
      int isContinue = 0;
      
      static int ntySetNonblock(int fd) {
      	int flags;
      
      	flags = fcntl(fd, F_GETFL, 0);
      	if (flags < 0) return flags;
      	flags |= O_NONBLOCK;
      	if (fcntl(fd, F_SETFL, flags) < 0) return -1;
      	return 0;
      }
      
      static int ntySetReUseAddr(int fd) {
      	int reuse = 1;
      	return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
      }
      
      
      
      int main(int argc, char **argv) {
      	if (argc <= 2) {
      		printf("Usage: %s ip port\n", argv[0]);
      		exit(0);
      	}
      
      	const char *ip = argv[1];
      	int port = atoi(argv[2]);
      	int connections = 0;
      	char buffer[128] = {0};
      	int i = 0, index = 0;
      
      	struct epoll_event events[MAX_EPOLLSIZE];
      	
      	int epoll_fd = epoll_create(MAX_EPOLLSIZE);
      	
      	strcpy(buffer, " Data From MulClient\n");
      		
      	struct sockaddr_in addr;
      	memset(&addr, 0, sizeof(struct sockaddr_in));
      	
      	addr.sin_family = AF_INET;
      	addr.sin_addr.s_addr = inet_addr(ip);
      
      	struct timeval tv_begin;
      	gettimeofday(&tv_begin, NULL);
      	int sockfd = 0;
      
      	while (1) {
      		if (++index >= MAX_PORT) index = 0;
      		
      		struct epoll_event ev;
      
      		if (connections < 340000 && !isContinue) {
      			sockfd = socket(AF_INET, SOCK_STREAM, 0);
      			if (sockfd == -1) {
      				perror("socket");
      				goto err;
      			}
      
      			//ntySetReUseAddr(sockfd);
      			addr.sin_port = htons(port+index);
      
      			if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
      				perror("connect");
      				goto err;
      			}
      			ntySetNonblock(sockfd);
      			ntySetReUseAddr(sockfd);
      
      			sprintf(buffer, "Hello Server: client --> %d\n", connections);
      			send(sockfd, buffer, strlen(buffer), 0);
      
      			ev.data.fd = sockfd;
      			ev.events = EPOLLIN | EPOLLOUT;
      			epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
      		
      			connections ++;
      		}
      		//connections ++;
      		if (connections % 1000 == 999 || connections >= 340000) {
      			struct timeval tv_cur;
      			memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
      			
      			gettimeofday(&tv_begin, NULL);
      
      			int time_used = TIME_SUB_MS(tv_begin, tv_cur);
      			printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
      
      			int nfds = epoll_wait(epoll_fd, events, connections, 100);
      			for (i = 0;i < nfds;i ++) {
      				int clientfd = events[i].data.fd;
      
      				if (events[i].events & EPOLLOUT) {
      				//	sprintf(buffer, "data from %d\n", clientfd);
      					send(sockfd, buffer, strlen(buffer), 0);
      				} else if (events[i].events & EPOLLIN) {
      					char rBuffer[MAX_BUFFER] = {0};				
      					ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
      					if (length > 0) {
      				//		printf(" RecvBuffer:%s\n", rBuffer);
      
      						if (!strcmp(rBuffer, "quit")) {
      							isContinue = 0;
      						}
      						
      					} else if (length == 0) {
      						printf(" Disconnect clientfd:%d\n", clientfd);
      						connections --;
      						close(clientfd);
      					} else {
      						if (errno == EINTR || errno == EAGAIN || errno == ENOTSOCK) continue;
      
      						printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
      						close(clientfd);
      					}
      				} else {
      					printf(" clientfd:%d, errno:%d\n", clientfd, errno);
      					close(clientfd);
      				}
      			}
      		}
      
      		usleep(500);
      	}
      
      	return 0;
      
      err:
      	printf("error : %s\n", strerror(errno));
      	return 0;
      	
      }
      

1.3、并发问题解决

1.3.1、编译报错

  • 编译程序

    • gcc reactor.c -o reactor
      
      // 报错
      collect2: fatal error: ld terminated with signal 11 [段错误], core dumped
      compilation terminated.
      /tmp/cc3xbSaQ.o:在函数‘set_event’中:
      reactor.c:(.text+0x19c): 截断重寻址至相符: R_X86_64_PC32 针对在 
      
  • 上述程序中创建了一个conn_list的数据,数组大小为1048576,因为数据空间过大,所有编译出错。

  • 解决方法:编译时加上-mcmodel=medium

    • gcc reactor.c -o reactor -mcmodel=medium
      
    • 在编译C程序时使用 -mcmodel 选项是针对不同的内存模型,主要影响代码和数据的内存布局。不同的内存模型适用于不同的应用场景,尤其是当程序的大小超过一定限制时,选择合适的内存模型可以避免编译器或链接器错误。

      • -mcmodel=medium:适用于中等规模的程序,代码段和数据段的大小在 2GB 以内,代码段是位置独立的,数据段是位置依赖的。
      • -mcmodel=large:适用于大型程序,代码段和数据段的大小可以超过 2GB,但它们都是位置依赖的,可能会影响性能。

1.3.2、客户端报错“ Too many open files”

  • 打开的文件太多
  • 该错误是因为Liunx中对文件描述符的限制
    • 使用ulimit -a查看文件描述的限制

    • 在这里插入图片描述

    • 使用ulimit -n 1048576修改文件描述符限制

    • 在这里插入图片描述

1.3.3、Cannot assign requested address

  • 无法分配请求的地址

    • 在这里插入图片描述
  • 在TCP连接中,当客户端尝试与服务器建立连接时,如果出现 “Cannot assign requested address” 错误,通常意味着客户端无法分配到一个有效的本地端口号或IP地址来进行连接。

  • 客户端在短时间内尝试了大量的TCP连接,导致可用的本地端口号耗尽。

  • 解决方法:

    • 查看端口范围:sysctl net.ipv4.ip_local_port_range
    • sudo sysctl -w net.ipv4.ip_local_port_range=“1024 65000”
  • 当第二次进行上述设置后再次出现**“无法分配请求地址”**此时应该整加服务端和客户端的端口数量

    • 在这里插入图片描述

    • 此时连接数已经到达62999

  • 解决方法:

    • 在服务端增加端口的数量

      • 在reactor.h中添加**#define PORTS 20**

      • 主函数修改为

        • int main()
          {
              int nready;
              struct epoll_event ev;
              struct epoll_event events[EVENT_SIZE] = {0};
          
              epfd = epoll_create(1);
          
              for(int i = 0; i < MAX_PORTS;i++)
              {
                  int server_fd = init_server(PORT+i);
                  conn_list[server_fd].fd = server_fd;
                  conn_list[server_fd].epollin_cb = accept_cb;
                  set_event(server_fd, EPOLLIN, 1);
              }
          
              gettimeofday(&begin, NULL);   // 获取时间
              // 主循环
              for(;;)
              {
                  nready = epoll_wait(epfd, events, EVENT_SIZE, -1);
                  if(-1 == nready)
                  {
                      perror("epoll_wait");
                      return -1;
                  }
                  for(int i = 0; i < nready; i++)
                  {
                      int fd = events[i].data.fd;
                      if(EPOLLIN & events[i].events)
                      {
                          conn_list[fd].epollin_cb(fd);
                      }
                      if(EPOLLOUT & events[i].events)
                      {
                          conn_list[fd].epollout_cb(fd);
                      }
                  }
              }
              close(epfd);
          
              return 0;
          }
          

1.3.4、完整代码

  • reactor.h
    • #ifndef _REACTOR_H_
      #define _REACTOR_H_
      
      #define PORT 2000        // 端口
      #define IP INADDR_ANY    // IP
      
      #define MAX_PORTS 20
      
      #define EVENT_SIZE 1024    // epoll一次返回事件的大小
      #define CONN_SIZE 1048576
      
      #define EPOLLIN_BUFFER_LEN  1024
      #define EPOLLOUT_BUFFER_LEN  1024
      
      typedef int(*CALLBACK)(int);
      
      // 存储连接数据
      struct conn{
          int fd;
      
          // EPOLLIN
          int in_len;
          char epollin_buffer[EPOLLIN_BUFFER_LEN];
          CALLBACK epollin_cb;
      
          // EPOLLOUT
          int out_len;
          char epollout_buffer[EPOLLOUT_BUFFER_LEN];
          CALLBACK epollout_cb;
      
      };
      
      #endif
      
  • reactor.c
    • #include <stdio.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <netinet/tcp.h>
      #include <sys/epoll.h>
      #include <string.h>
      #include <unistd.h>
      #include <sys/time.h>
      
      #include "reactor.h"
      
      #define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
      
      struct conn conn_list[CONN_SIZE] = {0};
      int epfd;
      int recv_cb(int);
      int send_cb(int);
      int set_event(int, int, int);
      struct timeval begin;
      
      int event_register(int fd, int event)
      {
          if(fd < 0)
              return -1;
          conn_list[fd].fd = fd;
          conn_list[fd].epollin_cb = recv_cb;
          conn_list[fd].epollout_cb = send_cb;
      
          conn_list[fd].in_len = 0;
          conn_list[fd].out_len = 0;
      
          memset(conn_list[fd].epollin_buffer, 0, EPOLLIN_BUFFER_LEN);
          memset(conn_list[fd].epollout_buffer, 0, EPOLLOUT_BUFFER_LEN);
      
          set_event(fd, event, 1);
          return 0;
      }
      
      int set_event(int fd, int event, int flag)
      {
          struct epoll_event ev;
          ev.data.fd = fd;
          ev.events = event;
          // flag不等于0,添加事件,否则改变。
          if (flag)
          {
              epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
          }
          else
          {
              epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
          }
      }
      
      int accept_cb(int fd)
      {
          int new_fd = accept(fd, NULL, NULL);
          if (-1 == new_fd)
          {
              perror("accept");
          }
          // printf("%d : 建立连接\n", new_fd);
          event_register(new_fd, EPOLLIN);
          if ((new_fd % 1000) == 0)
          {
              struct timeval current;
              gettimeofday(&current, NULL);
      
              int time_used = TIME_SUB_MS(current, begin);
              memcpy(&begin, &current, sizeof(struct timeval));
      
              printf("连接完成: %d, 用时: %d\n", new_fd, time_used);
          }
      }
      
      int recv_cb(int fd)
      {
          conn_list[fd].in_len  = 0;
          memset(conn_list[fd].epollin_buffer, 0, EPOLLIN_BUFFER_LEN);
          int len = recv(fd, conn_list[fd].epollin_buffer, EPOLLIN_BUFFER_LEN, 0);
          if(-1 == len)
          {
              perror("recv");
              return -1;
          }
          if(0 == len)
          {
              printf("%d : 连接关闭!\n", fd);
              epoll_ctl(fd, EPOLL_CTL_DEL, fd, NULL);
              close(fd);
              return 0;
          }
          // printf("recv_cb:%s\n", conn_list[fd].epollin_buffer);
          conn_list[fd].in_len = len;
          set_event(fd, EPOLLOUT, 0);
      }
      
      int send_cb(int fd)
      {
          conn_list[fd].out_len = conn_list[fd].in_len;
          memset(conn_list[fd].epollout_buffer, 0, EPOLLOUT_BUFFER_LEN);
          memcpy(conn_list[fd].epollout_buffer, conn_list[fd].epollin_buffer, conn_list[fd].in_len);
          if (-1 == send(fd, conn_list[fd].epollout_buffer, conn_list[fd].out_len, 0))
          {
              perror("send");
          }
          set_event(fd, EPOLLIN, 0);
      }
      
      
      int init_server(int port)
      {
          // 创建TCP套接字
          int tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
          if(-1 == tcp_socket)  // 错误判断
          {
              perror("socket");
          }
      
           // 端口复用
          int val = 1;
          if(-1 == setsockopt(tcp_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)))
          {   
              perror("setsockopt()");
              return -1;
          }
      
          // 设置监听地址
          struct sockaddr_in server_addr;
          server_addr.sin_family = AF_INET;
          server_addr.sin_addr.s_addr = htonl(IP);  // 监听所有可用的网络接口(即所有IP地址)
          server_addr.sin_port = htons(port);
      
          // 服务端绑定地址
          if(-1 == bind(tcp_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)))
          {
              perror("bind");
              close(tcp_socket);
              return -1;
          }
          
          // 监听
          listen(tcp_socket, 20);
          return tcp_socket;
      }
      
      
      int main()
      {
          int nready;
          struct epoll_event ev;
          struct epoll_event events[EVENT_SIZE] = {0};
      
          epfd = epoll_create(1);
      
          for(int i = 0; i < MAX_PORTS;i++)
          {
              int server_fd = init_server(PORT+i);
              conn_list[server_fd].fd = server_fd;
              conn_list[server_fd].epollin_cb = accept_cb;
              set_event(server_fd, EPOLLIN, 1);
          }
      
          gettimeofday(&begin, NULL);   // 获取时间
          // 主循环
          for(;;)
          {
              nready = epoll_wait(epfd, events, EVENT_SIZE, -1);
              if(-1 == nready)
              {
                  perror("epoll_wait");
                  return -1;
              }
              for(int i = 0; i < nready; i++)
              {
                  int fd = events[i].data.fd;
                  if(EPOLLIN & events[i].events)
                  {
                      conn_list[fd].epollin_cb(fd);
                  }
                  if(EPOLLOUT & events[i].events)
                  {
                      conn_list[fd].epollout_cb(fd);
                  }
              }
          }
          close(epfd);
      
          return 0;
      }
      
  • epoll_client.c

    • #include <stdio.h>
      #include <string.h>
      #include <stdlib.h>
      
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <sys/epoll.h>
      #include <errno.h>
      #include <netinet/tcp.h>
      #include <arpa/inet.h>
      #include <netdb.h>
      #include <fcntl.h>
      #include <sys/time.h>
      #include <unistd.h>
      
      #define MAX_BUFFER		128
      #define MAX_EPOLLSIZE	(384*1024)
      #define MAX_PORT		20
      
      #define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
      
      int isContinue = 0;
      
      static int ntySetNonblock(int fd) {
      	int flags;
      
      	flags = fcntl(fd, F_GETFL, 0);
      	if (flags < 0) return flags;
      	flags |= O_NONBLOCK;
      	if (fcntl(fd, F_SETFL, flags) < 0) return -1;
      	return 0;
      }
      
      static int ntySetReUseAddr(int fd) {
      	int reuse = 1;
      	return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
      }
      
      
      
      int main(int argc, char **argv) {
      	if (argc <= 2) {
      		printf("Usage: %s ip port\n", argv[0]);
      		exit(0);
      	}
      
      	const char *ip = argv[1];
      	int port = atoi(argv[2]);
      	int connections = 0;
      	char buffer[128] = {0};
      	int i = 0, index = 0;
      
      	struct epoll_event events[MAX_EPOLLSIZE];
      	
      	int epoll_fd = epoll_create(MAX_EPOLLSIZE);
      	
      	strcpy(buffer, " Data From MulClient\n");
      		
      	struct sockaddr_in addr;
      	memset(&addr, 0, sizeof(struct sockaddr_in));
      	
      	addr.sin_family = AF_INET;
      	addr.sin_addr.s_addr = inet_addr(ip);
      
      	struct timeval tv_begin;
      	gettimeofday(&tv_begin, NULL);
      	int sockfd = 0;
      
      	while (1) {
      		if (++index >= MAX_PORT) index = 0;
      		
      		struct epoll_event ev;
      
      		if (connections < 340000 && !isContinue) {
      			sockfd = socket(AF_INET, SOCK_STREAM, 0);
      			if (sockfd == -1) {
      				perror("socket");
      				goto err;
      			}
      
      			//ntySetReUseAddr(sockfd);
      			addr.sin_port = htons(port+index);
      
      			if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
      				perror("connect");
      				goto err;
      			}
      			ntySetNonblock(sockfd);
      			ntySetReUseAddr(sockfd);
      
      			sprintf(buffer, "Hello Server: client --> %d\n", connections);
      			send(sockfd, buffer, strlen(buffer), 0);
      
      			ev.data.fd = sockfd;
      			ev.events = EPOLLIN | EPOLLOUT;
      			epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
      		
      			connections ++;
      		}
      		//connections ++;
      		if (connections % 1000 == 999 || connections >= 340000) {
      			struct timeval tv_cur;
      			memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));
      			
      			gettimeofday(&tv_begin, NULL);
      
      			int time_used = TIME_SUB_MS(tv_begin, tv_cur);
      			printf("connections: %d, sockfd:%d, time_used:%d\n", connections, sockfd, time_used);
      
      			int nfds = epoll_wait(epoll_fd, events, connections, 100);
      			for (i = 0;i < nfds;i ++) {
      				int clientfd = events[i].data.fd;
      
      				if (events[i].events & EPOLLOUT) {
      				//	sprintf(buffer, "data from %d\n", clientfd);
      					send(sockfd, buffer, strlen(buffer), 0);
      				} else if (events[i].events & EPOLLIN) {
      					char rBuffer[MAX_BUFFER] = {0};				
      					ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
      					if (length > 0) {
      				//		printf(" RecvBuffer:%s\n", rBuffer);
      
      						if (!strcmp(rBuffer, "quit")) {
      							isContinue = 0;
      						}
      						
      					} else if (length == 0) {
      						printf(" Disconnect clientfd:%d\n", clientfd);
      						connections --;
      						close(clientfd);
      					} else {
      						if (errno == EINTR || errno == EAGAIN || errno == ENOTSOCK) continue;
      
      						printf(" Error clientfd:%d, errno:%d\n", clientfd, errno);
      						close(clientfd);
      					}
      				} else {
      					printf(" clientfd:%d, errno:%d\n", clientfd, errno);
      					close(clientfd);
      				}
      			}
      		}
      
      		usleep(500);
      	}
      
      	return 0;
      
      err:
      	printf("error : %s\n", strerror(errno));
      	return 0;
      	
      }
      
### 回答1: matlab2020b是一种强大的科学计算数据分析工具,在学习工作中都有广泛的应用。以下是matlab2020b的安装教程: 1.从matlab官网下载安装文件,根据你的操作系统选择对应的版本。 2.运行安装文件,选择在计算机上安装matlab2020b,接受安装协议。 3.选择安装类型,可以选择完整安装或自定义安装。 4.接下来,选择需要安装的工具箱模块,根据你的使用需要进行选择。 5.选择安装位置,根据你的需要选择安装位置。 6.安装完成后,启动matlab2020b,输入用户名密码完成激活。 7.在matlab2020b中进行语言设置,选择好使用的语言(中文或英文)。 8.进行matlab2020b的全局设置,参数设置、工具箱管理路径设置等。 9.在matlab2020b中使用程序编辑器命令窗口来运行程序或进行交互式计算。 10.根据需要设置matlab2020b的环境界面绘图参数,保证你的数据可视化图形输出的效果。 总之,matlab2020b的安装过程并不复杂,只需要根据安装向导一步一步进行即可。在安装过程中,请注意选择合适的安装位置、工具箱模块,并按需进行全局设置参数调整,以保证你能更好地使用matlab2020b进行科学计算数据分析。 ### 回答2: MATLAB是一种广泛使用的科学计算工具,常用于机器学习、信号处理、图像处理数值计算。本文将介绍MATLAB2020b的安装教程,包括以下几个步骤: 1. 软件下载安装 首先,您需要从MathWorks官方网站下载MATLAB2020b的安装程序。在下载前,您需要创建一个MathWorks账户,然后选择您的操作系统MATLAB版本进行下载。 下载完成后,运行安装程序。安装程序将引导您完成MATLAB安装过程。请注意,您需要输入MathWorks账户的用户名密码进行激活。 2. 安装完成后的设置 一旦安装完成,您需要配置MATLAB。首先,您需要选择您的默认文件夹。这将成为您工作文件夹的默认目录,也将保存您的所有工作数据。 其次,您需要选择您的默认启动文件。您可以选择从预定义的模板中选择一个或自定义一个。 后,您需要选择您的默认位数。MATLAB2020b可以在64位32位操作系统上运行。您需要根据自己的操作系统选择适当的版本。 3. 插件安装 MATLAB2020b有许多有用的插件工具箱,例如数据分析、图像处理机器学习工具箱。您可以选择安装这些插件工具箱,以便更好地完成自己的工作。 要安装插件工具箱,您可以进入MATLAB控制面板并选择“添加-移除程序”。选择您需要安装的插件或工具箱,按照安装程序进行操作。 4. 更新 MathWorks将定期发布MATLAB的更新版本,包括修复错误改进功能。您应该及时更新MATLAB以获得佳体验。 要更新MATLAB,您可以进入MATLAB控制面板并选择“帮助-检查更新”。如果有可用的更新版本,MATLAB会自动下载安装。 总结 MATLAB2020b是一种非常强大的科学计算工具,可以帮助您完成各种工作。在安装MATLAB2020b之前,请确保您已经创建了MathWorks账户,并注意选择适当的版本安装选项。并且在安装完成后,请务必配置MATLAB安装必要的插件工具箱,并定期检查更新。 ### 回答3: Matlab 是一款非常强大的数学计算软件,它在学术研究工业应用中都有着广泛的应用。在使用 Matlab 进行数据处理、模拟、算法设计等操作时,许多用户需要安装 Matlab 在自己的电脑上。本篇文章将细介绍 Matlab 2020b 的安装教程,供大家参考。 1. 下载 Matlab 2020b 首先,我们需要从 Matlab 官方网站下载 Matlab 2020b,网址为 https://www.mathworks.com/downloads/。在该网站上,选择您的操作系统,然后选择要安装Matlab 版本,点击下载。如果你还没有 MathWorks 账户,需要先创建一个账户。 2. 安装 Matlab 2020b 下载完成后,双击下载的安装文件,进入安装界面。按照指示一步步进行安装,直到出现许可协议界面。阅读协议后,点击同意,然后选择安装选项。如果您是第一次安装 Matlab,建议选择“完全安装”,这样你将可以访问软件包中的所有工具函数。如果您需要在少量设备上安装,可以选择“仅安装核心 Matlab”,其中包含基本的 Matlab 功能。选择合适的安装选项后,点击“下一步”。 在接下来的界面上,您将需要输入您的许可证密钥(License Key) MathWorks 账户信息。如果您已经创建了 MathWorks 账户,输入您的信息就可以了。如果您没有许可密钥或者没有 MathWorks 账户,您可以选择“试用 Matlab”,试用期为 30 天。 在安装过程中,您将需要选择 Matlab安装路径。默认情况下,Matlab安装在 C:\Program Files\MATLAB\R2020b 下。您可以选择更改此目录,但是建议选择默认选项。安装完成后,Matlab 2020b 就可以正常运行了。 3. 激活 Matlab 2020b 安装 Matlab 2020b 后,您需要激活软件。打开 Matlab,输入您的许可证密钥 MathWorks 账户信息,点击“下一步”。在接下来的页面上,选择“使用 Internet 激活 Matlab”,然后点击“下一步”。Matlab 会连接 Internet 并激活您的软件。如果您遇到任何问题,可以尝试手动激活 Matlab。在手动激活界面,您需要输入您的许可密钥计算机 ID。计算机 ID 可以在 Matlab 启动时获取。完成所有步骤后,您的 Matlab 就可以使用了。 以上就是 Matlab 2020b 的安装教程。希望这篇文章对于需要安装 Matlab 的同学有所帮助。如果您在安装过程中遇到任何问题,可以通过 MathWorks 官方网站获取更多的支持帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值