博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux 应用程序开发入门
阅读量:6203 次
发布时间:2019-06-21

本文共 16648 字,大约阅读时间需要 55 分钟。

Linux 应用程序开发入门

Neo Chen (netkiller)

版权 © 2011, 2012 http://netkiller.github.com

摘要

我会实现一个守护进程,从这个程序你将了解,Linux 应用程序开发基本流程

我们将实现一个远程shell的功能,可以通过tcp协议,运行远程机器上的命令或shell脚本

通过这个命令可以实现批量操作,管理上千台服务器。需要发挥你的想象力,灵活使用它。

写这个脚本,我是为了替代SSH远程操作,因为SSH不能控制运行命令,操作风险大,也不安全。

程序还不完善,还需要很多后续改进工作,比如通过SSL建立Socket链接,用户认证,ACL访问控制等等.

下面是我多年积累下来的经验总结,整理成文档供大家参考:

 

   

 


目录

1. 环境

OS: Ubuntu 10.10

: 3.2.2

程序目录: /srv/nodekeeper

目录与相关文件

$ cd /srv$ find nodekeeper | grep -v .svnnodekeepernodekeeper/nodekeeper.ubuntunodekeeper/nodekeeper.cenosnodekeeper/etcnodekeeper/etc/commands.cfgnodekeeper/etc/protocol.cfgnodekeeper/binnodekeeper/bin/nodekeepernodekeeper/bin/console

2. nodekeeper 主程序

