epoll示例

一、服务端

下面是一个使用epoll机制在Linux上编写的简单套接字程序示例:

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

#define MAX_EVENTS 10
#define MAX_BUFFER_SIZE 1024

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_size;
    char buffer[MAX_BUFFER_SIZE];
    struct epoll_event event, events[MAX_EVENTS];

    // 创建套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        perror("Error creating socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6868);
    //server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //绑定127.0.0.1

    memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

    // 绑定套接字到地址
    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Error binding socket");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_socket, 5) < 0) {
        perror("Error listening");
        exit(EXIT_FAILURE);
    }

    // 创建epoll实例
    int epoll_fd = epoll_create1(0);
    if (epoll_fd < 0) {
        perror("Error creating epoll instance");
        exit(EXIT_FAILURE);
    }

    // 设置event结构体
    event.events = EPOLLIN;
    event.data.fd = server_socket;

    // 将socket添加到epoll实例中
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) < 0) {
        perror("Error adding socket to epoll instance");
        exit(EXIT_FAILURE);
    }

    while (1) {
        int num_ready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (num_ready < 0) {
            perror("Error waiting for events");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < num_ready; i++) {
            if (events[i].data.fd == server_socket) {
                // 检测到新的客户端连接请求
                addr_size = sizeof(client_addr);
                client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &addr_size);

                // 设置client_socket为非阻塞
                int flags = fcntl(client_socket, F_GETFL, 0);
                fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);

                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client_socket;

                // 将客户端socket添加到epoll实例中
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) < 0) {
                    perror("Error adding client socket to epoll instance");
                    exit(EXIT_FAILURE);
                }

                printf("New client connected: %s\n", inet_ntoa(client_addr.sin_addr));
            } else {
                // 处理客户端发送的数据
                int client_fd = events[i].data.fd;
                memset(buffer, 0, MAX_BUFFER_SIZE);

                int num_bytes = recv(client_fd, buffer, MAX_BUFFER_SIZE, 0);
                if (num_bytes < 0) {
                    perror("Error receiving data");
                    close(client_fd);
                    continue;
                } else if (num_bytes == 0) {
                    // 客户端连接关闭
                    printf("Client disconnected\n");
                    close(client_fd);
                    continue;
                }

                // 处理接收到的数据
                printf("Received data from client: %s\n", buffer);

                // 将数据发送回客户端
                send(client_fd, buffer, num_bytes, 0);
            }
        }
    }

    // 关闭套接字和epoll实例
    close(server_socket);
    close(epoll_fd);

    return 0;
}

这个程序创建了一个服务器套接字,使用epoll机制监听连接请求和处理客户端发送的数据。它首先创建了一个套接字 server_socket,并将其绑定到地址。然后通过 listen 函数开始监听连接请求。

接下来,程序创建了一个epoll实例 epoll_fd,并使用 epoll_create1 函数进行创建。然后,将服务器套接字添加到epoll实例中,通过 epoll_ctl 函数实现。接下来,程序进入一个无限循环中,使用 epoll_wait 函数等待事件发生。一旦有事件发生,通过遍历 events 数组处理每个事件。

当检测到一个新的客户端连接请求时,程序通过 accept 函数接受新的客户端连接,并将新的客户端套接字设置为非阻塞模式。然后,将客户端套接字添加到epoll实例中。

当客户端发送数据时,程序通过 recv 函数接收数据,并处理接收到的数据。然后,将数据发送回客户端,使用 send 函数。

最后,在循环结束时,程序关闭服务器套接字和epoll实例。

在C语言中,`struct sockaddr_in` 是一个用于存储网络地址的结构体,其中包括IP地址和端口号。它定义在 <netinet/in.h> 头文件中。这个结构体的定义如下:

struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // Zero padding to make the struct the same size as struct sockaddr
};

其中最后一个成员 sin_zero 是一个填充字段,其目的是为了保证 struct sockaddr_in 结构体的总大小和 struct sockaddr 结构体的大小相同,因为在socket API中,地址通常是通过 struct sockaddr 类型来传递的。为了确保类型兼容和内存布局的一致性,`sin_zero` 成员被添加到 struct sockaddr_in 结构体中,当使用这个结构体时,通常需要将此字段设置为全 0。
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero)); 使用 memset 函数将 sin_zero 字段的内存设置为0。这里的 '\0' 是空字符(null terminator),用于表示字符串的结束,在内存中其值为0。这行代码保证了填充字段没有留下任何未定义的数据,满足某些系统和库对结构体初始化的要求。
在许多实现中,这个填充可能并不是严格必要的,因为sockaddr_in和sockaddr的转换通常都能正常工作,但按照好的编程习惯,仍然建议对这部分内存进行清零处理。 

请注意,此示例程序是一个简单的示例,为了简洁起见,没有进行错误处理和边界检查。在实际编程中,您需要根据需求进行适当的错误处理和边界检查。此外,此示例使用了阻塞的 recvsend 函数,您可以根据需要使用非阻塞的I/O函数。

二、客户端

在Linux系统中,`epoll` 是一个高效的多路复用IO接口,它可以用于同时监控多个文件描述符,来检测它们是否有IO事件发生。在网络编程中,`epoll` 常用于接收端来管理多个客户端连接。然而,`epoll` 也同样适用于发送端,特别是当程序需要管理大量的出站连接时。
在发送端使用 epoll 有若干优势:
1. 非阻塞 I/O: 可以将套接字设置为非阻塞模式,然后使用 epoll 来检测何时可以在不阻塞的情况下发送数据。
2. 效率: 当有大量的套接字需要同时发送数据时,使用 epoll 可以减少CPU时间片的浪费,并减少上下文切换,因为可以仅在写入操作能够进行时才尝试发送数据。
3. 可扩展性: epoll 比传统的多路I/O复用方法(如 select 和 poll)具有更好的可扩展性,并且当监控的文件描述符数量增加时,其性能不会显著下降。
下面是一个简单的例子代码,演示了如何在Linux环境下使用epoll来监控socket的发送情况:

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

#define MAX_EVENTS 10
#define SERVER_PORT 6868
#define SERVER_IP "127.0.0.1"

int set_non_blocking(int sockfd) {
    int flags = fcntl(sockfd, F_GETFL, 0);
    if (flags == -1) {
        return -1;
    }

    flags |= O_NONBLOCK;
    if (fcntl(sockfd, F_SETFL, flags) == -1) {
        return -1;
    }

    return 0;
}

int main() {
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
       perror("epoll_create1 failed");
       exit(EXIT_FAILURE);
    }

    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置套接字为非阻塞模式
    if (set_non_blocking(socket_fd) == -1) {
        perror("set_non_blocking failed");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // 连接到服务器
    if (connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        if (errno != EINPROGRESS) { // 非阻塞socket在连接时会返回EINPROGRESS
            perror("connect failed");
            exit(EXIT_FAILURE);
        }
    }

    struct epoll_event ev, events[MAX_EVENTS];
    ev.events = EPOLLOUT | EPOLLET;  // 关注可写事件,使用边缘触发模式
    ev.data.fd = socket_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev) == -1) {
        perror("epoll_ctl: socket_fd");
        exit(EXIT_FAILURE);
    }

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

        for (int i = 0; i < nfds; ++i) {
            if (events[i].data.fd == socket_fd && (events[i].events & EPOLLOUT)) {
                // 套接字准备好写入,发送数据
                const char *msg = "Hello, Server!";
                ssize_t bytes_sent = send(socket_fd, msg, strlen(msg), 0);
                if (bytes_sent < 0) {
                    // 发送失败的处理
                    perror("send failed");
                    close(socket_fd);
                    exit(EXIT_FAILURE);
                } else {
                    printf("Message sent: %s\n", msg);
                    // 为了简化示例,发送成功后就退出循环
                    close(socket_fd);
                    close(epoll_fd);
                    exit(EXIT_SUCCESS);
                }
            }
        }
    }

    close(epoll_fd);
    return 0;
}

