【linux高性能服务器编程】项目实战——仿QQ聊天程序源码剖析

news2024/12/23 14:27:16

hello !大家好呀! 欢迎大家来到我的Linux高性能服务器编程系列之项目实战——仿QQ聊天程序源码剖析,在这篇文章中,你将会学习到如何利用Linux网络编程技术来实现一个简单的聊天程序,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!

希望这篇文章能对你有所帮助9fe07955741149f3aabeb4f503cab15a.png,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!1a2b6b564fe64bee9090c1ca15a449e3.png(注:这章对于高性能服务器的架构非常重要哟!!!)

03d6d5d7168e4ccb946ff0532d6eb8b9.gif         

目录

一.项目介绍

二.服务器代码剖析

2.1 头文件和相关数据声明

2.2 服务器连接准备代码

2.3 服务器处理逻辑代码

2.3 客户端代码剖析


 

一.项目介绍

      像ssh这样的登录服务通常要同时处理网络连接和用户输入,这也可以使用I/O复用来实现。我们以poll为例实现一个简单的聊天室程序,以阐述如何使用I/O 复用技术来同时处理网络连接和用户输入。该聊天室程序能让所有用户同时在线群聊,它分为客户端和服务器两个部分。其中客户端程序有两个功能:一是从标准输入终端读入用户数据,并将用户数据发送至服务器;二是往标准输出终端打印服务器发送给它的数据。服务器的功能是接收,客户数据,并把客户数据发送给每一个登录到该服务器上的客户端(数据发送者除外)。下面我们依次给出客户端程序和服务器程序的代码。

二.服务器代码剖析

2.1 头文件和相关数据声明

#define _GNU_SOURCE 1
#include<t_stdio.h>
#include<t_file.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/poll.h>
#include<fcntl.h>
#include<errno.h>
#define user_limit 5 //最大客户连接数量
#define buffer_size 64
#define fd_limit 65535 //最大文件描述符数量

struct client_data{//创建一个客户地址结构体
    struct sockaddr_in address ; 
    char * write_buf;
    char buf[buffer_size];
};
int setnonblocking (int fd){//将文件描述符改为非阻塞模式
    int old_option = fcntl(fd , F_GETFL);
    int new_option = old_option | O_NONBLOCK;// 添加非阻塞选项
    fcntl(fd , F_SETFL , new_option);//设置
    return old_option;

}

这部分代码包含了头文件,定义了一些宏,以及一个用于存储客户端数据的结构体,这个结构体是为了服务器更好控制来自客户端的socket套接字,以及灵活控制对socket套接字的读写,然后还定义了setnonblocking()函数来将传入的文件描述符利用fcntl函数改为非阻塞模式,方便服务器进行监听。

2.2 服务器连接准备代码

这段代码是服务器端程序的入口和初始化部分。下面是逐行的解释:

int main(int argc , char *argv[]){
    if(argc <= 2)//如果参数太少
    {
        printf("usage :%s ip_address port_number\n",basename(argv[0]));
        return 1;
    }

这段代码检查命令行参数的数量。如果参数少于两个(程序名称和IP地址/端口号),则打印使用说明并退出程序。

    const char * ip = argv[1] ;// 提取ip地址
    int port = atoi(argv[2]); //提取端口号

从命令行参数中提取服务器的IP地址和端口号。

    struct sockaddr_in address ; //服务器地址
    bzero(&address ,sizeof(address));//清空
    address.sin_family = AF_INET;
    inet_pton(AF_INET , ip ,&address.sin_addr);//设置ip
    address.sin_port = htons(port); //设置端口号

这里创建了一个sockaddr_in结构体来存储服务器的地址信息,并使用bzero函数将其清零。然后设置地址族为AF_INET(IPv4),使用inet_pton函数将点分十进制的IP地址转换为网络字节序的格式,并存储在sin_addr字段中。最后,将端口号从主机字节序转换为网络字节序并存储在sin_port字段中。

    int listenfd = socket(PF_INET ,SOCK_STREAM , 0);//创建监听套接字
    assert(listenfd >=0);

创建一个TCP套接字(SOCK_STREAM)用于监听客户端连接,并检查套接字是否创建成功。

    int ret = bind(listenfd , (struct sockaddr*)&address , sizeof(address));//绑定
    assert(ret !=-1);

将套接字绑定到之前设置的服务器地址上,并检查绑定操作是否成功。

    ret = listen(listenfd ,5);//最多同时监听五个
    assert(ret!=-1);

调用listen函数,使套接字进入监听状态,并设置最大同时连接数为5。然后检查监听操作是否成功。

    //创建user数组,放入多个客户对象,并且使用socket的值可以直接用来索引(作为数组下标)连接对应的client_data对象
    struct client_data * user = malloc(fd_limit * sizeof(struct client_data));
   //为了提高poll性能,限制用户数量
    struct pollfd *fds = malloc(sizeof(struct pollfd) * 6);
    int user_counter = 0;//计算客户连接数量
    int i=0;
    for( i =  1 ; i<=user_limit ; ++i){//对每个fds数据初始化
        fds[i].fd = -1;
        fds[i].events =0;
    }

这段代码分配了两个数组:user数组用于存储客户端数据,fds数组用于poll函数。user数组的大小被设置为fd_limit,这是一个预定义的最大文件描述符数量。fds数组的大小被设置为6,这是因为服务器程序只监听一个套接字(listenfd),而其余的用于客户端连接。user_counter用于跟踪当前连接的客户端数量。fds数组的其余元素被初始化为-1,表示没有对应的文件描述符。

    //初始化怕poll中第一个数据:监听套接字
    fds[0].fd = listenfd;
    fds[0].events = POLLIN | POLLERR;
    fds[0].revents = 0;

最后,将监听套接字listenfd添加到fds数组中,并设置其监听的事件为可读事件(POLLIN)和错误事件(POLLERR)。revents字段用于poll函数返回时存储发生的事件,在这里初始化为0。

这段代码为服务器程序的后续操作设置了基础,包括套接字的创建和绑定,以及用于poll函数的数组的初始化。

2.3 服务器处理逻辑代码

这段代码是服务器程序的主循环,它使用poll系统调用来监控多个文件描述符(fds数组)的事件。这个循环会一直运行,直到遇到错误或者被显式地退出。

while(1){

这是一个无限循环,服务器程序将一直运行直到出现错误或者执行了退出循环的操作。

    ret = poll(fds , user_counter+1 , -1);//开始监听
    if(ret <0) {
        printf("poll failed..\n");
        break;
    }

在循环的顶部,调用poll函数来等待事件发生。fds数组包含了所有需要监控的文件描述符,user_counter+1表示总共有user_counter个客户端连接加上监听套接字listenfd-1表示poll函数将阻塞直到至少有一个文件描述符上有事件发生。如果poll调用失败(返回值小于0),则打印错误信息并退出循环。

    for ( i =0 ; i <user_counter+1;i++){//每次对整个fds数组进行遍历处理
        if(fds[i].fd==listenfd && (fds[i].revents & POLLIN)){//如果为第一个监听字符且发生可读事件时
            struct sockaddr_in client_address;//创建一个新客户套接字
            socklen_t client_addrlength = sizeof(client_address);
            int connfd = accept(listenfd ,(struct sockaddr*)&client_address ,&client_addrlength );//获取客户端套接字
            if(connfd<0){//连接错误
                printf("erron is:%dd\n");
                continue;
            }
            if(user_counter >=user_limit){//用户太多
                const char * info ="too many users\n";
                printf("%s\n",info);
                send(connfd , info ,strlen(info) , 0);//发送错误给客户端
                close(connfd);
                continue;
            }
            //对于新连接 ,我们要同时修改fds和users数组,user[connfd]即对应客户端数据
            user_counter++;//客户数量加一
            user[connfd].address = client_address;
            setnonblocking(connfd);//设置为非阻塞模式
            fds[user_counter].fd = connfd;//最新数据放入数组
            fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;
            fds[user_counter].revents = 0;
            printf("comes a new user , now have %d user\n",user_counter);
        }
        // ... 其他事件处理逻辑 ...
    }

这个循环遍历fds数组中的每个文件描述符,检查它们是否有事件发生。对于每个事件,服务器程序执行相应的操作:

  1. 如果监听套接字(listenfd)上有新的连接请求(POLLIN事件),服务器接受新连接,并将新的文件描述符(connfd)添加到fds数组中。如果连接数超过限制(user_limit),服务器会发送一个错误消息并关闭新连接。

  2. 如果有任何文件描述符上有POLLERR事件,表示发生了错误,服务器会打印错误信息。

  3. 如果有任何已连接的套接字上有POLLIN事件,表示有数据可读,服务器会读取数据并打印。

  4. 如果有任何套接字上有POLLRDHUP事件,表示对方已经关闭了连接,服务器会关闭对应的连接并更新fds数组。

  5. 如果有任何套接字上有POLLOUT事件,表示可以写数据,服务器会发送数据(如果有数据要发送)。

在循环结束后,服务器程序会继续执行下一次循环,等待更多的连接和事件。

在服务器端代码中,poll函数用于监控多个文件描述符的事件。poll函数的返回值表示有多少个文件描述符发生了事件,而每个文件描述符的事件类型存储在revents字段中。下面是服务器端代码中使用poll函数监控的不同事件类型及其解释:

if(fds[i].fd==listenfd && (fds[i].revents & POLLIN)){
    // ... 接受连接逻辑 ...
}
else if(fds[i].revents & POLLERR){
    // ... 错误处理逻辑 ...
}
else if(fds[i].revents & POLLIN){
    // ... 读取数据逻辑 ...
}
else if(fds[i].revents & POLLRDHUP){
    // ... 关闭连接逻辑 ...
}
else if(fds[i].revents & POLLOUT){
    // ... 写数据逻辑 ...
}
  1. POLLIN: 这个事件表示文件描述符上有数据可读。对于服务器来说,这意味着有新的客户端连接请求或者已连接的客户端有数据发送过来。

  2. POLLERR: 这个事件表示文件描述符发生了错误。可能是网络错误,也可能是其他类型的错误。服务器需要检查并处理这些错误。

  3. POLLRDHUP: 这个事件表示文件描述符的读端已经被对方关闭。这通常发生在客户端突然断开连接的情况下。

  4. POLLOUT: 这个事件表示文件描述符的写端准备好了,可以写入数据。对于服务器来说,这意味着它可以向客户端发送数据。

服务器程序通过检查fds数组中每个文件描述符的revents字段,来确定发生了哪种事件,并相应地执行处理逻辑。如果没有任何事件发生,poll函数会阻塞,直到至少有一个文件描述符上有事件发生。服务器程序通过这种方式可以高效地处理多个客户端连接。

2.3 客户端代码剖析

这段代码是一个简单的客户端程序,用于连接到一个服务器,并通过标准输入和输出与服务器进行通信

#define _GNU_SOURCE 1
#include<t_stdio.h>
#include<t_file.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/poll.h>
#include<fcntl.h>
#include<poll.h>
#define buffer_size 64 //缓冲区大小

这段代码包含了必要的头文件和宏定义。_GNU_SOURCE是一个宏,它用于启用一些GNU扩展,如splice系统调用。

int main(int argc , char * argv[])
{
    if(argc <= 2)//如果参数太少
    {
        printf("usage :%s ip_address port_number\n",basename(argv[0]));
        return 1;
    }

这段代码检查命令行参数的数量。如果参数少于两个(程序名称和IP地址/端口号),则打印使用说明并退出程序。

    const char * ip = argv[1] ;// 提取ip地址
    int port = atoi(argv[2]); //提取端口号

从命令行参数中提取服务器的IP地址和端口号。

    struct sockaddr_in server_address ; //服务器地址
    bzero(&server_address ,sizeof(server_address));//清空
    server_address.sin_family = AF_INET;
    inet_pton(AF_INET , ip ,&server_address.sin_addr);//设置ip
    server_address.sin_port = htons(port); //设置端口号

创建一个sockaddr_in结构体来存储服务器的地址信息,并使用bzero函数将其清零。然后设置地址族为AF_INET(IPv4),使用inet_pton函数将点分十进制的IP地址转换为网络字节序的格式,并存储在sin_addr字段中。最后,将端口号从主机字节序转换为网络字节序并存储在sin_port字段中。

    int sockfd = socket(PF_INET , SOCK_STREAM , 0 );//创建本地套接字
    assert(socket >= 0 ); //判错
    if(connect(sockfd , (struct sockaddr *)&server_address , sizeof(server_address)) < 0){//连接失败的话
        printf("connection failed...\n");
        close(sockfd);
        return 1;
    }

创建一个TCP套接字(SOCK_STREAM)用于与服务器通信,并检查套接字是否创建成功。然后尝试连接到服务器。如果连接失败,打印错误信息并退出程序。

    struct pollfd fds[2];//创建pollfd结构类型数组,注册标准输入和sockfd文件描述符上的可读事件
    fds[0].fd = 0;
    fds[0].events = POLLIN ;//标准输入可读
    fds[0].revents = 0; //实际发生事件,由内核填充
    fds[1].fd = sockfd;
    fds[1].events = POLLIN | POLLRDHUP ;//标准输入可读
    fds[1].revents = 0; //实际发生事件,由内核填充

创建一个pollfd结构体数组,用于监控标准输入(0)和套接字(sockfd)上的可读事件。

    while (1){
        ret = poll(fds , 2 , -1); //最大被监听事件只有两个, 返回符合条件文件总数
        if(ret < 0){//如果监听发生错误
            printf("poll falied..\n");
            break;
        }

在循环的顶部,调用poll函数来等待事件发生。fds数组包含了所有需要监控的文件描述符,2表示总共有两个文件描述符(标准输入和套接字)。-1表示poll函数将阻塞直到至少有一个文件描述符上有事件发生。如果poll调用失败(返回值小于0),则打印错误信息并退出循环。

        if(fds[1].revents & POLLRDHUP){//假如发生了关闭对端连接
            printf("server close the connection..\n");
            break;
        }
        else if(fds[1].revents & POLLIN){//假如sockfd文件发生可读,则读取服务器传来数据
            memset(readbuf , '\0' , buffer_size);
            recv(fds[1].fd , readbuf , buffer_size -1 , 0);//接收数据
             if(ret <= 0){// 如果接收失败或对方关闭了连接
                printf("server close the connection..\n");
               break;
           }
            printf("%s\n",readbuf);//打印数据
        }

 if(fds[0].revents & POLLIN){//标准输入文件描述符可读,说明我们需要写入数据

        ret = splice(0 , NULL , pipefd[1] , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);//从标准输入写入数据到管道写端
        ret = splice(pipefd[0] , NULL , sockfd , NULL ,32768 , SPLICE_F_MORE | SPLICE
        ret = splice(0 , NULL , pipefd[1] , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);//从标准输入写入数据到管道写端
        ret = splice(pipefd[0] , NULL , sockfd , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);//从管道读端将数据传输到sockfd
        printf("ok");
        }

    }
    close(sockfd);
    return 0;
}

这段代码检查套接字(sockfd)上的事件。如果套接字上有POLLRDHUP事件,表示对方已经关闭了连接,服务器会关闭对应的连接并退出循环。如果套接字上有POLLIN事件,表示有数据可读,服务器会读取数据并打印。

这段代码是客户端程序主循环的最后一部分,它处理标准输入(0)上的数据,并通过管道(pipefd)将其传输到套接字(sockfd)上。

  1. ret = splice(0 , NULL , pipefd[1] , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);:

    • splice是一个系统调用,用于直接在内核空间复制数据,避免了用户空间和内核空间之间的数据拷贝。
    • 第一个参数是源文件描述符,这里是从标准输入0
    • 第二个参数是源文件描述符的偏移量,这里为NULL,表示从文件开始读取。
    • 第三个参数是目标文件描述符,这里是对应的管道写端pipefd[1]
    • 第四个参数是目标文件描述符的偏移量,这里为NULL,表示从文件开始写入。
    • 第五个参数是传输的数据量,这里为32768,是一个系统定义的常量,表示最多传输32768字节。
    • 第六个参数是SPLICE_F_MORE,表示这只是一个中间步骤,还有更多的数据要传输。
    • 第七个参数是SPLICE_F_MOVE,表示传输的数据是从内核缓冲区直接移动,而不是复制。
  2. ret = splice(pipefd[0] , NULL , sockfd , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);:

    • 类似地,这段代码使用splice系统调用来从管道读端pipefd[0]传输数据到套接字sockfd
  3. printf("ok");:

    • 打印"ok"表示数据传输成功。

循环继续执行,重复上述操作,直到连接被关闭或出现错误。

  1. close(sockfd);:

    • 关闭套接字sockfd,释放资源。
  2. return 0;:

    • 程序返回0,表示正常退出。

这个客户端程序通过poll系统调用来监控标准输入和套接字的事件,并通过splice系统调用来高效地传输数据。它使用管道作为中间缓冲区,以避免在用户空间和内核空间之间进行数据拷贝。

   好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦4d7d9707063b4d9c90ac2bca034b5705.png!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!2cd0d6ee4ef84605933ed7c04d71cfef.jpeg 

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

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

相关文章

Flutter应用下拉菜单设计DropdownButtonFormField控件介绍

文章目录 DropdownButtonFormField介绍使用方法重点代码说明属性解释 注意事项 DropdownButtonFormField介绍 Flutter 中的 DropdownButtonFormField 是一个用于在表单中选择下拉菜单的控件。它是 DropdownButton 和 TextFormField 的组合&#xff0c;允许用户从一组选项中选择…

使用工具速记

文章目录 一、sqlyoy登录账号信息迁移二、idea导入之前的已配置的idea信息三、设置windows UI大小四、其他 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、sqlyoy登录账号信息迁移 工具(sqlyog上面菜单栏)->导入导出详情->选择要导出的账号…

day03-(docker)

文章目录 DockerDocker和虚拟机的差别docker在linux安装配置镜像命令容器命令介绍Docker-容器&#xff08;基本操作&#xff09;docker基本操作&#xff08;数据卷&#xff09;数据卷挂载直接挂载四.Dockerfile自定义镜像五.Docker-Compose 安装修改权限镜像仓库![在这里插入图…

Vscode上使用Clang,MSVC, MinGW, (Release, Debug)开发c++完全配置教程(包含常见错误),不断更新中.....

1.VSCode报错头文件找不到 clang(pp_file_not_found) 在Fallback Flags中添加 -I&#xff08;是-include的意思&#xff0c;链接你的编译器对应头文件地址&#xff0c;比如我下面的是MSVC的地址&#xff09; 问题得到解决~

Docker基本操作 容器相关命令

docker run:运行镜像; docker pause:暂停容器&#xff0c;会让该容器暂时挂起&#xff1b; docker unpauser:从暂停到运行; docker stop:停止容器&#xff0c;杀死进程; docker start:重新创建进程。 docker ps&#xff1a;查看所有运行的容器及其状态&#xff0c;默认只展…

城市建筑轮廓矢量边界、建设用地数据、城市道路网分布、城市土地利用规划分布、土地利用数据、城市绿地分布

数据下载链接&#xff1a;数据下载链接 中国主要城市建筑底面轮廓和建筑高度空间分布数据&#xff0c;包括省会城市、地级市及县级市等主要城市。城市建筑底面轮廓和建筑高度数据&#xff0c;数据坐标为 WGS84地理坐标&#xff0c; 数据格式为 SHP 文件。数据范围基本覆盖城市…

OceanBase开发者大会实录 - 阳振坤:云时代的数据库

本文来自2024 OceanBase开发者大会&#xff0c;OceanBase 首席科学家阳振坤的演讲实录——《云时代的数据库》。完整视频回看&#xff0c;请点击这里 >> 在去年的开发者大会中&#xff0c;我跟大家分享了我对数据库产品和技术一些看法&#xff0c;包括单机分布式一体化&…

openjudge_2.5基本算法之搜索_200:Solitaire

题目 200:Solitaire 总时间限制: 5000ms 单个测试点时间限制: 1000ms 内存限制: 65536kB 描述 Solitaire is a game played on a chessboard 8x8. The rows and columns of the chessboard are numbered from 1 to 8, from the top to the bottom and from left to right resp…

maven-idea新建和导入项目

全局配置 新建项目 需要新建的文件夹 src/testsrc/test/javasrc/main/java 注&#xff1a;1、新建Java-class&#xff0c;输入.com.hello.hellomaven 2、快捷键psvm显示 public static void main(String[] args) {.... } package com.hello;public class hellomaven {publ…

Java-字符集和字符编码-roadmap

1 需求 2 接口 3 示例 4 参考资料 「烫烫屯屯锟斤拷」揭秘ASCII、GBK、UTF-8&#xff0c;B站独家&#xff0c;一听就懂_哔哩哔哩_bilibili 非常详细的字符编码讲解&#xff0c;ASCII、GB2312、GBK、Unicode、UTF-8等知识点都有_哔哩哔哩_bilibili 你懂乱码吗&#xff1f;锟斤…

Feign负载均衡

Feign负载均衡 概念总结 工程构建Feign通过接口的方法调用Rest服务&#xff08;之前是Ribbon——RestTemplate&#xff09; 概念 官网解释: http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign Feign是一个声明式WebService客户端。使用Feign能让…

Vitis HLS 学习笔记--Syn Report解读(1)

目录 1. 介绍 2. 示例一 2.1 HLS 代码 2.2 Report 解读 2.2.1 General Information 2.2.2 Timing Estimate 2.2.3 Performance & Resource Estimates 2.2.4 HW interfaces 2.2.4.1 硬件接口报告 2.2.4.2 导出至 Vivado 中的 IP 2.2.4.3 Port-Level Protocols 端…

【小梦C嘎嘎——启航篇】C++四大类型转换

&#x1f60e; 前言&#x1f64c;C四大类型转换什么是类型转换C语言中的类型转换为什么C要嫌弃C语言的类型转换&#xff1f;自行搞一套呢&#xff1f;C强制类型转换1、static_cast2、reinterpret_cast3、const_cast4、dynamic_cast为什么要支持向下转呢&#xff1f; RTTI 总结撒…

C++之STL-list+模拟实现

目录 一、list的介绍和基本使用的方法 1.1 list的介绍 1.2 list的基本使用方法 1.2.1 构造方法 1.2.2 迭代器 1.2.3 容量相关的接口 1.2.4 增删查改的相关接口 1.3 关于list迭代器失效的问题 二、模拟实现list 2.1 节点类 2.2 迭代器类 2.3 主类list类 2.3.1 成员变…

yolov8 dll 编译

1. 每次用yolo v8 都要用python &#xff0c;对于我这种写软件的太不方便了&#xff0c;下面尝试编译dll 调用, 我已经有做好的模型.best.pt 参考视频方法: yolov8 TensorRT C 部署_哔哩哔哩_bilibili 【yolov8】tensorrt部署保姆级教程&#xff0c;c版_哔哩哔哩_bilibili 需…

C语言基础知识笔记——万字学习记录

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本文主要参考浙大翁恺老师的C语言讲解以及其他博主的C语言学习笔记&#xff0c;进而梳理C语言的基础知识&#xff0c;为后续系统性学习数据结构和其他语言等知识夯实一定的基础。&#xff08;其他博主学习笔记的链接包括&#x…

陕西省人力资源和社会保障厅 陕西省住房和城乡建设厅 关于开展2023年度全省建设工程专业高级工程师评审工作的通知

陕西工程系列建设工程专业工程师评审工作要求链接陕西省人力资源和社会保障厅 陕西省住房和城乡建设厅 关于开展2023年度全省建设工程专业高级工程师评审工作的通知 - 陕西省住房和城乡建设厅类别基本条件业绩成果备注助理工程师 最新公告http://www.snhrm.com/zxggao2/597358…

怎么排查K8S容器当中的Java程序内存泄露问题

今天早上发现生产线其中的一个服务在凌晨的时候突然重启了&#xff0c;内存突然从1G升到1.8G&#xff0c;CPU使用量从0.1升到了0.28&#xff0c;说明在这个时间点&#xff0c;内存突增达到了限额以上&#xff0c;服务重启了。因为这个服务布署了多节点&#xff0c;这次重启对业…

不同技术实现鼠标滚动图片的放大缩小

摘要&#xff1a; 最近弄PC端的需求时&#xff0c;要求在layui技术下实现鼠标滚动图片的放大缩小的功能&#xff01;下面来总结一下不同框架剩下这功能&#xff01; layui: 看了一下layui文档&#xff0c;其实这有自带的组件的&#xff01;但是又版本要求的!并且layui的官方文档…

element 分页切换时:current-page无效 页数不会跟着一起切换

问题回溯&#xff1a;使用el-pagination组件 选择切换当前分页 页数为2 问题结果&#xff1a;el-pagination组件 当前页切换失败 一直都是 1&#xff0c;接口传参分页数据是2&#xff0c;打印当前分页也是2 解决方案1&#xff1a;使用 current-page参数 .sync 修饰符 解决方案2…