IPC:匿名管道和命名管道

一 管道初级测试

写两个小程序,一个负责向管道发数据,一个从管道接收数据;

pipe.cpp

#include <iostream>
using namespace std;

int main()
{
    cout << "hello world" << endl;
    return 0;
}

pipe2.cpp 

#include <iostream>
#include <stdio.h>

using namespace std;
#define DEBUG_INFO(format, ...) printf("%s:%d -- " format "\n", __func__, __LINE__,##__VA_ARGS__)

int main()
{
    string str;
    while(1){
        str.clear();
        cin >> str;
        if(str.length() == 0)break;
        DEBUG_INFO("%s",str.c_str());
    }
    
    return 0;
}

编译生成pipe和pipe2两个可执行文件:

$ls -lsh pipe*
12K -rwxrwxr-x 1 lkmao lkmao 9.1K 5月   6 15:52 pipe
16K -rwxrwxr-x 1 lkmao lkmao  14K 5月   6 15:52 pipe2

执行如下指令:

$ ./pipe | ./pipe2 
main:14 -- hello
main:14 -- world

第二个程序接收到数据了,那么问题来了,代码中怎么没有管道。执行程序那根竖线就是管道。

strace命令跟踪

strace ./pipe | ./pipe2 

 strace ./pipe

strace ./pipe | strace ./pipe2  

 如图所示,S_IFIFO和S_IFCHR不一样,这两个宏的含义如下所示

S_IFCHR:文件是一个特殊的字符设备

S_IFIFO:文件是一个FIFO设备

也就是说,如果是S_IFIFO,那么文件描述符1表示,它对应的打开的文件是个管道。如果是S_IFCHR,则表示对应的文件是个字符设备,也就是终端,终端也是个字符设备,所以没毛病,就是常说的标准输出。

在命令./pipe | ./pipe2 中,pipe程序向管道输出"hello world" pipe2从管道中读取数据。

二 pipe函数创建匿名管道

#include <unistd.h>

int pipe(int pipefd[2]);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>

int pipe2(int pipefd[2], int flags);

测试,使用pipe创建管道,使用fork创建一个子进程,进程中向向1 写入数据,父进程从0读出数据

测试代码1:传递一个字符串

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#define DEBUG_INFO(format, ...) printf("%s:%d -- " format "\n", __func__, __LINE__,##__VA_ARGS__)
using namespace std;

int main()
{
    int fds[2];
    pid_t pid;
    int ret = pipe(fds);
    if(ret != 0){
        perror("pipe");
        exit(0);
    }
    char send_buf[100];
    char read_buf[100];
    memset(read_buf, 0, sizeof(read_buf));
    memset(send_buf, 0, sizeof(send_buf));
    DEBUG_INFO("%d %d",fds[0],fds[1]);
    cout << "create pipe ok" << endl;
    pid = fork();
    if(pid == -1){
        perror("fork");
        exit(-1);
    }
    
    
    if(pid == 0){
        
        int send_len = snprintf(send_buf, sizeof(send_buf),"wo shi child %u",getpid());
        DEBUG_INFO("write:send_len = %d,buf = %s",send_len,send_buf);
        int ret = write(fds[1], send_buf,send_len);
        if(ret == -1){
            perror("write");
            exit(-1);
        }
        DEBUG_INFO("child write finish:ret = %d,buf = %s",ret,send_buf);


        sleep(1);
    }else{
        int read_len = read(fds[0],read_buf,sizeof(read_buf));
        DEBUG_INFO("parent read finish:len = %d,buf = %s",read_len,read_buf);
        sleep(1);
    }
    DEBUG_INFO("bye bye %d",getpid());
    return 0;
}

测试结果:

main:23 -- 3 4
create pipe ok
main:35 -- write:send_len = 18,buf = wo shi child 46834
main:41 -- child write finish:ret = 18,buf = wo shi child 46834
main:47 -- parent read finish:len = 18,buf = wo shi child 46834
main:50 -- bye bye 46834
main:50 -- bye bye 46833