注意:这个示例假设与服务器的连接已经建立,并准备发送数据。如果服务器没有运行在端口 6868 或者服务器拒绝连接,那么 connect 调用将失败。
在运行这个代码前,确保本地的服务器正在监听端口 6868,否则 connect 调用将不会成功。此外,该例子只发送一次数据并在发送成功后立即关闭socket和epoll文件描述符,这只是为了示范目的。实际使用中,可能希望保持连接并继续根据需要进行数据发送。

三、编译运行

1. 服务端

gcc server.c -o server
./server
New client connected: 127.0.0.1
Received data from client: Hello, Server!
Client disconnected

2.客户端

gcc client.c -o client
./client
Message sent: Hello, Server!

四、多个客户端

想要在一个进程中管理多个到同一个服务器的连接,不需要为每个连接创建新的进程。而是在同一个进程中打开多个套接字,并将它们全部注册到同一个`epoll`实例。如下所示:

#include <sys/epoll.h>
#include <sys/socket.h>
// 其他必要的头文件...

#define MAX_EVENTS 10

int main() {
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
       perror("epoll_create1 failed");
       exit(EXIT_FAILURE);
    }

    struct epoll_event ev, events[MAX_EVENTS];
    int socket_fds[2];  // 假设我们有两个连接

    // 对每个套接字重复连接和设置过程
    for (int i = 0; i < 2; i++) {
        socket_fds[i] = /* 这里是创建套接字并连接到服务器的代码 */;
        ev.events = EPOLLOUT;
        ev.data.fd = socket_fds[i];
        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fds[i], &ev) == -1) {
            perror("epoll_ctl: socket_fds[i]");
            exit(EXIT_FAILURE);
        }
    }

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

        for (int i = 0; i < nfds; ++i) {
            if (events[i].events & EPOLLOUT) {
                // 在这里根据events[i].data.fd判断是哪个套接字准备好了,然后发送数据
                send(events[i].data.fd, /* data */, /* size */, /* flags */);
                // 处理发送逻辑
            }
        }
    }

    close(epoll_fd);
    for (int i = 0; i < 2; i++) {
        close(socket_fds[i]);  // 关闭套接字
    }
    return 0;
}

在这个示例中,`socket_fds` 数组用来存储两个套接字描述符,并且都被添加到同一个`epoll`实例中。这样,在主循环中使用`epoll_wait`时可以同时监控两个套接字的事件状态。当套接字准备好写数据时,`epoll_wait`会返回并且通过检查`events[i].events` 来确定是哪个套接字准备好,并执行相应的`send`操作。

上述代码是一个示意性的框架,其中需要填充创建套接字并连接到服务器的代码,以及进行实际数据发送的代码。此外,异常处理和清理操作(如关闭套接字)在实际应用中也需要妥善处理。

五、动态添加和删除客户端套接字

1. 动态添加

为每个客户端都维护一个 epoll 实例并不是一个可扩展或高效的解决方案。事实上,`epoll` 的主要优势之一就是能够使用单个 epoll 实例来监控多个文件描述符(如socket连接)。这样,使用单个线程或者进程就能够管理大量的客户端连接,从而显著减少系统资源的使用和上下文切换的开销。

正确的做法是为所有的客户端连接使用同一个 epoll 实例。当有新的客户端连接时,可以把新的socket文件描述符添加到这个 epoll 实例中去。这个 epoll 实例会告诉哪些socket有事件需要处理,比如数据准备好读取或socket准备好写入数据。

下面是一个简单的例子,展示了如何使用单个 epoll 实例来处理来自多个客户端的连接:

