免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1196 | 回复: 0
打印 上一主题 下一主题

UNIX/IPC管道和FIFO [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-02-06 11:06 |只看该作者 |倒序浏览
    管道是最初的UNIX IPC形式,由于管道没有名字,所以只能在用于有亲缘关系的进程(所谓的亲缘关系是指进程间有共同的祖先)。FIFO则被称为命名管道。
    先说明fork,exec,_exit函数对管道及fifo函数的影响:
fork:子进程取得父进程的管道以及fifo描述字的拷贝。
exec:所有代开的描述字依旧打开,除非已经设置描述字的FD_CLOEXEC位。
_exit:关闭所有打开的描述字,最后一个描述字关闭时删除管道与FIFO中的数据。
    管道和FIFO涉及的函数有:pipe, mkfifo, fcntl, open, read, write, close, unlink.
2、管道函数及说明:int pipe(int fd[2]);
(1)、这个函数提供一个单向管道。fd是输出参数,fd[0]用来读出,fd[1]用来写入(这里的读出和写入是的宾语是管道)。函数成功返回0,错误返回-1.

意:a、linux的system
call(相当于系统API)函数的返回值是成功返回0,返回其他的就表示出错,而windows的api函数则刚好相反,错误返回0,成功返回其他。
b、这里不写函数的头文件,要知道它用到那些头文件,在类unix下用man来查看即可,如:man 2
pipe,2表示pipe是一个系统调用,如果是c函数库里面的函数则按3。
(2)、管道的典型用途如下(用来作为父子进程消息的共享):
a、创建管道1(int fd1[2]) 和管道2(int fd2[2]);
b、fork创建子进程
c、父进程关闭管道1的读出端和2的写入端(fd1[0],fd2[1])
d、子进程关闭管道2的读出端和1的写入端(fd1[1],fd2[0])
代码如下:
  • int pipe1[2];int pipe2[2];
    pid_t childPid;

  • pipe(pipe1);
  • pipe(pipe2);

  • if( ( childPid = fork()) == 0 ) /* child process */{
  •     close(pipe1[1]);    close(pipe2[0]);
  •     ...   /* your code */}

  • /* parent process */
  • close(pipe1[0]);close(pipe2[1]);
  • ...waitpid(childPid, NULL, 0);

  • ...子
    进程在这里用来专门做一件事情,像服务器。而父进程就相当于客户端。如是,子进程在完成任务之后就exit(0),那么但子进程终止时候变成僵尸进程
    (zombie),内核给父进程发送一个信号SIGCHILD,但是父进程没有扑抓,缺省行为是忽略;父进程后面调用到waitpid的时候就可以等待
    childPid进程的终止,取得终止状态。如果没有waitpid而直接返回,则父进程返回后那个僵尸子进程将成为托孤给init进程的孤儿进程。内核
    将为init进程发送SIGCHILD信号。
    这样子,就变成: 客户-->管道fd1-->服务器;服务器-->fd2-->客户
    3、fifo 有名管道(fifo的意思是:先进先出)
    也是一个半双工的单向数据流,但是有一个路径名与之相连。函数如下:
    int mkfifo(const char *pathname, mode_t mode);
    说明:
    a、mkfifo函数已经隐含制订了O_CREAT | O_EXCL也就是说如果pathname那个管道已经存在,则返回一个错误值EEXIST,如果想打开一个已经存在的管道,用open()即可。
    b、由于fifo是先进先出的,所以write函数总是往管道末尾写入数据,而read函数总是从管道的开头返回数据,如果用lseek定位文件指针,会返回ESPIPE错误。
    应用:
  • /* server.c */
  • ...
  • if( ( mkfifo(FIFO1, FILE_MODE )     /* error handle and exit(1) */

  • if( ( mkfifo(FIFO2, FILE_MODE )
  • {    unlink(FIFO1);
  •     /* error handle and exit(1) */}

  • readfd = open(FIFO1, O_RDONLY, 0);
  • writefd = open(FIFO2, O_WRONLY, 0);
  • /* your code */
  • exit(0);这是服务器进程的FIFO,其中FIFO1,FIFO2可以定义一个宏来指定,比如,#define FIFO1 "/temp/fifo.1"(路径名随意,不过由于是临时的文件,一般放在/temp目录下,权限比较低嘛)
    打开文件之后就可以用read,write函数往管道读写信息了,FIFO1,FIFO2就是像文件描述符。
    再看client的代码:
  • /* client.h */
  • writefd = open(FIFO1, O_WRONLY, 0);readfd = open(FIFO2, O_RDONLY, 0);

  • /* your code */

  • close(writefd);
  • close(readfd);

  • unlink(FIFO1);unlink(FIFO2);

  • exit(0);说明:a、通常管道是由服务器建立,由客户端销毁(unlink函数),close只是关闭管道而已。b、注意clinet.h上的那两个open和server.h上的那两个open的顺序,否则会引起死锁(见后面)
    4、阻塞态下的规则(阻塞态是默认的状态):
    (1)、管道和FIFOopen函数的返回结果:
    writefd
    = open(FIFO1, O_WRONLY, 0);
    这个是用只写方式打开管道,如果FIFO1此时已经有别的进程以只读方式打开(就是说在这条代码运行之前,已经有代码open(FIFO1,
    O_RDONLY, 0)运行),则此函数返回成功,否则,将会阻塞到有别的进程以只读方式打开FIFO1为止。反过来也一样。
    (2)、read函数作用于管道和FIFO 的返回结果:
    如果FIFO1为空(就是说里面没有数据):该管道以只读方式打开,则返回0;FIFO1以只写方式打开,则阻塞到FIFO1有数据或者是FIFO1不再以写方式打开为止。
    (3)、write函数作用于管道和FIFO的返回结果:
    如果FIFO1没有以只读方式打开,则给进程产生SIGPIPE信号(缺省行为是终止该进程);如果已经以只读方式打开,则见下。
    (4)、write操作的原子性:
    如果写入数据的字节数小于等于PIPE_BUF,则该函数保证其原子性;否则不能保证。
    现在来看一下上面那个程序,服务器代码先运行,因此:当起运行到readfd = open(FIFO1, O_RDONLY, 0);的时候,还没有任何进程以O_WRONLY方式打开FIFO1,进程阻塞在这里;
    然后客户端代码开始运行,但运行到这个地方的时候:writefd = open(FIFO1, O_WRONLY, 0);服务器阻塞的地方开始释放,而在客户端,因为FIFO1在服务器已经是以O_RDONLY打开了,所以继续运行。
    如果客户端的这两个open交换一个顺序,那么readfd = open(FIFO2, O_RDONLY, 0);先运行,则由于FIFO2还没有以O_WRONLY方式打开,所以客户端也阻塞,客户和服务器都阻塞,大家都在等对方的资源,这种情况我们称之为死锁(deadlock)
    5、非阻塞态下的规则:
    (1)、非阻塞态的设置。
    a、调用open时可以指定 readfd = open(FIFO1, O_RDONLY | O_NONBLOCK );
    b、如果readfd已经打开,则可以用fcntl来设置O_NONBLOCK标志。
    (2)、对open操作的影响:
    如果当前操作是wrfd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0); 那么如果是FIFO1在此以前没有用O_RDONLY方式打开过,返回ENOXIO错误,否则都成功返回。
    (3)、对空管道或空FIFO read操作的影响:read(readfd, ...); 如果该readfd对应的FIFO用O_WRONLY打开返回0,否则返回EAGAIN。
    (4)、对write操作的影响同阻塞态下。
    6、技巧:单个服务器多个客户时候,服务器中有连续的两行代码:
    readfifo = open(SERV_FIFO, O_RDONLY, 0);
    dummy = open(SERV_FIFO, O_WRONLY, 0);
    作用:a、服务器运行到readfifo,阻塞,直到有客户用O_WRONLY打开SERV_FIFO为止。然后因为SERV_FIFO已经以O_RDONLY打开,因此这个dummy成功返回。
    b、到客户完成任务,关闭SERV_FIFO时,SERV_FIFO变成空,因此,服务器在运行到read语句的时候,阻塞知道下一个客户以O_WRONLY方式打开SERV_FIFO为止(也就是直到有下一个用户请求为止)
    7、Dos攻击(拒绝服务型攻击)
    有见上面的阻塞,有个攻击方法就是说,发送一条请求,但是从来不打开自己的FiFO,让服务器死等。服务器一直阻塞,没办法用了。
    当然,现在的服务器都是并发性服务器,最多只能阻塞他的一个子进程;但是即使在这种情况下一样可以进行Dos攻击,方法是发送大量的请求,以至于服务器开辟的进程达到极限,而且每个子进程都用上面的办法来阻塞。(此时不但服务器进程被阻塞了,整个服务器系统都被阻塞了)。
    8、字节流
    管道和FIFO是以字节流的方式来传递信息的,类似于TCP。那么怎样区分信息的界限呢?有下面三种常用的技巧:
    a、带内特殊终止符:分隔标志(类似与SLIP的标记)
    b、显示长度在信息头:像TCP协议栈实现。
    c、每次连接一个记录:像HTTP1.0协议实现。
                   
                   
                   

    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/88572/showart_1815264.html
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP