用lazarus开发Linux daemon程序及编写启动脚步

来源:互联网 发布:加内特生涯场均数据 编辑:程序博客网 时间:2024/05/16 13:41

用lazarus开发Linuxdaemon程序及编写启动脚步

 

Linux Daemon程序简介

Linux Daemon程序(守护进程或精灵进程)是生存期较长的一种进程。它们常常在系统自举时启动,仅当系统关闭时才终止。所以它们没有控制终端,在后台运行。

在Linux C环境下编写daemon程序,需要遵循一些基本规则,以便防止产生并不需要的交互作用。比如其中有两条重要的规则。

第一,要调用fork,然后父进程退出。这样做有两个目的:

(1)如果该daemon进程是作为一条shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕;

(2)子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这对于之后要调用的setsid是必要的前提条件。

第二,调用setsid会创建一个新会话,调用进程成为新会话的首进程,并成为一个新进程组的组长,最重要的该调用导致进程没有了控制终端。

但使用lazarus开发daemon服务程序,不用这么麻烦,只需要建立daemon工程,在其上编写代码即可。

Lazarus下开发daemon程序

安装lazarus

根据自己的系统类型,从 https://sourceforge.net/projects/lazarus/files/下载合适的rpm安装包到本地目录,包括如下三个文件:

fpc-2.6.2-0.laz.i686.rpm

fpc-src-2.6.2-0.laz.i686.rpm

lazarus-1.0.8-0.i686.rpm

命令行,进入下载目录,安装:

Rpm -ivh *.rpm

Lazarus在Linux系统默认安装在/usr/lib/lazarus目录中。

安装lazdaemon开发包

在lazarus ide的package->openpackage file(.lpk)菜单打开/usr/lib/lazarus/components/daemon/lazdaemon.lpk工程,如图:

图1 安装lazdaemon

依次compile,use->install。安装lazdaemon包,会重新编译lazaruside,并自动重启ide。

在lazarus ide的file菜单中,点击new…,弹出界面如下图,如果出现Daemon(service)applications项目,说明安装成功。

图2 新建daemonservice应用程序

Daemon例子

该例子以定期向一个注册服务器发送注册信息为例,注册服务器的代码这里没有给出,该例子主要是为了说明daemon程序如何编写。

在图2的截图中,新建Daemon(service) application项目。新建后,界面如下图。

新建工程后,默认有两个单元文件,分别是daemonMapperunit1.pas和daemonunit1.pas文件,当然还有其相应的窗体文件。

编写daemonunit1.pas单元文件内容如下:

unitDaemonUnit1;

 

{$mode objfpc}{$H+}

 

interface

 

uses

  Classes,SysUtils,FileUtil,DaemonApp,sockets,eventlog;

 

type

 

  { TRegThread }

  TRegThread=class(TThread)

  private

    FLog:TEventLog;

    procedureDoLog(Msg:String);

  public

    ConstructorCreate(ALog:TEventLog);

    ProcedureExecute;override;

  end;

 

  { TDaemon1 }

  TDaemon1=class(TDaemon)

    procedureDataModuleStart(Sender:TCustomDaemon;varOK:Boolean);

  private

    { private declarations }

    FLog:TEventLog;

    FThread:TRegThread;

    procedureStartLog;

  public

    { public declarations }

  end;

 

var

  Daemon1:TDaemon1;

 

implementation

 

procedureRegisterDaemon;

begin

  RegisterDaemonClass(TDaemon1)

end;

 

{$R *.lfm}

 

{ TRegThread }

procedureTRegThread.DoLog(Msg:String);

begin

  IfAssigned(FLog)then

    FLog.Info(Msg);

end;

 

constructorTRegThread.Create(ALog:TEventLog);

begin

  FLog:=Alog;

  InheritedCreate(False);

end;

 

procedureTRegThread.Execute;

var

  buf:string;

  SAddr:TInetSockAddr;

  s:Longint;

  sin,sout:text;

  inaddr:in_addr;

  i:integer;

const

  CRLF= #13#10;