#define MAX_EVENTS 1024

// 创建并初始化epoll实例
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
    // 处理错误
}

struct epoll_event event, events[MAX_EVENTS];

// 通过某种方式获取到一个监听socket_fd,例如bind和listen之后的socket

event.events = EPOLLIN; // 监控可读事件
event.data.fd = listen_socket_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_socket_fd, &event) == -1) {
    // 处理错误
}

while (1) {
    // 等待事件发生
    int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        // 处理错误
    }

    for (int i = 0; i < nfds; ++i) {
        if (events[i].data.fd == listen_socket_fd) {
            // 接受新的连接
            int client_fd = accept(listen_socket_fd, NULL, NULL);
            if (client_fd == -1) {
                // 处理错误
            }

            // 设置新的socket为非阻塞模式...

            // 将新的客户端socket添加到epoll实例中
            event.events = EPOLLIN | EPOLLET; // 边缘触发模式
            event.data.fd = client_fd;
            if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
                // 处理错误
            }
        } else {
            // 处理客户端socket的事件:
            // 如果是EPOLLIN事件,读取数据
            // 如果是EPOLLOUT事件,发送数据
            // 如果有EPOLLERR或EPOLLHUP,处理断开连接
        }
    }
}

// 清理资源
close(epoll_fd);
// 关闭其他打开的sockets

这个例子中,我们通过对每个新接受的客户端连接调用 epoll_ctl,让单个 epoll 实例监控多个客户端连接。在服务端程序运行期间,`epoll_wait` 调用返回准备好的事件,然后我们根据事件类别(可读、可写、错误等)来处理每个客户端的socket。

使用这种方式,可以高效、可靠和可扩展地管理成千上万个并发连接。

2. 动态删除

在同一个 epoll 实例中动态地删除多个客户端套接字,可以通过调用 epoll_ctl 函数并指定 EPOLL_CTL_DEL 操作来实现。当决定不再监控某个文件描述符时,需要从 epoll 的监控列表中移除它,以避免无用的资源占用和可能的错误触发。

以下是一个简单的示例,说明如何删除多个套接字:

#include <sys/epoll.h>
// 其他必要的头文件...

int main() {
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1 failed");
        exit(EXIT_FAILURE);
    }

    // 假设我们有一个socket_fds数组,包含了要监控的所有客户端套接字文件描述符
    int socket_fds[] = { /* ... 客户端套接字文件描述符列表 ... */ };
    int num_sockets = sizeof(socket_fds) / sizeof(socket_fds[0]);

    // 将所有客户端套接字添加到epoll监控
    for (int i = 0; i < num_sockets; ++i) {
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = socket_fds[i];

        if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fds[i], &ev) == -1) {
            perror("epoll_ctl: ADD");
            exit(EXIT_FAILURE);
        }
    }

    // ... 在这里进行一些IO操作 ...

    // 假设现在要移除多个客户端套接字
    for (int i = 0; i < num_sockets; ++i) {
        if (需要删除的条件) { // 这里应该是具体的逻辑条件,用来判断哪些套接字需要被删除
            if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, socket_fds[i], NULL) == -1) {
                perror("epoll_ctl: DEL");
                // 即使删除失败,你可能也希望继续尝试删除其他套接字
            }
        }
    }

    // 清理并关闭epoll实例
    close(epoll_fd);
    return 0;
}

在上面的示例中,通过循环遍历一个包含多个套接字的数组,并使用条件来判断是否应该删除某个套接字。满足条件的套接字会通过 epoll_ctl 调用与 EPOLL_CTL_DEL 操作来从 epoll 实例中移除。在 EPOLL_CTL_DEL 操作中,事件参数可以是 NULL,因为删除操作不需要事件结构的信息。

在实际的并发服务器应用程序中,可能需要对资源访问进行同步,以防止出现竞态条件。如果应用程序是多线程的,确保在访问和修改与 epoll 实例相关的共享资源时使用适当的锁机制。

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

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

