linux环境编程(2): 使用pipe完成进程间通信

1. 写在前面

linux系统内核为上层应用程序提供了多种进程间通信(IPC)的手段,适用于不同的场景,有些解决进程间数据传递的问题,另一些则解决进程间的同步问题。对于同样一种IPC机制,又有不同的API供应用程序使用,目前有POSIX IPC以及System V IPC可以为应用程序提供服务。后续的系列文章将逐一介绍消息队列,共享内存,信号量,socket,fifo等进程间通信方法,本篇文章主要总结了管道相关系统调用的使用方式。文中代码可以在这个代码仓库中获取,代码中使用了我自己实现的一个单元测试框架,对测试框架感兴趣的同学可以参考上一篇文章。

2. pipe介绍

在linux环境进行日常开发时,管道是一种经常用到的进程间通信方法。在shell环境下,'|'就是连接两个进程的管道,它可以把一个进程的标准输出通过管道写到另一个进程的标准输入,利用管道以及重定向,各种命令行工具经过组合之后可以实现一个及其复杂的功能,这也是继承自UNIX的一种编程哲学。

除了在shell脚本中使用管道,另一种方式是通过系统调用去操作管道。使用pipe或者pipe2创建管道,得到两个文件描述符,分别是管道的读端和写端,有了文件描述符,进程就可以像读写普通文件一样对管道进行readwrite操作,操作完成之后调用close关闭管道的两个文件描述符即可。可以看到,当完成创建之后,管道的使用和普通文件相比没有什么区别。

管道有两个特点: 1) 通常只能在有亲缘关系的进程之间进行通信; 2) 阅后即焚;有亲缘关系是指,通信的两个进程可以是父子进程或者兄弟进程,这里的父子和兄弟是一个广义的概念, 子进程可以是父进程调用了多次fork创建出来的,而不仅局限在只经过一次fork,总之,只要通信双方的进程拿到了管道的文件描述符就可以使用管道了。说”阅后即焚“是因为管道中的数据在被进程读取之后就会被管道清除掉。有一个形象的比喻说,管道就像某个进程家族各个成员之间传递情报的中转站,情报内容阅后即焚。

3. pipe的基本使用

在使用管道时,需要注意管道中数据的流动方向,通常都是把管道作为一个单向的数据通道使用的。虽然通信双方可以都持有管道的读端和写端,然后使用同一个管道实现双向通信,但这种方式实际上很少使用。下面通过几段代码说明几种使用管道的方法:

3.1 自言自语

管道虽然时进程间通信的一种手段,但一个进程自言自语也是可以的,内核并没有限制管道的两端必须由不同的进程操作。下面的代码展示了一个孤独的进程怎样通过管道自言自语,代码中使用了自己实现的测试框架cutest。执行之后它将从管道的另一头收到前一个时刻发给自己的消息。

CUTEST_CASE(basic_pipe, talking_to_myself) {
    int pipefd[2];
    pipe(pipefd);

    const char *msg = "I'm talking to myself";
    write(pipefd[1], msg, strlen(msg));

    char buf[32];
    read(pipefd[0], buf, 32);
    printf("talking_to_myself: %s\n", buf);

    close(pipefd[0]);
    close(pipefd[1]);
}

3.2 父进程向子进程传递数据

自言自语始终是太过无聊,是时候让父子进程之间聊点什么了。因为fork之后的子进程会继承父进程的文件描述符,fork之前父进程向管道写入的数据,子进程可以在管道的另一端读到。

CUTEST_CASE(basic_pipe, parent2child) {
    int pipefd[2];
    pipe(pipefd);

    const char *msg = "parent write, child read";
    write(pipefd[1], msg, strlen(msg));

    if (fork() == 0) {
        close(pipefd[1]);
        char buf[64];
        memset(buf, 0, 64);
        read(pipefd[0], buf, 64);
        printf("parent2child: %s\n", buf);
        exit(0);
    }

    close(pipefd[0]);
    close(pipefd[1]);
}

3.2 自进程向父进程传递数据

管道的方向是由通信双方操作的文件描述符决定的,子进程同样可以传递消息给父进程。