begin

  whiletruedo

  begin

  dolog('heart frame start...');

  S:=fpSocket(AF_INET,SOCK_STREAM,0);

  ifs=-1then

  begin

    dolog('fpsocket error:'+inttostr(socketerror));

    exit;

  end;

  SAddr.sin_family:=AF_INET;

  SAddr.sin_port:=htons(10001);

  SAddr.sin_addr.s_addr:=hosttonet((10shl24)or(11shl16)or(32shl8)or(59));

  ifnotconnect(s,SAddr,Sin,Sout)then

  begin

    dolog('connect error:'+inttostr(socketerror));

    continue;

  end;

  reset(sin);

  rewrite(sout);

    begin

      buf:=Char($FF);

      buf:=buf+ 'cxqTest'+CRLF+ '10000'+CRLF;

      buf:=buf+ 'svn';

      buf:=buf+CRLF+ '1.0';

      writeln(sout,buf);

    end;

    dolog(buf);

    flush(sout);

    close(sout);

    closesocket(s);

    sleep(10000);

  end;

end;

 

{ TDaemon1 }

procedureTDaemon1.DataModuleStart(Sender:TCustomDaemon;varOK:Boolean);

begin

  sleep(10000);

  startlog;

  FThread:=TRegThread.Create(flog);

end;

 

procedureTDaemon1.StartLog;

begin

  FLog:=Self.Logger;

  //FLog.FileName:= 'cxqcxq.txt';

  //flog.LogType:=ltfile;

end;

 

initialization

  RegisterDaemon;

end.

在这个例子中,DaemonMapperunit1.pas单元不用写任何代码,但需要设置其窗体属性。点击daemonMapper1窗体,在左边的objectinspector窗口中显示该窗体的属性列表。

点击daemonDefs属性后面的省略号,弹出一个编辑窗口,新建一个条目,并设置属性如下,其中DaemonClassName属性要和daemonunit1.pas单元中的继承于TDaemon类的类名相同。

设置好,就可以编译该程序了。但是,在linux下编译多线程程序时,需要在工程单元文件中,注释掉一个条件宏,否则该程序不能正常运行。这应该是lazarus ide的一个bug。代码如下。

Programproject1;

 

Uses

{$IFDEF UNIX}//{$IFDEF UseCThreads}

  CThreads,

{$ENDIF}//{$ENDIF}

  DaemonApp,lazdaemonapp,DaemonMapperUnit1,DaemonUnit1

  { add your units here };

 

begin

  Application.Initialize;

  Application.Run;

end.   

这样,就可以编译并测试了。

手工运行daemon测试

在命令行下进入源程序目录,假设编译的程序名称是project1,执行project1 –r。如果想在后台执行,执行 project1 –r &。

要想在Linux开机时自动启动该程序,还需要配置启动脚本。由于不同的系统有不同的配置方法,我们先看看系统版本再配置启动脚本,下面以两个真实系统为例。

查看Linux版本命令

可用如下命令:

lsb_release cat /proc/version  cat/etc/issue  cat /etc/redhat-release

在fedora系统中执行各命令如下。

在redhat6.3系统中执行各命令如下。

启动脚本

在redhat Linux系统中,根据启动级别,执行不同集合的启动脚本。各级别的启动脚本分别放在rcN.d目录中,init进程会根据用户配置的启动等级,启动相应rcN.d目录下的脚本。在rcN.d目录中的脚本都是符号链接,真正的脚本放在/etc/rc.d/init.d/目录中。符号连接名称体现出是启动还是停止,以及优先级。所以,如果要想在开机启动自己的程序,需要在/etc/rc.d/init.d/目录中放启动脚本,并在各rcN.d目录中放符号连接。比如Linux代理程序SVNServerService程序,该程序放在/home/ftp/incoming/linux目录中,编写对应的控制脚本如下:

#!/bin/bash

#

# SVNServerAgent        Startup script for the SVNServerAgent Server

#

# chkconfig: 2345 85 15

# description: The SVNServerAgent Server is an efficient and extensible  \

#              server implementing the SVNServer cmd Agent.

# processname: SVNServerService

#

### BEGIN INIT INFO

# Provides: SVNServerAgent

# Required-Start: $local_fs $remote_fs $network $named

# Required-Stop: $local_fs $remote_fs $network

# Should-Start: distcache

# Short-Description: start and stop SVNServerAgent Server

# Description: The Apache HTTP Server is an extensible server

#  implementing the SVNServer cmd Agent.

### END INIT INFO

 

SVC_START_OPTIONS="-r"

SVC_STOP_OPTIONS="s"

 

# Edit SVC_ALIAS to the long description of your service application

SVC_ALIAS="SVNServerAgent server"

# Edit SVC_FILENAME to the actual name of your compiled service application

SVC_FILENAME="SVNServerService"

 

