zoukankan      html  css  js  c++  java
  • linux下fifo,mq,shm

    最近想为系统添加一个统计脚本,但是系统内的模块是有perl和java两种语言编写,且模块是通过crontab定时调用的,所以需要使用IPC传输信息。

    第一个想到的是socket方式,感觉需要统一设定一个端口,不是特别保险,并且全都在一台服务器上,用socket有些浪费,所以打算尝试一下linux的IPC功能。

    1.FIFO. 接着想起FIFO,即有名管道。在linux中是一种特殊的文件,因为linux中所有事物都可以被视为文件,所以对FIFO的操作和对文件的操作方式是一样的。

    可以使用mkfifo命令创建一个有名管道:

    $ mkfifo fifo
    $ ll fifo
    prw-r--r-- 1 star star 0 Feb 14 15:42 fifo|

    属性‘p’表示这个文件是个管道,最后的‘|’也是相同的意义。

    先上perl代码:

    读管道:
    my $fifo = 'fifo';
    # 和普通文件一样打开
    open( my $fh, "<", $fifo) or die "open($fifo) failed: $!";
    seek($fh,0,0);
    while(1)
    {
      while(my $line = readline($fh))
      {
        print "$line";
      }
    }

    。。。

    写管道:
     my $fifo = 'fifo';
     open( my $fh, ">", $fifo) or die "open($fifo) failed: $!";print $fh "test fifo
    ";

    FIFO是一种先进先出的文件,没有seek等的操作,只能从开始读,写到末尾。与普通文件不同之处在于:

    a) 如果当前是为读打开FIFO,则需要有其他进程为写打开FIFO,否则会阻塞(没有设置非阻塞标志)。

    b) 如果是为写打开,则需要有其他进程为读打开。

    也就是说,默认情况下,读和写至少都要有一个才能正常打开FIFO,如果想要不阻塞在打开操作上,可以使用O_NONBLOCK标志。参考自:http://blog.csdn.net/xiaobai1593/article/details/7402205

    sysopen( my $fh, $fifo, O_WRONLY | O_NONBLOCK) or die "open($fifo) failed: $!";

    后来没有使用FIFO,原因有两个:

    a) java端虽然可以正常操作FIFO文件,但是java的打开文件操作没有O_NONBLOCK方式,如果另一方进程由于非正常原因退出了,那么java会一直阻塞在打开操作上。如果通过设置超时等方式处理,会比较繁琐。

    b) 多次写入后,一次读取有可能读取不完整。

    另外值得注意的是,对FIFO而言,当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。 PIPE_BUF这个值是可以设置的,所以如果一次写入的数据很小,可以不用担心写写冲突的问题。如果是读的话,应该是只有一个读者才好。

    2. shared memory

    共享内存在linuxIPC中是性能最高的,因为别的方式需要先把数据复制到内核区,再复制到用户区,而共享内存全部在内存,不经过内核区。

    并且,共享内存是可以设置IPC_PRIVATE方式的,提高独立性。

    但是它的操作方式和文件类似,这就会有内容覆盖和需要制定位置的问题。也就是说,每次写入新的内容,很容易出错就覆盖掉老的内容,读的时候还得把读过内容删除掉,非常麻烦。

    所以没有选择共享内存的方式。


    3. Message Queue

    最后比较下来,选用了消息队列的方式。

    消息队列的好处:

    1. 每个消息是相互独立的,数据是被包装成条消息,没有上面的内容覆盖问题。读取都是对消息的操作,比较方便。

    2. 操作系统层面上会保证每个消息的完整,不会有写写冲突的存在。

    消息队列需要注意的是:读写之间的配合,如果写入过多会导致消息队列到达容量上限,不能再写入;写入的数据不能过大,可以在 /etc/sysctl.conf中设置。http://www.cnblogs.com/samurail/archive/2012/11/14/2770379.html

    perl可以直接创建一个消息队列,这点是java做不到的,java只能打开一个已经存在的消息队列。

    所以如果想要perl和java之间通过消息队列交互,需要先用perl创建一个消息队列,再把这个消息队列的编号保存在一个文件中,java读取文件获得编号,打开mq进行操作。

    perl创建代码如下(使用的是SysV标准):

    use IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRUSR S_IWUSR IPC_NOWAIT);
    use IPC::Msg;
    
    $key = IPC::SysV::ftok($file,'a');
    $msg =new IPC::Msg($key,0666|IPC_CREAT) or logAbort("create message queue:$!");

    移除:

    $msg->remove();

    为了确保程序意外终止的情况下,不会有消息队列遗留,remove操作最好放在END{}代码区内。

    读取:

    # infinite loop to read messages from MQ
    while (1) {
      $msg->rcv($buf,1024,$msgtype);
      if ($!) {
        killThread($thr);
        logAbort("read message queue failed: $!");
      }
      $q->enqueue($buf);
    }

    $msg->rcv在没有IPC_NOWAIT时会阻塞至有数据来,所以会新建一个线程专门读取mq数据。

    写入:

    $msg->snd($msgtype, $buf, IPC_NOWAIT) or logWarning("writing message to mq failed. $!");

    其中读和写的$msgtype需要一致才行,最简单的就是都设为1。这里设置了IPC_NOWAIT,因为写操作不能阻塞,会影响正常程序运行。

    http://perldoc.perl.org/perlipc.html#SysV-IPC 

    java要操作消息队列有点繁琐,因为java本意是为了跨平台运行,而消息队列是操作系统相关的,所以如果想要操作mq,需要使用JNI。

    需要使用的包是lajp-10.05,这是专门开发用来结合java和php优点的包,但是在目前,我们只需要使用其中的IPC部分。

    另外,java的项目管理通常都是使用maven,而lajp并不在maven的几个主流仓库中,所以,要想在maven中纳入lajp包,需要做一些额外的工作,

    比较方便的是使用maven把lajp包放入本地的maven仓库,这样可以随时使用:

    mvn install:install-file -Dfile=./lajp-10.05/lajp-10.05.jar -DgroupId=lajp -DartifactId=lajp -Dversion=10.5 -Dpackaging=jar -DgeneratePom=true

    成功之后,在pom里就可以和往常一样把lajp写入<dependency></dependency>中。在java代码中还得加入:

    static {
            //JNI
            System.loadLibrary("lajpmsgq");
            });
        }

    java操作mq:

    import lajp.MsgQ;
    
    Integer key;
    // 打开
    msqid = MsgQ.msgget(key);
    //
    // 1:和perl中$msgType一致 MsgQ.msgsnd(msqid, 1, str.getBytes(), str.getBytes().length);

    这里的key即时前文中perl写在文件中的key。

    4. 命令行查看和删除IPC

    查看命令:

    ipcs

    删除命令:

    ipcrm

    可以指定删除某一个IPC对象

    usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
              [-Q msgkey] [-M shmkey] [-S semkey] ... ]

    ps:perl中,在使用了chdir之后,再使用abs_path,结果和预料的有时候会有偏差。。。

    在操作IPC对象时,需要考虑如果进程突然终止或退出了的情况,这时候往往会在IPC对象中留下遗留数据(比如在统计时间时,需要写入结束标志才能统计一个任务的时间,而进程突然中止后,mq中收不到结束标志了,另一个统计进程会认为这个任务一直在运行)。这时候需要使用一些预防措施,如在java中使用

    Runtime.getRuntime().addShutdownHook()

    函数,在退出时,会由jvm执行,补上结束标志。

  • 相关阅读:
    条码的开发使用介绍文档
    identity server4
    IUrlHelper ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
    netcore 版本 切换 sdk
    深层目录文件复制,C# 递归,录音录像图片文件过多,用于测试程序
    C# int uint long ulong byte sbyte float double decimal 范围,及类型!
    sqlserver 数据类型 C# clr 数据类型 映射
    Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activate 'xxxxx.Controllers.xxxxController'.
    identity 基础表没有创建 aspnetuserclaims aspnetuserlogins
    DatabaseGeneratedOption
  • 原文地址:https://www.cnblogs.com/starRebel/p/IPC.html
Copyright © 2011-2022 走看看