$ cat nodekeeper/bin/nodekeeper#!/usr/bin/env python3#/bin/env python3#-*- coding: utf-8 -*-############################################### Home  : http://netkiller.sf.net# Author: Neo 
##############################################import asyncore, asynchat, socket, threadingimport subprocess, os, sys, getopt, configparser, loggingimport string, refrom multiprocessing import Processclass Backend(asyncore.dispatcher): queue = [] def __init__(self, host, port,config): asyncore.dispatcher.__init__(self) self.host = host self.port = port self.config = config self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind((host,port)) self.listen(10) try: cfg = Protocol(config['protocol'], self.host) #self.protocols = cfg.items(self.host) self.protocols = cfg.all() self.sections = cfg.sections() except configparser.NoSectionError as err: print("Error: %s %s" %(err, config['protocol'])) sys.exit(2) try: logging.basicConfig(level=logging.NOTSET, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filename=config['logfile'], filemode='a') self.logging = logging.getLogger() #self.logging.debug('Test') except AttributeError as err: print("Error: %s %s" %(err, config['logfile'])) sys.exit(2) def handle_accept (self): conn, addr = self.accept() self.queue.append(addr) request_handler(conn, self) def handle_connect(self): pass def handle_expt(self): self.close() def handle_close(self): self.close()class request_handler(asynchat.async_chat): def __init__(self, sock, resource): asynchat.async_chat.__init__(self, sock=sock) self.sessions = resource self.buffer = b'' self.set_terminator(b"\r\n") self.logging = resource.logging self.protocols = resource.protocols self.sections = resource.sections self.host = self.sessions.host def handle_connect(self): # connection succeeded #self.logging.info('') pass def handle_expt(self): # connection failed self.close() def collect_incoming_data(self, data): """Buffer the data""" #self.buffer.append(data) self.buffer = data def found_terminator(self): try: buffer = bytes.decode(self.buffer) except UnicodeDecodeError: print("\r\nError: ",err) buffer = '' try: execute = re.split(' ', buffer) command = execute[0] parameter = ' '.join( execute[1:]) response = b'' screen = '' if self.buffer == b'quit' or self.buffer == b'exit' : self.push(b'shutdown!!!\r\n') self.close_when_done() elif self.buffer == b'help' or self.buffer == b'?': screen = "Help may be requested at any point in a command by entering a question mark '?' or 'help'. the help list will be showing the available options.\r\n" for cmd,v in self.protocols : screen += cmd + "\r\n" elif self.buffer == b'sections' : for sect in self.sections : screen += sect + "\r\n" elif self.buffer == b'help.html' : for cmd,v in self.protocols : screen += '
'+ cmd +'
' + "\r\n" elif self.buffer == b'enable': self.prompt = b'#' elif self.buffer == b'end' or self.buffer == b'^z': self.prompt = b'>' else: proto = dict(self.protocols) if command in proto : run = proto[command] + ' ' + parameter screen = subprocess.getoutput(run) if screen : response = bytes(screen + "\r\n",'utf8') self.push(response) self.logging.info(bytes.decode(self.buffer)) self.buffer = b'' self.close_when_done() except : self.close_when_done() sys.exit(2)class Protocol(): config = None agreement = None def __init__(self,cfg = 'protocol.cfg',sections = ''): self.config = configparser.SafeConfigParser() self.config.read(cfg) #self.agreement = self.config.items('common') def sections(self): return self.config.sections() def items(self, sections): self.agreement = self.config.items(sections) return self.agreement def dicts(self): return dict(self.agreement) def all(self): self.agreement = [] for section in self.config.sections(): self.agreement += self.config.items(section) return self.agreementdef main(): daemon = False host = 'localhost' port = 7800 pidfile = '' logfile = '' cfgfile = '' try: opts, args = getopt.getopt(sys.argv[1:], "h:p:d?v", [ "daemon","host=","port=", 'help',"h=","p=", "basedir=", "pidfile=", "config=", "protocol=", "logfile="]) if not opts : usage() sys.exit() for o, a in opts : if o in ('-?', '--help') : usage() sys.exit() elif o in ("-v", "--verbose"): usage() sys.exit() elif o in ("-d", "--daemon"): daemon = True elif o in ("-h", "--host"): host = a elif o in ("-p", "--port"): port = int(a) elif o in ("--basedir"): BASEDIR = a elif o in ("--pidfile"): pidfile = a elif o in ("--config"): cfgfile = a elif o in ("--protocol"): protocol = a elif o in ("--logfile"): logfile = a else: assert False, "unhandled option" except getopt.GetoptError as err: # print help information and exit: usage() sys.exit(2) try: if daemon : pid = os.fork() if pid > 0: #exit first parent sys.exit(0) myself = str(sys.argv[0].split('/')[-1:][0]) #pidfile = os.getpid() if not pidfile : pidfile = '/var/run/'+myself+'.pid' file = open(pidfile,'w') file.write(str(os.getpid())) file.close() if not cfgfile : cfgfile = ''+myself+'.cfg' if not logfile : logfile = '/var/log/'+myself+'.log' config = dict({'cfgfile':cfgfile, 'pidfile':pidfile, 'logfile':logfile, 'protocol':protocol}) Backend(host,port,config) asyncore.loop(timeout=30, use_poll=True) except socket.error as err: print("\r\nError: ",err) sys.exit(2) except IOError as err: print("\r\nError: ",err) sys.exit(2)def usage(): myself = str(sys.argv[0].split('/')[-1:][0]) print("Usage: %s -d -h
-p <7800>" % myself ); print("Development and deployment administration platform") print("\r\nMandatory arguments to long options are mandatory for short options too.") print("\t-?, --help") print("\t-v, --verbose") print("\t-d, --daemon") print("\t-h, --host \t\t(default localhost)") print("\t-p, --port") print("\t --config \t\t(default %s.cfg)" % myself) print("\t --protocol \t\t(default %s.cfg)" % "protocol.cfg") print("\t --pidfile \t\t(default /var/run/%s.pid)" % myself) print("\t --logfile \t\t(default /var/log/%s.log)" % myself) print("\r\nExample:") print("\t%s --daemon --host localhost --port 7800" % myself) print("\t%s -d -h localhost -p 7800" % myself) print("\r\nSee http://netkiller.sf.net/ for updates, bug reports, and answers, \r\nif you have no web access, by sending email to Neo Chan
. ") # Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.if __name__ == '__main__': try: main() except KeyboardInterrupt: print ("Crtl+C Pressed. Shutting down.")

2.1. 帮助信息

usage()

Usage: nodekeeper -d -h 
-p <7800>Development and deployment administration platformMandatory arguments to long options are mandatory for short options too. -?, --help -v, --verbose -d, --daemon -h, --host (default localhost) -p, --port --config (default nodekeeper.cfg) --protocol (default protocol.cfg.cfg) --pidfile (default /var/run/nodekeeper.pid) --logfile (default /var/log/nodekeeper.log)Example: nodekeeper --daemon --host localhost --port 7800 nodekeeper -d -h localhost -p 7800See http://netkiller.sf.net/ for updates, bug reports, and answers, if you have no web access, by sending email to Neo Chan
.

2.2. 参数处理

getopt.getopt 实现Unix风格的命令参数,例如:

nodekeeper --daemon --host localhost --port 7800--host localhost --port 7800 IP地址与端口参数--daemon 参数实现后台运行

具体实现代码