测试代码2:传递一个结构体:

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#define DEBUG_INFO(format, ...) printf("%s:%d -- " format "\n", __func__, __LINE__,##__VA_ARGS__)
using namespace std;
struct mystruct{
    int type;
    int len;
    int frame_size;
    int frame_index;
    int frame_count;
    int offset;
    int length;
    char buf[128];
    uint32_t crc;
};

int main()
{
    int fds[2];
    pid_t pid;
    int ret = pipe(fds);
    if(ret != 0){
        perror("pipe");
        exit(0);
    }
    struct mystruct *ms1 = (struct mystruct *)malloc(sizeof(struct mystruct));
    struct mystruct *ms2 = (struct mystruct *)malloc(sizeof(struct mystruct));
    char send_buf[100];
    char read_buf[100];
    memset(read_buf, 0, sizeof(read_buf));
    memset(send_buf, 0, sizeof(send_buf));
    DEBUG_INFO("%d %d",fds[0],fds[1]);
    cout << "create pipe ok" << endl;
    pid = fork();
    if(pid == -1){
        perror("fork");
        exit(-1);
    }
    
    
    if(pid == 0){
        ms1->type = 1001;
        ms1->crc = 0x1001;

        ms2->type = 1002;
        ms2->crc = 0x1002;
        ret = write(fds[1], ms1,sizeof(struct mystruct));
        if(ret == -1){
            perror("write");
            exit(-1);
        }
        ret = write(fds[1], ms2,sizeof(struct mystruct));
        if(ret == -1){
            perror("write");
            exit(-1);
        }
        DEBUG_INFO("child %u write finish",getpid());


        sleep(1);
    }else{
        sleep(2);
        int read_len_1 = read(fds[0],ms1,sizeof(struct mystruct));
        int read_len_2 = read(fds[0],ms2,sizeof(struct mystruct));

        DEBUG_INFO("parent read finish:len = %d,crc = %04x",read_len_1,ms1->crc);
        DEBUG_INFO("parent read finish:len = %d,crc = %04x",read_len_2,ms2->crc);
        sleep(1);
    }
    DEBUG_INFO("bye bye %d",getpid());
    return 0;
}

在父进程中睡眠两秒,是为了保证让子进程先写完两次。 

测试结果:

main:37 -- 3 4
create pipe ok
main:62 -- child 47519 write finish
main:75 -- bye bye 47519
main:71 -- parent read finish:len = 160,crc = 1001
main:72 -- parent read finish:len = 160,crc = 1002
main:75 -- bye bye 47518

从测试结果可知:

1 管道可以用于传输结构体

2 管道中的传输的数据先写先到达,先被读

例如这些特点,和管道队列满的特点,就可以实现管道的双向通信了。

测试一下PIPE_BUF

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/limits.h>

#define DEBUG_INFO(format, ...) printf("%s:%d -- " format "\n", __func__, __LINE__,##__VA_ARGS__)
using namespace std;
struct mystruct{
    int type;
    int len;
    int frame_size;
    int frame_index;
    int frame_count;
    int offset;
    int length;
    char buf[PIPE_BUF];
    uint32_t crc;
};

int main()
{
    int fds[2];
    pid_t pid;
    int ret = pipe(fds);
    if(ret != 0){
        perror("pipe");
        exit(0);
    }
    DEBUG_INFO("PIPE_BUF = %d",PIPE_BUF);
    struct mystruct *ms1 = (struct mystruct *)malloc(sizeof(struct mystruct));
    struct mystruct *ms2 = (struct mystruct *)malloc(sizeof(struct mystruct));
    char send_buf[100];
    char read_buf[100];
    memset(read_buf, 0, sizeof(read_buf));
    memset(send_buf, 0, sizeof(send_buf));
    DEBUG_INFO("%d %d",fds[0],fds[1]);
    cout << "create pipe ok" << endl;
    pid = fork();
    if(pid == -1){
        perror("fork");
        exit(-1);
    }
    
    
    if(pid == 0){
        ms1->type = 1001;
        ms1->crc = 0x1001;

        ms2->type = 1002;
        ms2->crc = 0x1002;
        ret = write(fds[1], ms1,sizeof(struct mystruct));
        if(ret == -1){
            perror("write");
            exit(-1);
        }
        DEBUG_INFO("ret = %d",ret);
        ret = write(fds[1], ms2,sizeof(struct mystruct));
        if(ret == -1){
            perror("write");
            exit(-1);
        }
        DEBUG_INFO("ret = %d",ret);
        DEBUG_INFO("child %u write finish",getpid());

        sleep(1);
    }else{
        sleep(2);
        int read_len_1 = read(fds[0],ms1,sizeof(struct mystruct));
        int read_len_2 = read(fds[0],ms2,sizeof(struct mystruct));

        DEBUG_INFO("parent read finish:len = %d,crc = %04x",read_len_1,ms1->crc);
        DEBUG_INFO("parent read finish:len = %d,crc = %04x",read_len_2,ms2->crc);
        sleep(1);
    }
    DEBUG_INFO("bye bye %d",getpid());
    return 0;
}