CUTEST_CASE(basic_pipe, child2parent) {
    int pipefd[2];
    pipe(pipefd);

    if (fork() == 0) {
        close(pipefd[0]);
        const char *msg = "parent read, child write";
        write(pipefd[1], msg, strlen(msg));
        close(pipefd[1]);
        exit(0);
    }

    close(pipefd[1]);

    char buf[64];
    memset(buf, 0, 64);
    read(pipefd[0], buf, 64);
    printf("child2parent: %s\n", buf);
    close(pipefd[0]);
}

3.3 父进程向多个子进程传递数据

当有多个子进程时,只要它们持有了管道的文件描述符,就可以利用管道通信,把父进程写进管道的数据读取出来。当然,在具体的应用中需要考虑子进程的读取顺序等因素,下面的例子只是简单的创建了多个子进程,每个进程读取一个int类型的数据,开始阶段由父进程向管道写入数据,需要说明一点,三个子进程并没有将管道内的数据都读完,当所有引用了这个管道的文件描述符都关闭了之后,内核也会在适当的时机销毁自己维护的管道。


void fork_child_read(int id, int pipefd[2], const char *msg_pregix) {
    if (fork() == 0) {
        close(pipefd[1]);
        int n;
        read(pipefd[0], &n, sizeof(int));
        printf("%s: child %d get data %d\n", msg_pregix, id, n);
        close(pipefd[0]);
        exit(0);
    }
}

CUTEST_CASE(basic_pipe, parent2children) {
    int pipefd[2];
    pipe(pipefd);

    for (int i = 1; i <= 10; i++)
        write(pipefd[1], &i, sizeof(int));

    const char *msg_prefix = "parent2children:";
    fork_child_read(1, pipefd, msg_prefix);
    fork_child_read(2, pipefd, msg_prefix);
    fork_child_read(3, pipefd, msg_prefix);

    close(pipefd[0]);
    close(pipefd[1]);
}

3.4 父进程接收多个子进程的数据

考虑这样一种场景,一个任务需要由多个子进程进行处理,最终的计算结果需要由父进程汇总,下面的代码模拟了这样的场景,代码中创建了两个子进程向管道写入数据,父进程则一直尝试读取管道内的数据。

void fork_child_write(int pipefd[2], int data) {
    if (fork() == 0) {
        close(pipefd[0]);
        write(pipefd[1], &data, sizeof(int));
        close(pipefd[1]);
        exit(0);
    }
}

CUTEST_CASE(basic_pipe, children2parent) {
    int pipefd[2];
    pipe(pipefd);

    int data[] = {512, 1024};

    fork_child_write(pipefd, data[0]);
    fork_child_write(pipefd, data[1]);

    close(pipefd[1]);
    int n;
    while (read(pipefd[0], &n, sizeof(int)) == sizeof(int)) {
        printf("children2parent: get data %d\n", n);
    }
    close(pipefd[0]);
}

3.5 兄弟进程之间传递数据

如果有两个兄弟进程,进程A需要得到进程B的计算结果之后才能完成自己的任务,这时也可以用管道通信。代码中分别创建了两个进程对管道进行写和读操作,实际应用中经常还需要一种通知机制,让等待的进程知道它依赖的任务已经就绪了,这需要用到信号量,后续文章会介绍。下面代码的第二个进程在read操作时是阻塞的,会一直等到管道中数据可读,因为创建管道时没有指定O_NONBLOCK标志。

CUTEST_CASE(basic_pipe, two_children) {
    int pipefd[2];
    pipe(pipefd);

    const char *msg = "pipe between two children";
    if (fork() == 0) {
        close(pipefd[0]);
        write(pipefd[1], msg, strlen(msg));
        close(pipefd[1]);
        exit(0);
    }

    if (fork() == 0) {
        close(pipefd[1]);
        char buf[64];
        memset(buf, 0, 64);
        read(pipefd[0], buf, 64);
        printf("two_children: %s\n", buf);
        close(pipefd[0]);
        exit(0);
    }

    close(pipefd[0]);
    close(pipefd[1]);
}

3.6 阻塞和非阻塞的问题