# Edit SVC_DIR to where you place your compiled service application

SVC_DIR="/home/ftp/incoming/linux/"

 

# Edit SVC_SERVICE_SCRIPT to the name of this file without the extension

SVC_SERVICE_SCRIPT="SVNServerAgent"

# this will become your service name.  Ie.) service YourService start

 

SVC_FILE=$SVC_DIR$SVC_FILENAME

start(){

        if [ -f $SVC_FILE ]; then

          #reset      

          echo -n"Starting "$SVC_ALIAS": "

          RETVALS=$(start-stop-daemon -S -b -x $SVC_FILE -- $SVC_START_OPTIONS)

 

          Count=${#RETVALS[@]}

          RETVAL="[FAIL]"

 

          if [ $Count -eq 0 ];then

            RETVAL="[OK]"

          elif [ $Count -eq 1 ];then

            if [ ${#RETVALS[0]}-eq0]; then

              RETVAL="[OK]"

            else

              iStart=${#SVC_FILE}

              iLength=${#RETVALS[0]}

              Response=${RETVALS[0]:(iStart+1):7}

              RETVAL=$Response

              if [ "$Response" =="already"];then

                RETVAL="[OK]"

              fi

            fi

          fi

          echo $RETVAL

          return0

        else

          echo $SVC_ALIAS" not installed" $SVC_DIR

          exit 2;

        fi

}

 

stop(){

        echo -n "Shutting down "$SVC_ALIAS":"

        RETVALS=$(start-stop-daemon -K -x $SVC_FILE -- $SVC_STOP_OPTIONS)

        #additional PROCKILLS=$(killall -w -q -e $SVC_PROCESS_NAME $SVC_FILENAME)

        Count=${#RETVALS[@]}

        Index=0

        RETVAL="[FAIL]"

        if [ $Count -eq 1 ];then

                if [ ${#RETVALS[0]}-eq0]; then

                        RETVAL="[OK]"

                else

                        Response=${RETVALS[0]:0:2}

                        RETVAL=$Response

                        if["$Response"=="No"];then

                                RETVAL="[OK]"

                        fi

                fi

        else

                RETVAL="[OK]"

        fi

 

        echo $RETVAL

        return 0

}

 

case "$1" in

    start)

        start

        ;;

    stop)

        stop

        ;;

    status)

        status $SVC_SERVICE_SCRIPT

        ;;

    restart)

        stop

        start

        ;;

    *)

        echo $SVC_ALIAS" [Invalid Startup Parameters]"       

        echo "Usage:  {start|stop|status|restart}"

        exit 1

        ;;

esac

exit $?

把该脚本放在/etc/rc.d/init.d/目录中,用chkconfig命令工具在各rcN.d目录中生成对应的启动和停止脚步符号连接。注意,用chkconfig工具管理该脚本,需要在脚本的头部按照指定格式编写注释。其中代码的第6行,分表表示启动的级别,启动优先级,停止优先级。

Chkconfig –add命令可以增加管理的脚本。Chkconfig根据脚本第6行注释在各rcN.d目录中生成对应的符号连接文件,详细见下图命令结果。

从上图中可以看到,chkconfig命令在rc2,rc3,rc4,rc5目录中生成了启动脚本,以S(start)开头,之后是启动等级,最后是脚本名称;除此,还在其他的rcN.d目录中生成了以K开头的符号连接,这表示kill。手工也可以完成该任务,但用chkconfig工具更方便。

在fedora中,用systemctl控制脚本的执行启动,该脚本是个ini文件。

[Unit]

Description=SVNServer

After=network.target

 

[Service]

Type=simple

ExecStart=/home/ftp/incoming/linux/SVNServerService -r

RemainAfterExit=yes

TimeoutSec=25

 

[Install]

WantedBy=multi-user.target

把该脚本保存为SVNServerAgent.service文件,并放到/lib/systemd/system目录里。用systemctl enable SVNServerAgent.service命令激活该脚步在启动时执行。

服务启停和状态

使用service脚本命令或systemctl控制服务启停和查看状态。Service最终调用的是服务程序本身+参数方式。

Service serviceName start

Service serviceName stop

Service serviceName status

以及

Systemctl status serverName

Systemctl enable serverName(使脚本在系统启动时执行)

Systemctl add serverName

查看状态:

Ps –axj(查看所有无终端的进程)

lsof -Pln +M -i4

 

0 0
原创粉丝点击