测试结果:

main:33 -- PIPE_BUF = 4096
main:40 -- 3 4
create pipe ok
main:60 -- ret = 4128
main:66 -- ret = 4128
main:67 -- child 47924 write finish
main:79 -- bye bye 47924
main:75 -- parent read finish:len = 4128,crc = 1001
main:76 -- parent read finish:len = 4128,crc = 1002
main:79 -- bye bye 47923

PIPE_BUF的值是4096

写一个测试程序,

测试看看写入多少时,会写满出错。在此例中,将管道描述符设置为非阻塞模式:

1 子进程循环向管道写数据,直到出错返回-1

2 父进程循环从管道读数据,直到出错返回-1

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/limits.h>
#include <fcntl.h>

#define DEBUG_INFO(format, ...) printf("%s:%d -- " format "\n", __func__, __LINE__,##__VA_ARGS__)
using namespace std;
struct mystruct{
    int type;
    int len;
    int frame_size;
    int frame_index;
    int frame_count;
    int offset;
    int length;
    char buf[PIPE_BUF];
    uint32_t crc;
};

int main()
{
    int fds[2];
    pid_t pid;
    int ret = pipe(fds);
    if(ret != 0){
        perror("pipe");
        exit(0);
    }
    DEBUG_INFO("PIPE_BUF = %d",PIPE_BUF);
    struct mystruct *ms1 = (struct mystruct *)malloc(sizeof(struct mystruct));
    char send_buf[100];
    char read_buf[100];
    memset(read_buf, 0, sizeof(read_buf));
    memset(send_buf, 0, sizeof(send_buf));
    DEBUG_INFO("%d %d",fds[0],fds[1]);
    cout << "create pipe ok" << endl;
    pid = fork();
    if(pid == -1){
        perror("fork");
        exit(-1);
    }
    
    
    if(pid == 0){
        
        int flag = fcntl(fds[1], F_GETFL, 0);
        flag |= O_NONBLOCK;
        fcntl(fds[1], F_SETFL, flag);
        int count = 0;
        while(1){
            ms1->type = 1001 + count;
            ms1->crc = 0x1001 + count;
            ret = write(fds[1], ms1,sizeof(struct mystruct));
            if(ret == -1){
                perror("write");
                break;
            }
            DEBUG_INFO("ret = %d",ret);
            count++; 
        }
        DEBUG_INFO("write %d cuccess",count * sizeof(struct mystruct));
        DEBUG_INFO("child %u write finish",getpid());

        sleep(1);
    }else{
        sleep(20);
        int flag = fcntl(fds[0], F_GETFL, 0);
        flag |= O_NONBLOCK;
        fcntl(fds[0], F_SETFL, flag);
        while(1){
            ret = read(fds[0],ms1,sizeof(struct mystruct));
            if(ret == -1){
                perror("read");
                break;
            }
            DEBUG_INFO("ret = %d,%d,%04x",ret,ms1->type,ms1->crc);

        }
        DEBUG_INFO("parent");
    }
    DEBUG_INFO("bye bye %d",getpid());
    return 0;
}

 测试结果:

main:34 -- PIPE_BUF = 4096
main:40 -- 3 4
create pipe ok
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4096
write: Resource temporarily unavailable
main:66 -- write 45408 cuccess
main:67 -- child 48554 write finish
main:86 -- bye bye 48554
main:81 -- ret = 4128,1001,1001
main:81 -- ret = 4128,1002,1002
main:81 -- ret = 4128,1003,1003
main:81 -- ret = 4128,1004,1004
main:81 -- ret = 4128,1005,1005
main:81 -- ret = 4128,1006,1006
main:81 -- ret = 4128,1007,1007
main:81 -- ret = 4128,1008,1008
main:81 -- ret = 4128,1009,1009
main:81 -- ret = 4128,1010,100a
main:81 -- ret = 4096,1011,100a
read: Resource temporarily unavailable
main:84 -- parent
main:86 -- bye bye 48553

从结果可知,write返回结果的前一次,返回结果是4096,小于结构体的大小。这里需要判断,这个返回的4096,是不是表示写了4096个字节。正式项目时,这里还要剩下的没写成功的数据写完。

父进程读数据,最后读回了4096个字节,也是要判断数据的完整性。防止误判。

至少,管道中到底能存多少数据。这个要不同的操作系统,在使用之前测试一下。大部分情况下,只要满足需要就可以了。

测试代码修改为阻塞模式:就是注释掉下图中的两段代码

 一不小心,就出来了一大堆,总之阻塞模式下是不会出现写一半的情况的。

main:81 -- ret = 4128,75163,131b3
main:63 -- ret = 4128
main:63 -- ret = 4128
main:63 -- ret = 4128
main:81 -- ret = 4128,75164,131b4
main:63 -- ret = 4128
main:81 -- ret = 4128,75165,131b5
main:63 -- ret = 4128
main:81 -- ret = 4128,75166,131b6
main:63 -- ret = 4128
main:81 -- ret = 4128,75167,131b7
main:63 -- ret = 4128
main:81 -- ret = 4128,75168,131b8
main:63 -- ret = 4128
main:81 -- ret = 4128,75169,131b9
main:81 -- ret = 4128,75170,131ba
main:81 -- ret = 4128,75171,131bb
main:63 -- ret = 4128
main:63 -- ret = 4128
main:81 -- ret = 4128,75172,131b

 所以,如果代码相对简单,设置为阻塞模式。就会使代码更简单。减少出错的机会。尽量不要为了使用IO多路复用而使用IO多路复用。

三使用命令测试命名管道:

 创建一个命名管道:

mkfifo hello
$ ls -lsh hello
0 prw-rw-r-- 1 lkmao lkmao 0 5月   8 13:53 hello

 向管道中写数据:

$ echo "hello world" > hello
$ cat hello
hello world

在命令行中测试时,执行echo "hello world" > hello后是会阻塞的,只有执行的cat hello以后echo "hello world" > hello的命令才会返回

四 编程测试命名管道

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

#include <fcntl.h>           /* Definition of AT_* constants */
#include <sys/stat.h>

int mkfifoat(int dirfd, const char *pathname, mode_t mode);

删除一个命名管道:

    #include <unistd.h>

    int unlink(const char *pathname);

 测试一:创建一个命名管道:子进程写,父进程读

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <linux/limits.h>
#include <fcntl.h>

#define DEBUG_INFO(format, ...) printf("%s:%d -- " format "\n", __func__, __LINE__,##__VA_ARGS__)
using namespace std;

#define FIFO_NAME   "myfifo"

void create_fifo(const char *file_name){
    int ret = unlink(file_name);
    if(ret == -1){
        perror("unlink");
    }

    ret = mkfifo(file_name,0666);
    if(ret < 0){
        perror("mkfifo");
        exit(1);
    }
    DEBUG_INFO("mkfifo ok ret = %d",ret);
    system("ls -lsh myfifo");
}

int main(int argc, char** argv){
    create_fifo(FIFO_NAME);
    pid_t pid = fork();
    if(pid == 0){
        int fd = open(FIFO_NAME,O_WRONLY);
        if(fd < 0){
            perror("open");
            exit(1);
        }
        write(fd, "hello world",sizeof("hello world"));
        close(fd);
        sleep(1);
    }
    if(pid > 0){
        char buf[100] = {0};
        int fd = open(FIFO_NAME,O_RDONLY);
        if(fd < 0){
            perror("open");
            exit(1);
        }
        int len = read(fd, buf,sizeof(buf));
        DEBUG_INFO("read len = %d,buf = %s",len,buf);
        close(fd);
    }
    if(pid == -1){
        perror("fork");
        exit(-1);
    }
    
    sleep(10);
    
    return 0;
}