前面的例子中提到了管道的阻塞和非阻塞,这里详细说明一下这个问题。对于一个阻塞的管道,如果进程在read时,系统中存在没有关闭的写端文件描述符,但此时管道是空的,read操作就会阻塞在这里。可以这样理解,因为写端的存在,read就固执地认为在未来的某个时刻一定会有人会向管道中写入数据,所以它就阻塞在这里。对于非阻塞的管道,在前面的条件下,read会立即返回。上述的特性就要求我们在使用阻塞类型的管道时要及时关闭不使用的文件描述符,因为进程read操作时在等待的写端文件描述符很可能是由当前进程打开的,当系统中管道的其他写端都关闭了的时候,当前进程的read就会出现自己等自己的问题,类似死锁。

CUTEST_CASE(basic_pipe, blocking_read) {
    int pipefd[2];
    pipe(pipefd);

    if (fork() == 0) {
        /* NOTE: remove the comment below if you don't want child process
         * blocking while reading data from pipe. Otherwise you will see that
         * there is still a "basic-pipe" process after you finish this test, and
         * you have to kill it manually.*/
        // close(pipefd[1]);

        int num;
        read(pipefd[0], &num, sizeof(int));

        /* NOTE: since the write end of pipe is a valid file descriptor in
         * current process, the print below should never execute.*/
        printf("should NEVER goes here\n");
        exit(0);
    }

    close(pipefd[0]);
    close(pipefd[1]);
    printf("blocking_read: parent process exit\n");
}

上述代码使用的是阻塞类型的管道,fork出的进程没有关闭管道的写端,然后执行了read操作,当父进程退出之后,系统中仍存在这个管道的写端描述符,并且就在已经处于睡眠状态下的子进程中,这种情况下将不会再有人向管道中写入数据,子进程会一直睡眠。运行代码之后使用ps命令可以看到这个睡死过去的子进程。

3.7 测试执行结果

以下是上述测试的执行结果,可以看到在程序退出之后仍然由一个"basic-pipe"进程,这是因为3.6节中的代码在子进程中没有及时关闭不使用的管道文件描述符。此时不得不手动把睡死的进程kill掉了。

[junan@arch1 test-all]$ make install
[junan@arch1 test-all]$ ./script/run_test.sh basic-pipe
blocking_read: parent process exit
two_children: pipe between two children
children2parent: get data 512
children2parent: get data 1024
parent2children:: child 1 get data 1
parent2children:: child 2 get data 2
parent2children:: child 3 get data 3
child2parent: parent read, child write
talking_to_myself: I'm talking to myself
cutest summary:
        [basic_pipe] suit result: 7/7
        [basic_pipe::blocking_read] case result: Pass
        [basic_pipe::two_children] case result: Pass
        [basic_pipe::children2parent] case result: Pass
        [basic_pipe::parent2children] case result: Pass
        [basic_pipe::child2parent] case result: Pass
        [basic_pipe::parent2child] case result: Pass
        [basic_pipe::talking_to_myself] case result: Pass
parent2child: parent write, child read
[junan@arch1 test-all]$ ps -e|grep basic-pipe
  18866 pts/2    00:00:00 basic-pipe
[junan@arch1 test-all]$ kill -9 18866
[junan@arch1 test-all]$ ps -e|grep basic-pipe
[junan@arch1 test-all]$ 

4. pipe的进阶使用

以上的几段示例代码说明了管道的一些基本使用方法和注意事项,下面看一个使用管道和多进程生成质数的问题。我们的需求是这样的,给定一个整数nmax,生成[2, nmax]区间上的所有质数,并且要求生成质数的核心逻辑使用管道和多进程。第一次碰到这个问题是在xv6操作系统的lab中,也是为了说明pipefork的使用。

看到这里,不妨先稍微思考一下?一个简单的想法可能是这样的,首先有一个函数,其功能是判断输入的n是否是质数,接下来遍历[2, nmax]上的整数,并且用之前的函数把质数都过滤出来,但问题是如何用管道和多进程实现这个函数的过滤功能呢?OK, 思考结束,来看看管道加多进程版本的质数生成器算法思路:

prime-number-pipe

这个“质数筛子”中的每个进程主要有三个任务,1)从pipe1读取第一个数据并打印出来,并且它一定是质数;2)用得到的质数过滤pipe1中的其他数据,并把过滤出来的数据写入pipe2;3)fork自己的子进程,并把pipe2传递给它;具体的代码实现如下,当过滤之后没有数据时,就不会继续创建子进程了。