try:        opts, args = getopt.getopt(sys.argv[1:], "h:p:d?v", [ "daemon","host=","port=", 'help',"h=","p=", "basedir=", "pidfile=", "config=", "protocol=", "logfile="])        if not opts :            usage()            sys.exit()        for o, a in opts :            if o in ('-?', '--help') :                usage()                sys.exit()            elif o in ("-v", "--verbose"):                usage()                sys.exit()            elif o in ("-d", "--daemon"):                daemon = True            elif o in ("-h", "--host"):                host = a            elif o in ("-p", "--port"):                port = int(a)            elif o in ("--basedir"):                BASEDIR = a            elif o in ("--pidfile"):                pidfile = a            elif o in ("--config"):                cfgfile = a            elif o in ("--protocol"):                protocol = a		            elif o in ("--logfile"):                logfile = a            else:                assert False, "unhandled option"    except getopt.GetoptError as err:        # print help information and exit:        usage()        sys.exit(2)

2.3. 后台运行

--daemon 参数实现后台运行,原理是首先通过os.fork()克隆一个进程,然后退出当前进程,克隆的新进程继续运行

如果是Shell程序,你可使用“&”符号后台运行,但作为一个应用程序,使用“&”显得不专业。

具体实现的代码如下

if daemon :            pid = os.fork()            if pid > 0:                #exit first parent                sys.exit(0)

程序一旦进入后台,当前进程即将关闭,所以你必须保存PID,为后面的推出程序操作使用,这里我们可以通过 --pidfile 指定一个pid文件

2.4. 日志记录

程序一旦进入后台,你只能通过ps,pstree, top 等命令查看状态,运行情况必须通过日志的形式,打印出来

具体实现代码如下:

logging.basicConfig(level=logging.NOTSET,                    format='%(asctime)s %(levelname)-8s %(message)s',                    datefmt='%Y-%m-%d %H:%M:%S',                    filename=config['logfile'],                    filemode='a')            self.logging = logging.getLogger()            self.logging.debug('Test')

2.5. 多线程

继承 asynchat.async_chat 实现多线程

class request_handler(asynchat.async_chat):    def __init__(self, sock, resource):        asynchat.async_chat.__init__(self, sock=sock)

连接数限制

self.listen(10)

可以将这个参数提出来,然后通过命令行设置。

nodekeeper --daemon --maxconn 100 --host localhost --port 7800			self.max_connect = maxconnself.listen(self.max_connect)

3. 配置文件

$ cat nodekeeper/etc/protocol.cfg [system]ls = lsos.hosts = cat /etc/hostsos.issue = cat /etc/issueos.memory = freeos.who = whoos.harddisk = df -hos.uptime = uptimeos.cpuinfo = cat /proc/cpuinfoos.meminfo = cat /proc/meminfoos.dmesg = dmesgos.process = ps auxos.summary = echonetwork.status = netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'network.netstat = netstat -nlpnetwork.ifconfig = ifconfignetwork.route = ip route[apache]apache.start = /usr/local/apache/bin/apachectl startapache.stop = /usr/local/apache/bin/apachectl stopapache.restart = /usr/local/apache/bin/apachectl restartapache.status = ps ax |grep httpdapache.conf = cat /usr/local/apache/conf/httpd.confapache.conf.vhost = cat /usr/local/apache/conf/extra/httpd-vhosts.confapache.logs.now = apache.logs.tail = [resin]resin.start = /usr/local/resin/bin/httpd.sh startresin.stop = /usr/local/resin/bin/httpd.sh stopresin.restart = /usr/local/resin/bin/httpd.sh restartresin.status = /usr/local/resin/bin/httpd.sh statusresin.conf = cat /usr/local/resin/conf/resin.conf[www]www.list = ls -1 /wwwwww.permission = find /www -type d -exec chmod 755 {} \; find /www -type f -exec chmod 644 {} \; www.permission.777 = chmod 777 -R /www/*lamp.status = ps ax |grep -E "mysqld|httpd|resin"[samba]samba.start = /etc/init.d/smb startsamba.stop = /etc/init.d/smb stopsamba.restart = /etc/init.d/smb restartsamba.status = /etc/init.d/smb status[mysql]mysql.start = /etc/init.d/mysql startmysql.stop = /etc/init.d/mysql stopmysql.restart = /etc/init.d/mysql restart[memcache]memcache.start = /etc/init.d/memcache startmemcache.stop = /etc/init.d/memcache stopmemcache.restart = /etc/init.d/memcache restart[vsftpd]vsftpd.start = /etc/init.d/vsftpd startvsftpd.stop = /etc/init.d/vsftpd stopvsftpd.restart = /etc/init.d/vsftpd restartvsftpd.status = /etc/init.d/vsftpd status