相关文章

一天吃透面试八股文

内容摘自我的学习网站&#xff1a;topjavaer.cn 分享50道Java并发高频面试题。 线程池 线程池&#xff1a;一个管理线程的池子。 为什么平时都是使用线程池创建线程&#xff0c;直接new一个线程不好吗&#xff1f; 嗯&#xff0c;手动创建线程有两个缺点 不受控风险频繁创…

滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编

滴水逆向三期笔记与作业——02C语言——10 Switch语句反汇编 一、Switch语句1、switch语句 是if语句的简写2、break加与不加有什么特点?default语句可以省略吗&#xff1f;3、游戏中的switch语句&#xff08;示例&#xff09;4、添加case后面的值&#xff0c;一个一个增加&…

LLM之llm-viz:llm-viz(3D可视化GPT风格LLM)的简介、安装和使用方法、案例应用之详细攻略

LLM之llm-viz&#xff1a;llm-viz(3D可视化GPT风格LLM)的简介、安装和使用方法、案例应用之详细攻略 目录 llm-viz的简介 1、LLM可视化 2、CPU模拟&#xff08;WIP&#xff1b;尚未公开&#xff01;&#xff09; llm-viz的安装和使用方法 llm-viz的案例应用 1、三维可视化…

【云原生】k8s图形化管理工具之rancher

k8s的图形化工具-----rancher rancher是一个开源的企业级多集群的k8s管理平台。 rancher和k8s区别: 都是为了容器的调度和编排系统&#xff0c;但是rancher不仅能够调度&#xff0c;还能管理k8s集群&#xff0c;自带监控(普罗米修斯)&#xff0c;大公司都是图形化。 ranche…

Stable Diffusion与Midjourney:如何做出明智之选?

Stable Diffusion与Midjourney&#xff1a;如何做出明智之选&#xff1f; 在人工智能领域中&#xff0c;Stable Diffusion和Midjourney是两个备受瞩目的技术。它们各自具有独特的特点和优势&#xff0c;但选择哪一个更适合您的需求呢&#xff1f;本文将为您详细分析两者的差异…

Linux 驱动开发基础知识—— 具体单板的 LED 驱动程序(五)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

蓝桥小白赛4 乘飞机 抽屉原理 枚举

&#x1f468;‍&#x1f3eb; 乘飞机 &#x1f437; 抽屉原理 import java.util.Scanner;public class Main {static int N 100010;static int[] a new int[N];public static void main(String[] args){Scanner sc new Scanner(System.in);int n sc.nextInt();int q s…

大数据安全 | 期末复习(下)

文章目录 &#x1f4da;安全策略和攻击&#x1f34b;&#x1f407;安全协议&#x1f407;IPsee&#x1f407;SSL&#x1f407;SSH&#x1f407;S/MIME协议&#x1f407;公钥基础设施PKI&#x1f407;PGP&#x1f407;HTTPS&#x1f407;防火墙&#x1f407;防毒墙&#x1f407;…

Django学习之小试牛刀

六、Django学习之小试牛刀 其他关于Python Web开发笔记&#xff1a;&#xff08;如果遇到问题可以一起交流~&#xff09; 一、Flask学习之HTML-CSDN博客 二、Flask学习之CSS-CSDN博客 【接上篇】二、Flask学习之CSS&#xff08;下篇&#xff09;-CSDN博客 三、Flask学习之B…

支付宝开通GPT4.0,最新经验分享

ChatGPT是由OpenAI开发的一种生成式对话模型&#xff0c;具有生成对话响应的能力。它是以GPT&#xff08;Generative Pre-trained Transformer&#xff09;为基础进行训练的&#xff0c;GPT是一种基于Transformer架构的预训练语言模型&#xff0c;被广泛用于各种自然语言处理任…