测试结果:

create_fifo:28 -- mkfifo ok ret = 0
0 prw-rw-r-- 1 lkmao lkmao 0 5月   8 12:49 myfifo
main:53 -- read len = 12,buf = hello world

代码中设置的mode是0666,但是文件实际上是664,为什么呢,这个和系统本身的掩码mask有关:

执行umask:

umask
0002

所以设置的真正值是(mode & ~umask) = 0666 &~0002 = 0664

小结 

更多细节还需根据具体情况严谨测试,切记相当然的认为,它一定会使这样的,以事实为依据,以理论为准绳。

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

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

相关文章

Java线程池及其实现原理

线程池概述 线程池&#xff08;Thread Pool&#xff09;是一种基于池化思想管理线程的工具&#xff0c;经常出现在多线程服务器中&#xff0c;如MySQL。 线程过多会带来额外的开销&#xff0c;其中包括创建销毁线程的开销、调度线程的开销等等&#xff0c;同时也降低了计算机…

【设计模式】单例模式(懒汉和饿汉模式详解)

目录 1.设计模式是什么&#xff1f; 2.单例模式 1.概念&#xff1a; 2.如何设计一个单例 1.口头约定&#xff08;不靠谱&#xff09; 2.使用编程语言的特性来处理 3.使用"饿汉模式"设计单例 1.详细步骤 2.完整代码 4.使用"饿汉模式"设计单例 1.详…

为什么我在大厂待了三个月就选择离开?我聊聊应届生该选择大厂还是小公司

我在互联网大厂只待了3个月就离开了&#xff0c;主要原因不是大厂的福利或者薪资不够好&#xff0c;只是因为我发现在大厂里每天都有开不完的会&#xff0c;忙碌到没有自己的生活。当时我每天10点上班&#xff0c;晚上要工作到11甚至是12点&#xff0c;甚至半夜两三点都接到过工…

Flowable+React+bpmn-js实现工作流

由于新东家使用的是React&#xff0c;不是Vue&#xff0c;而自己一直想做一个关于工作流的应用出来&#xff0c;断断续续&#xff0c;花了几个月的时间&#xff0c;开发了工作流的功能&#xff0c;后面会继续完善。 技术栈 前端 前端是基于React开发的&#xff0c;使用了ant…

OpenCV 直方图统计函数 cv::calcHist算是彻底弄明白了

参数说明 void calcHist( const Mat* images, int nimages,const int* channels, InputArray mask,OutputArray hist, int dims, const int* histSize,const float** ranges, bool uniform true, bool accumulate false );images 图像数组。每个图像的大小要一致&#xff0c…