4. init.d 脚本

Linux 所有守护进程都是用init.d下面的脚本来管理

当人你也可以直接运行命令:

nodekeeper --daemon --host localhost --port 7800

但这样只能算是一个半成品,也不够专业,我们写的是linux运用程序,必须遵循Linux规范,所有要实现一个init.d脚本

$ cat nodekeeper#! /bin/sh### BEGIN INIT INFO# Provides:          nodekeeper# Required-Start:    $local_fs $remote_fs $network $syslog# Required-Stop:     $local_fs $remote_fs $network $syslog# Default-Start:     2 3 4 5# Default-Stop:      0 1 6# Short-Description: starts the nodekeeper web server# Description:       starts nodekeeper using start-stop-daemon### END INIT INFOPATH=/srv/nodekeeper/bin:$PATHDAEMON=/srv/nodekeeper/bin/nodekeeperNAME=nodekeeperDESC=nodekeeperBASEDIR="/srv/nodekeeper"HOST=$(ifconfig  | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1}'|head -n 1)PORT=7800CONFIG=$BASEDIR/etc/$NAME.cfgLOGFILE=$BASEDIR/log/$NAME.logPIDFILE=$BASEDIR/run/$NAME.pidPIDFILE=/var/run/$NAME.pidPROTOCOL=$BASEDIR/etc/protocol.cfgDAEMON_OPTS="--daemon --host $HOST --port $PORT --config=$CONFIG --protocol=$PROTOCOL --pidfile=$PIDFILE --logfile=$LOGFILE"test -x $DAEMON || exit 0# Include nodekeeper defaults if availableif [ -f /etc/default/nodekeeper ] ; then	. /etc/default/nodekeeperfiset -e. /lib/lsb/init-functions#test_nodekeeper_config() {#  if $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1#  then#    return 0#  else#    $DAEMON -t $DAEMON_OPTS#    return $?#  fi#}case "$1" in  start)	echo -n "Starting $DESC: "	start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \		--exec $DAEMON -- $DAEMON_OPTS || true	echo "$NAME."	;;  stop)	echo -n "Stopping $DESC: "	start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \		--exec $DAEMON || true	echo "$NAME."	;;  restart|force-reload)	echo -n "Restarting $DESC: "	start-stop-daemon --stop --quiet --pidfile \		/var/run/$NAME.pid --exec $DAEMON || true	sleep 1	start-stop-daemon --start --quiet --pidfile \		/var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS || true	echo "$NAME."	;;  reload)        echo -n "Reloading $DESC configuration: "        start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \            --exec $DAEMON || true        echo "$NAME."        ;;  configtest)        echo -n "Testing $DESC configuration: "        if test_nodekeeper_config        then          echo "$NAME."        else          exit $?        fi        ;;  status)	status_of_proc -p /var/run/$NAME.pid "$DAEMON" nodekeeper && exit 0 || exit $?	;;  *)	echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}" >&2	exit 1	;;esacexit 0

我们将使用DAEMON_OPTS变量,提供所有需要的参数

DAEMON_OPTS="--daemon --host $HOST --port $PORT --config=$CONFIG --protocol=$PROTOCOL --pidfile=$PIDFILE --logfile=$LOGFILE"

4.1. start/stop

/etc/init.d/nodekeeper start/etc/init.d/nodekeeper stop

4.2. service start/stop

service nodekeeper startservice nodekeeper stop

转载地址:http://byica.baihongyu.com/

你可能感兴趣的文章
jmeter命令行运行-分布式测试
查看>>
day25 面向对象引子
查看>>
爬虫,基于request,bs4 的简单实例整合
查看>>
mysql的安装
查看>>
使用shell脚本生成数据库markdown文档
查看>>
PyQt5:布局
查看>>
指针常量,常量指针
查看>>
C#程序运行计时
查看>>
Android init.rc执行顺序
查看>>
如何通过 Vue+Webpack 来做通用的前端组件化架构设计
查看>>
通过javascript的日期对象来得到当前的日期
查看>>
重装系统
查看>>
VS2008切换时卡死解决方法
查看>>
Mysql之sql语句操作
查看>>
查看Sql Server 数据库的内存使用情况
查看>>
dedecms--二次开发之会员帐号过期无法登录
查看>>
批处理Ping服务器
查看>>
上传模板到SAP资源库
查看>>
loadrunner-2-9添加事务
查看>>
2014年百度之星程序设计大赛 - 资格赛 1004 -- Labyrinth
查看>>