void generate_primes(int pipe1[2]) {
    close(pipe1[1]);

    int prime = 0;
    int err = read(pipe1[0], &prime, sizeof(int));
    if (err <= 0) {
        close(pipe1[0]);
        return;
    }
    printf("%d\n", prime);

    int pipe2[2];
    pipe(pipe2);

    pid_t pid = fork();
    if (pid == 0) {
        generate_primes(pipe2);
    } else {
        int num = 0;
        while ((err = read(pipe1[0], &num, sizeof(int))) > 0) {
            if (num % prime) {
                write(pipe2[1], &num, sizeof(int));
            }
        }
    }

    close(pipe1[0]);
    close(pipe2[0]);
    close(pipe2[1]);
    exit(0);
}

CUTEST_SUIT(prime_numbers_pipe)

CUTEST_CASE(prime_numbers_pipe, prime_number_max30) {
    int nmax = 30;

    int pipe1[2];
    pipe(pipe1);

    for (int i = 2; i <= nmax; ++i)
        write(pipe1[1], &i, sizeof(int));

    if (fork() == 0) {
        generate_primes(pipe1);
    }

    close(pipe1[0]);
    close(pipe1[1]);
}

代码中生成的是2到30区间上的质数,执行结果如下:

[junan@arch1 test-all]$ ./script/run_test.sh prime-number-pipe
cutest summary:
        [prime_numbers_pipe] suit result: 1/1
        [prime_numbers_pipe::prime_number_max30] case result: Pass
2
3
5
7
11
13
17
19
23
29
[junan@arch1 test-all]$ 

5. 写在最后

管道是一种比较基础和常用的进程间通信方法,在使用过程中需要注意及时关闭不再使用的文件描述符的问题,否则可能使得进程一直睡眠。文中的代码示例可以在我的代码仓库中找到,有兴趣的可以自己clone下来实际跑跑看。后续会继续更新其他的IPC相关的文章,并在最后使用各种IPC方法实现一个小项目,有想法的欢迎在评论区冒泡。

6. 相关链接

  • http://man7.org/linux/man-pages/man2/pipe.2.html
  • http://man7.org/linux/man-pages/man7/pipe.7.html
  • http://github.com/kfggww/test-all
  • http://github.com/kfggww/cutest

本文来自博客园,作者:kfggww,转载请注明原文链接:http://www.cnblogs.com/kfggww/p/17066291.html

本文转载于网络 如有侵权请联系删除