MYSQL库和表的操作(修改字符集和校验规则,备份和恢复数据库及库和表的增删改查)

文章目录 一、MSYQL库的操作1.连接MYSQL2.查看当前数据库3.创建数据库4.字符集和校验规则5.修改数据库6.删除数据库7.备份和恢复8.查看连接 二、表的操作1.创建表2.查看表结构3.修改表4.删除表 一、MSYQL库的操作 1.连接MYSQL 我们使用下面的语句来连接MSYQL&#xff1a; my…

Android发展历程及安装

目录 发展历程 下载网址 安装过程 发展历程 安卓基于Linux内核&#xff0c;Linux内核相当于房屋的地基 开源不等于免费&#xff0c;不能商用 安卓一般每半年小更新&#xff0c;一年大更新 对应API相当于别名 现在安卓安全性越来越高&#xff0c;性能越来越快&#xff0c…

基于Javaweb开发的二手图书零售系统详细设计【附源码】

基于Javaweb开发的二手图书零售系统详细设计【附源码】 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统…

Netty源码一:服务端启动

示例 public class Server {public static void main(String[] args) throws InterruptedException {// todo 创建两个 eventGroup boss 接受客户端的连接, 底层就是一个死循环, 不断的监听事件 处理事件// new NioEventLoopGroup(1); todo 入参1 表示设置boss设置为1个线程,…

Linux系统——点菜名

Linux系统可以点菜啦&#xff01; [rootlocalhost ~]#vim menu1.sh #!/bin/bash sum0 PS3"请输入(1-6):" MENU" 宫保鸡丁 酸菜鱼 鱼香肉丝 佛跳墙 水煮肉片 点菜结束 "select menu in $MENU do case $REPLY in 1) echo $menu 价格是20 let sum20 ;; 2) ec…

一个基于electron自动化桌面应用-流程图构建

前期工作已搞定&#xff0c;现在可以搭建桌面应用了。这个阶段可以结合前面定义好的数据格式构建流程图。 模板 还是使用熟悉的技术栈vite react electron&#xff0c;模板 流程图 官方文档 自定义 节点样式 因为配置化的操作类型较多&#xff0c;因此可以利用自定义节…

数据结构和算法笔记5:堆和优先队列

今天来讲一下堆&#xff0c;在网上看到一个很好的文章&#xff0c;不过它实现堆是用Golang写的&#xff0c;我这里打算用C实现一下&#xff1a; Golang: Heap data structure 1. 基本概念 满二叉树&#xff08;二叉树每层节点都是满的&#xff09;&#xff1a; 完全二叉树&a…

JAVA_Set系列集合:HashSet、LinkedHashSet、TreeSet底层详解

先看看 Set 系列集合的位置&#xff1a; Set 系列集合的特点&#xff1a; 无序&#xff1a;存取顺序不一致 如存入张三、李四、王五。而遍历获取到的是李四, 张三, 王五 不重复&#xff1a;可以去除重复无索引&#xff1a;没有带索引的方法&#xff0c;所以不能使用普通for循…

Redis缓存设计与性能优化

文章目录 多级缓存架构缓存设计缓存穿透缓存失效(击穿)缓存雪崩热点缓存key重建优化缓存与数据库双写不一致 开发规范与性能优化一、键值设计1. key名设计2. value设计bigkey的危害&#xff1a;bigkey的产生&#xff1a;如何优化bigkey 二、命令使用三、客户端使用Redis对于过期…

SpringBoot系列之MybatisPlus实现分组查询

SpringBoot系列之MybatisPlus实现分组查询 我之前博主曾记写过一篇介绍SpringBoot2.0项目怎么集成MybatisPlus的教程&#xff0c;不过之前的博客只是介绍了怎么集成&#xff0c;并没有做详细的描述各种业务场景&#xff0c;本篇博客是对之前博客的补充&#xff0c;介绍在mybat…