最强算法视频公开课!(内容硬核,完全免费!

和录友们汇报一下&#xff0c;代码随想录算法公开课已经更新完毕了。 由我亲自录制了140期算法视频&#xff0c;覆盖了 《代码随想录》纸质版上全部题目的讲解。 视频全部免费开放在B站&#xff1a;代码随想录 目录就在视频播放的右边&#xff0c;完全按照代码随想录的顺序讲…

鸿蒙Hi3861学习七-Huawei LiteOS-M(信号量)

一、简介 信号量&#xff08;Semaphore&#xff09;是一种实现任务间通信的机制&#xff0c;实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中&#xff0c;各任务之间需要同步或互斥实现临界资源的保护&#xff0c;信号量功…

面对AI“龙卷风”破坏力 白宫“软着陆”欧盟“硬防御”

ChatGPT的风靡与风险将OpenAI的CEO山姆奥特曼&#xff08;Sam Altman&#xff09;送进白宫&#xff0c;他被蹲守在美国总统府邸的记者们围追&#xff0c;面对5月4日白宫发起的AI风险治理会议&#xff0c;奥特曼很官方地给出“重要也很及时”的回应&#xff0c;自信的反复强调“…

chatGPT润色中英论文软件-文章修改润色器

chatGPT可以润色英文论文吗&#xff1f; ChatGPT可以润色英文论文&#xff0c;它具备自动纠错、自动完善语法和严格全面的语法、句法和内容结构检查等功能&#xff0c;可以对英文论文进行高质量的润色和优化。此外&#xff0c;ChatGPT还支持学术翻译润色、查重及语言改写等服务…

Java入门指南:从零开始的基础语法

java语言概述 Java是一种高级编程语言&#xff0c;最初由Sun Microsystems&#xff08;现在是Oracle Corporation的一部分&#xff09;在1995年推出。Java以其简单、可移植和安全的特性而闻名&#xff0c;并广泛用于各种应用程序开发&#xff0c;从桌面应用程序到移动应用程序和…

icevision环境安装

Installation - IceVision # 1. git clone 代码# pip 换源&#xff1a; ~/.pip/pip.conf 隐藏文件[global] index-url https://pypi.tuna.tsinghua.edu.cn/simple [install] trusted-hostmirrors.aliyun.compip install -e .[all,dev]ImportError: cannot import name Multi…

ASEMI代理ADUM131E1BRWZ-RL原装ADI车规级ADUM131E1BRWZ-RL

编辑&#xff1a;ll ASEMI代理ADUM131E1BRWZ-RL原装ADI车规级ADUM131E1BRWZ-RL 型号&#xff1a;ADUM131E1BRWZ-RL 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOIC-16-300mil 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;16 工作温度…

WPF异常处理详解

总目录 文章目录 总目录一、WPF异常1 未捕获异常2 模拟未捕获异常场景 二、处理未捕获异常1 DispatcherUnhandledException 异常捕获2 UnhandledException异常捕获3 UnobservedTaskException异常捕获4 异常捕获的综合使用 结语 一、WPF异常 1 未捕获异常 正常情况下&#xff…

又一里程碑,alibaba首推Java技术成长笔记,业内评级“钻石级”

前言 根据数据表明&#xff0c;阿里巴巴已经连续3年获评最受欢迎的中国互联网公司&#xff0c;实际上阿里巴巴无论在科技创新力还是社会创造价值这几个方面&#xff0c;都是具有一定代表里的。在行业内&#xff0c;很多互联网企业也将阿里作为自己的标杆&#xff0c;越来越多的…

【PSO-LSTM】基于PSO优化LSTM网络的电力负荷预测(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

iOS与Android应用开发的对比:如何选择最佳开发平台?

第一章&#xff1a;引言 在移动应用开发领域&#xff0c;iOS和Android是最为流行的操作系统。选择最佳的开发平台可以使开发人员更有效地开发和发布应用程序。本文将分析iOS和Android应用开发的优缺点&#xff0c;并提供一些有关如何选择最佳开发平台的建议。 第二章&#xf…

Kali-linux攻击WordPress和其他应用程序

今天越来越多的企业利用SAAS&#xff08;Software as a Service&#xff09;工具应用在他们的业务中。例如&#xff0c;他们经常使用WordPress作为他们网站的内容管理系统&#xff0c;或者在局域网中使用Drupal框架。从这些应用程序中找到漏洞&#xff0c;是非常有价值的。 为…

《算经》中的百钱买百鸡问题,你会做吗?试下看看(39)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 欢迎和猫妹一起&#xff0c;趣味学Python。 今日主题 你知道我国历史上有个王朝叫北魏吗&#xff1f; 北魏&#xff08;386年—534年&#xff09;&#xff0c;南北朝时期北…

AdaSparse: 自适应稀疏网络的多场景CTR预估建模

▐ 摘要 CTR(Click-through rate)预估一直是推荐/广告领域重要技术之一。近年来&#xff0c;通过统一模型来服务多个场景的预估建模已被证明是一种有效的手段。当前多场景预估技术面临的挑战主要来自两方面&#xff1a;1&#xff09;跨场景泛化能力&#xff1a;尤其对稀疏场景&…

vscode IDE 能用的上的扩展工具功能介绍

记录分享vscode扩展&#xff0c;包括提升开发效率。必备。主题美化。ChatGPT等。 参考 vscode-extensions [Best] 记录分享方式&#xff0c;整理自己用的扩展&#xff0c;还有一键备份和还原方法。 ⭐快速下载和使用扩展 后面会介绍很多vscode扩展.这裡有一个技巧&#xff0c;…