相关文章

  • 保姆级教程:写出自己的移动应用和小程序(篇三)

    在本系列的上一篇文章,我们学习了如何编写一个iOS与Android的第三方SDK,但在实际的研发工作中,纯靠自己手写SDK毕竟是少数情况,在常见的大多数时候,还是需要引入第三方SDK。今天我们重点学习如何在Android与iOS应用中引入我们在上一篇文章中写好的SDK。在Android环境下开发SDK 与iOS工程不一样,在Android环境中引入第三方SDK,我们会通过Gradle来进行管理。Gradle是Android构建的主要组成部分,依赖也是由Gradle管理,可以点击这里查看其官方文档。与iOS应用不一样,通过Gradle引入的SDK既可以存在于本地系统中,也可以存在于远程的代码库之中。不论存在于哪里,SDK所声明的所有传递依赖都会被自动集成在项目之中。AndroidStudio导入外部SDK的三种方式在AndroidStudio中,一共有以下3种方式导入外部SDK,让我们一起看看。LibraryDependency:需联网下载;Jar/aarDependency:添加本地jar包,添加前要先将jar包放到项目的libs目录下;ModuleDependency:添加本地带有源

  • scala:把函数作为值或参数进行传递、作为返回值进行返回,以及什么是闭包和柯里化

    函数可以作为值进行传递语法varf=函数名_如果明确了变量的数据类型,那么下划线可以省略//函数正常的声明与调用 deffoo():Int={ println("foo...") 10 } //将foo函数的执行结果赋值给res变量 //valres:Int=foo() //println(res) //函数作为值进行传递语法:在函数名称的后面+空格加下划线 //注意:将foo函数作为一个整体,赋值给f变量,f是函数类型()=>Int //valf:()=>Int=foo_ deff:()=>Int=foo_ //这个时候,f就是一个函数,如果要想运行f函数的话,必须得加() //println(f)//<function0> //println("-----------------") //println(f()) //varff=foo//将函数执行结果赋值给ff //varff=foo_//将函数本身作为值赋给ff //将函数本身作为值赋给ff如果明确了变量的类型,那么空格和下划线可以省略 //var

  • 国美上升,苏宁迷茫

    今年3月,国美在京东开了家旗舰店。时隔不到3个月时间,随着一纸公告,京东又成为了国美的战略投资人,前者购买了国美的可转债,同时还达成了深度的战略合作。这也意味着国美在零售行业的地位进一步得到提升和巩固。根据双方达成的协议,国美会将“家·生活”供应链、中大件物流仓储配送、服务体系、全国2600多家门店接入京东平台。国美将为京东提供家电与“家·生活”整体解决方案等优势性商品,扩充京东平台家电商品品类,而京东将为国美提供非家电商品,扩充国美商品SKU,加速推进全品类进程。而在本次合作最值得玩味的就是双方在联合采购方面的合作。一个是线上最大的消费类电子平台,一个是线下家电零售行业的头部企业,双方联手形成的中国最大家电采购规模,已经重新划定了中国家电零售的势力范围。拼多多和京东为何战投国美在京东之前,拼多多在4月即购买了国美的可转债,并达成深度战略合作。两件事情一起看,其重要的意义在于,原来电商平台所言必打败的传统线下零售商不仅难以打败,甚至还存在着不可或缺的重要价值。拼多多需要国美强大的家电品类供应链及对应的服务能力,能够让拼多多在短期通过平台补贴的方式,快速实现高客单价的家电品类的销售增长,

  • 通过BGP EV**方式动态建立VXLAN隧道实现

    组网需求 如下图的组网图所示,Router1为企业分支网关,Router2为企业总部网关,由于分支与总部之间用户的业务需求不同,则将其规划为不同网段。企业分支的PC_1与总部的PC_2终端用户所属VLANID分别为VLAN10、VLAN20。现企业希望通过分支与总部之间通过BGPEV**方式动态建立VXLAN隧道实现用户间互通 通过VXLAN三层网关通信组网图 配置思路 采用如下思路配置不同网段用户通过BGPEV**方式动态建立VXLAN隧道实现互通: 分别在Router1、Router2、Router3上配置路由协议,保证网络三层互通。分别在Router1、Router2上配置VXLAN接入业务选择部署方式。配置BGPEV**对等体关系。在Router1和Router2上配置源端VTEP的IP地址。在Router1和Router2上配置V**实例。在Router1和Router2上配置三层网关。在Router1与Router2之间配置BGP对邻居发布IP前缀类型的路由。操作步骤 配置路由协议。配置Router1。Router2和Router3的配置与Router1类似,这里不再赘述。

  • 跟我学Spring Cloud(Finchley版)-22-Spring Cloud Config-配置动态刷新

    先解释下为什么突然断更半个月: 正月初三-正月十二:父亲肺气肿住院;母亲肺炎,也要挂水,故请假照顾。正月十四-正月二十:奶奶摔了一跤,突然离世…老家有守夜、办丧的习俗,请假事丧。总之,2019开局很不顺利……Anyway,今天开工,今天恢复更新。配置刷新三要素1依赖中有spring-boot-starter-actuator2添加如下配置,暴露/actuator/refresh端点:management:endpoints:web:exposure:include:refresh复制3待刷新的配置属性所在的类上添加了@RefreshScope注解,例如:@RestController@RefreshScopepublicclassConfigClientController{@Value("${profile}")privateStringprofile; @GetMapping("/profile")publicStringhello(){returnthis.profile;}}复制这样,修改profile配置后,只需向应用的/actuato

  • SQL学习笔记三(补充-1)之MySQL存储引擎

    阅读目录一什么是存储引擎二mysql支持的存储引擎三使用存储引擎一什么是存储引擎mysql中建立的库===>文件夹库中建立的表===>文件现实生活中我们用来存储数据的文件有不同的类型,每种文件类型对应各自不同的处理机制:比如处理文本用txt类型,处理表格用excel,处理图片用png等数据库中的表也应该有不同的类型,表的类型不同,会对应mysql不同的存取机制,表类型又称为存储引擎。存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方 法。因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和 操作此表的类型)在Oracle和SQLServer等数据库中只有一种存储引擎,所有数据存储管理机制都是一样的。而MySql 数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据 自己的需要编写自己的存储引擎SQL解析器、SQL优化器、缓冲池、存储引擎等组件在每个数据库中都存在,但不是每个数据库都有这么多存储引擎。MySQL的插件式存储引擎可以让存储引擎层的开发人员设计他们希望的

  • 2款AI芯片、深度学习框架MindSpore:华为史无前例集中发布AI战略

    机器之心报道作者:李泽南今天上午,在上海举行的2018全联接大会上,华为轮值董事长徐直军宣布了华为的AI战略与全栈式解决方案,同时发布了两款全新AI芯片以及跨平台深度学习框架。可以说,这是近年来BAT等巨头高调投入AI之际,华为高层首次对外宣布集团层面的AI战略。华为本次发布的AI全栈式解决方案,让这家公司成为目前全球唯一提供AI全栈软件和系列化芯片的提供商。同时,华为还提供了一套与之配套的统一开发框架。华为AI发展战略AI是基础生产力,这个观点是华为通过自身的实践总结出的经验,现在华为希望把提升自身生产力的技术开放出来,供所有人使用。「自2017年起,华为就确定了构建万物互联的愿景,」华为轮值董事长徐直军表示。「为此,华为制定了AI发展战略。如同工业化革命期间的电力和铁路一样,人工智能是21世纪的新通用目的技术。」徐直军表示,华为之所以强调AI是一种通用技术,是为了让我们正视AI的价值,AI不仅能够让我们高效解决已经解决的问题,还可以解决很多未解决的问题。华为认为,人工智能带来的改变将涉及所有行业,包括交通、教育、医疗和金融。而人工智能引发的变革才刚刚开始,目前我们正处在AI应用与社

  • 小程序已成为旅游行业的新利器,你还在观望么?

    暑期旅游的高分期已经到来,和以往不同的是,通过小程序购买机票、旅游产品的用户越来越多,自去年微信推出小程序以来,短短一年时间,小程序的使用用户数已达1.7亿,上线小程序数量高达58万。各行各业在小程序的应用创新上花样叠出。就旅游业来看,携程、途牛、蚂蜂窝等业界大佬早已第一时间推出专属小程序抢占市场,可以说旅游企业要想占领最新流量红利,就必须利用好小程序。旅游企业为什么要开发小程序呢?首先,就是用户使用便捷,旅游并不是一个高频次消费产品,以往基本上是需要先下载APP,但是对于用户来说,下载一个APP,需要时间,占用内存,可能用完了还要花时间卸载,一年用不到一两次。而微信小程序则直接通过微信就可以打开,效率高,随用随走。其次就是推广的成本,“携程的用户出行场景都在线下,比如火车站、汽车站、景点和酒店,因此小程序的入口和使用场景与携程产品非常契合。”据携程的开发人员介绍,小程序面世后,携程第一时间通过在诸如高铁站、汽车站、酒店等场所摆放易拉宝等方式,做扫码推广。此外,携程还推出了携程攻略小程序,并且做了一个用人工智能写诗的小程序,试图吸引用户使用。艺龙原本在微信钱包中就拥有入口,白志伟透露艺

  • POJ 2311 Cutting Game(二维SG+Multi-Nim)

    DescriptionUrejlovestoplayvarioustypesofdullgames.Heusuallyasksotherpeopletoplaywithhim.Hesaysthatplayingthosegamescanshowhisextraordinarywit.RecentlyUrejtakesagreatinterestinanewgame,andErifNezorfbecomesthevictim.Togetawayfromsufferingplayingsuchadullgame,ErifNezorfrequestsyourhelp.ThegameusesarectangularpaperthatconsistsofW*Hgrids.Twoplayerscutthepaperintotwopiecesofrectangularsectionsinturn.Ineachturntheplayercancuteitherhorizontallyorvertically,keepingeverygridsunbroken.AfterNturnsthepaperwi

  • 一步步教你弹性框架-下篇

    HTML5学堂:本文继续为大家讲解弹性框架,在前两篇文章当中,我们从最基本的来回运动,讲解到缓冲运动、有摩擦力的运动。基本实现了弹性动画效果。今天我们主要来进行函数的封装与优化。相关阅读:一步步教你弹性框架-中篇一步步教你弹性框架-上篇第六步运动功能函数封装首先在一个元素点击时,应当执行一个功能函数,这个功能函数我们将其独立出来,作为一个全局的函数而存在,从而实现多次调用。之后我们为了便于控制,需要“变量换常量”、“使用参数控制传入”。在整个功能当中,要发生位置变化的元素是不确定的;每次的终点值以及起点值也是不确定的。对于到底要通过哪种属性让元素变化(之前我们采用的是margin-left,如果使用定位也是可以采用left的)。因此,我们至少需要采用3个参数来辅助我们效果的完成。完成至第五步的代码varbtn=document.getElementById("btn");//获取控制按钮 varmove=document.getElementById("move");//获取运动块 vartimer=null;//初始化一个计时器 varspee

  • 实战:使用 OpenCV 的自动驾驶汽车车道检测(附代码)

    一、边缘检测 六、主要代码一旦我们准备好了单独的函数,我们只需要在我们的主代码中调用它们,你就会在你的图像中检测到车道。image=cv2.imread("3.jpg")#LoadImage edged_image=canyEdgeDetector(image)#Step1 roi_image=getROI(edged_image)#Step2 lines=getLines(roi_image)#Step3 smooth_lines=getSmoothLines(image,lines)#Step5 image_with_smooth_lines=displayLines(image,smooth_lines)#Step4 cv2.imshow("Output",image_with_smooth_lines) cv2.waitKey(0)复制复制输出将如下所示:Github代码链接: https://github.com/pdhruv93/computer-vision/tree/main/lane-detection-self-drivin

  • Keras中的RNN模型

    博客作者:凌逆战 博客地址:https://www.cnblogs.com/LXP-Never/p/10940123.html 这篇文章主要介绍使用Keras框架来实现RNN家族模型,TensorFlow实现RNN的代码可以参考我的另外一篇博客:TensorFlow中实现RNN,彻底弄懂time_step Keras实现RNN模型 SimpleRNN层 keras.layers.GRU(units,activation='tanh',recurrent_activation='hard_sigmoid',use_bias=True, kernel_initializer='glorot_uniform',recurrent_initializer='orthogonal',bias_initializer='zeros', kernel_regularizer=None,recurrent_regularizer=None,bias_regularizer=None,activity_regularizer=None, kernel_constraint=None,recurren

  • WebRTC与音频音量

    WebRTC打开麦克风,获取音频,在网页上显示音量。 播放示例音频 先从播放音频入手。准备一个现成的音频文件。 界面上放一个audio元素,提前准备好一个音频文件,路径填入src <audioid="sample-audio"src="God_knows_01.mp3"controlsautoplay></audio> 复制 audio有默认的样式。打开网页就可以利用这个来播放示例音频了。 WebRTC打开麦克风 准备 html页面上放audio,meter,button等等 <audioid="play-audio"controlsautoplay></audio> <divid="meters"> <divid="instant"> <divclass="label">实时:</div> <meterhigh="0.25"max="1"value="0"></meter> <divclass="value"></div> </div

  • python读取中文

    如何从文件中读取300个汉字? 看起来很简单,但很容易掉坑里了。   一开始我这么写: 1try: 2fd=codecs.open(os.path.join(settings.TEXT_CONTENT_DIR,channel_name.lower(),article_id),encoding='utf-8') 3#fd=open(os.path.join(settings.TEXT_CONTENT_DIR,channel_name.lower(),article_id)) 4text=fd.read(300) 5fd.close() 6exceptException,e: 7print"content.load()Error:",e复制   但是文件中如果是中英文夹杂怎么办? 因为utf8编码是变长的,所以很有可能会读出半个汉字。   解决办法: 1.写文件时指定utf8编码: 1importcodecs 2 3fd=codecs.open(conf.data_directory+os.sep+conf.text_directory+os.sep+c

  • 第二章 flex输入输出

    flex程序默认总是从标准输入读取,实际上,词法分析程序都从文件读取输入 flex总是通过名为yyin的文件句柄读取输入,下面的例子,我们修改单词计数程序,使得它能从文件读取输入 /*evenmorelikeUnixwc*/ %optionnoyywrap %{ intchars=0; intwords=0; intlines=0; %} %% [a-zA-Z]+{words++;chars+=strlen(yytext);} \n{chars++;lines++;} .{chars++;} %% main(argc,argv) intargc; char**argv; {   if(argc>1){     if(!(yyin=fopen(argv[1],"r"))){       perror(argv[1]);       return(1);     }   }   yylex();   printf("%8d%8d%8d\n",lines,words,chars); } 复制  程序中,如果在命令行中提供的文件参数,那么程序会打开它,修改yyin &nbs

  • Juniper初始化配置

    转载:https://www.jianshu.com/p/406e5a5c03fa?appinstall=0 感谢关注

  • [蓝桥杯][2013年第四届真题]剪格子

      问题1432:[蓝桥杯][2013年第四届真题]剪格子 时间限制:1Sec内存限制:128MB提交:781解决:272 题目描述 历届试题 剪格子   时间限制:1.0s   内存限制:256.0MB       问题描述  如下图所示,3 x 3 的格子中填写了一些整数。  +--*--+--+  |10* 1|52|  +--****--+  |20|30* 1|  *******--+  | 1| 2| 3|  +--+--+--+   我们沿着图中的星号线剪开,得到两个部分,每个部分的数字和都是60。  本题的要求就是请你编程判定:对给定的m x n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。 

  • 作业3:迷人的黛丝

    日落西山红霞飞,战士打靶把营归是《打靶归来》中的一句歌词,歌词中有一句是:misoramiso,rasomidore,写成简谱是:35635,65312。 ”我的成绩比你好,先后起来你悲催。“表示将两数相减,大数减小数,得到密钥29677。 “黛丝”表示用DES解码,得到明文xxaqdl(p25-12-bstring)U2FsdGVkX199XCqgBzG3McJ1NuWuuPclqBS9lekxPpE= 前面的字符串xxaqdl为信息安全导论的缩写,(p25-12-bstring)应是指书上页码。 查询书籍可得第25页,第12行字符串为01000011. 由”再来一次子弹飞“,推测需要再进行一次解码。而xxaqdl(p25-12-bstring)后的即是新密文。 将后面未解密的字符根据第一次解密得到的密钥01000011继续解密,最终得到明文Youareamazing  

  • DoBox 下载

    DoBox下载   一款简单十分好用的办公助手,用于记录您接下来需要做的事情。待办事项小工具-DoBox DoBox下载 下载地址:http://www.wxzzz.com/?id=141 最新版本:关注www.wxzzz.com   简单介绍 非常感谢您下载DoBox,作为您日常使用的待办事项工具,本工具是开源的,由WPF编写QQ群号:115797237,欢迎加入讨论,优化本工具。 这是Dobox发布的第一版有很多处理不周到之处,后续会补上,如:DoBox缺少时间提醒,后续会补上这个。 欢迎大家下载试用  

  • action方法不返回

    当被请求的action方法中还有资源没有释放时,请求方法是不会返回的,会一直停留在方法中,即使是最后一行,因为请求方法一旦返回,那方法中的资源,引用就没有位置住了,所以所请求的方法会一直不返回,直到方法中的所有资源,引用可以销毁时,这样会导致客户端一直得不到响应。所有在方法中一定要记得释放资源。

  • ios动力特效,最重要的一点 属性保持(写了动力特效但是没效果的原因就在这里)

    @property(nonatomic,strong)UIDynamicItemBehavior*square1PropertiesBehavior; @property(nonatomic,strong)UIDynamicItemBehavior*square2PropertiesBehavior; @property(nonatomic,strong)UIDynamicAnimator*animator; @property(nonatomic,strong)UIDynamicAnimator*dy; @end   @implementationViewController   -(void)viewDidLoad{   [superviewDidLoad];   UIView*apple=[[UIViewalloc]initWithFrame:CGRectMake(40,40,40,40)];     apple.backgroundColor=[UIColorredColor]; &nbs

相关推荐

推荐阅读