Linux 应用程序开发入门
Linux 應用程序開發(fā)入門
Neo Chen?(netkiller)
<openunix@163.com>
版權 ? 2011, 2012 http://netkiller.github.com
摘要
我會實現(xiàn)一個守護進程,從這個程序你將了解,Linux 應用程序開發(fā)基本流程
我們將實現(xiàn)一個遠程shell的功能,可以通過tcp協(xié)議,運行遠程機器上的命令或shell腳本
通過這個命令可以實現(xiàn)批量操作,管理上千臺服務器。需要發(fā)揮你的想象力,靈活使用它。
寫這個腳本,我是為了替代SSH遠程操作,因為SSH不能控制運行命令,操作風險大,也不安全。
程序還不完善,還需要很多后續(xù)改進工作,比如通過SSL建立Socket鏈接,用戶認證,ACL訪問控制等等.
下面是我多年積累下來的經(jīng)驗總結,整理成文檔供大家參考:
?
| Netkiller Architect 手札 | Netkiller Linux 手札 | Netkiller Developer 手札 | Netkiller Database 手札 |
| Netkiller Debian 手札 | Netkiller CentOS 手札 | Netkiller FreeBSD 手札 | Netkiller Shell 手札 |
| Netkiller Web 手札 | Netkiller Monitoring 手札 | Netkiller Storage 手札 | Netkiller Mail 手札 |
| Netkiller Security 手札 | Netkiller PostgreSQL 手札 | Netkiller MySQL 手札 | Netkiller LDAP 手札 |
| Netkiller Cryptography 手札 | Netkiller Intranet 手札 | Netkiller Cisco IOS 手札 | Netkiller Writer 手札 |
| Netkiller Version 手札 | Netkiller Studio Linux 手札 | ? | ? |
?
目錄
1. 環(huán)境2. nodekeeper 主程序2.1. 幫助信息2.2. 參數(shù)處理2.3. 后臺運行2.4. 日志記錄2.5. 多線程3. 配置文件4. init.d 腳本4.1. start/stop4.2. service start/stop1.?環(huán)境
OS: Ubuntu 10.10
Python: 3.2.2
程序目錄: /srv/nodekeeper
目錄與相關文件
$ cd /srv $ find nodekeeper | grep -v .svn nodekeeper nodekeeper/nodekeeper.ubuntu nodekeeper/nodekeeper.cenos nodekeeper/etc nodekeeper/etc/commands.cfg nodekeeper/etc/protocol.cfg nodekeeper/bin nodekeeper/bin/nodekeeper nodekeeper/bin/console2.?nodekeeper 主程序
$ cat nodekeeper/bin/nodekeeper #!/usr/bin/env python3 #/bin/env python3 #-*- coding: utf-8 -*- ############################################## # Home : http://netkiller.sf.net # Author: Neo <openunix@163.com> ##############################################import asyncore, asynchat, socket, threading import subprocess, os, sys, getopt, configparser, logging import string, re from multiprocessing import Processclass Backend(asyncore.dispatcher):queue = []def __init__(self, host, port,config):asyncore.dispatcher.__init__(self)self.host = hostself.port = portself.config = configself.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):passdef 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 = resourceself.buffer = b''self.set_terminator(b"\r\n")self.logging = resource.loggingself.protocols = resource.protocolsself.sections = resource.sectionsself.host = self.sessions.hostdef handle_connect(self):# connection succeeded#self.logging.info('')passdef handle_expt(self):# connection failedself.close()def collect_incoming_data(self, data):"""Buffer the data"""#self.buffer.append(data)self.buffer = datadef 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 += '<a href="?host='+self.host+'&cmd='+cmd+'">'+ cmd +'</a><br />' + "\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] + ' ' + parameterscreen = 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 = Noneagreement = Nonedef __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.agreementdef 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 = Falsehost = 'localhost'port = 7800pidfile = ''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 = Trueelif o in ("-h", "--host"):host = aelif o in ("-p", "--port"):port = int(a)elif o in ("--basedir"):BASEDIR = aelif o in ("--pidfile"):pidfile = aelif o in ("--config"):cfgfile = aelif o in ("--protocol"):protocol = a elif o in ("--logfile"):logfile = aelse: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 parentsys.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 <ip address> -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<openunix@163.com>. ")# 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 <ip address> -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 7800nodekeeper -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<openunix@163.com>.2.2.?參數(shù)處理
getopt.getopt 實現(xiàn)Unix風格的命令參數(shù),例如:
nodekeeper --daemon --host localhost --port 7800--host localhost --port 7800 IP地址與端口參數(shù) --daemon 參數(shù)實現(xiàn)后臺運行具體實現(xiàn)代碼
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 = Trueelif o in ("-h", "--host"):host = aelif o in ("-p", "--port"):port = int(a)elif o in ("--basedir"):BASEDIR = aelif o in ("--pidfile"):pidfile = aelif o in ("--config"):cfgfile = aelif o in ("--protocol"):protocol = a elif o in ("--logfile"):logfile = aelse:assert False, "unhandled option"except getopt.GetoptError as err:# print help information and exit:usage()sys.exit(2)2.3.?后臺運行
--daemon 參數(shù)實現(xiàn)后臺運行,原理是首先通過os.fork()克隆一個進程,然后退出當前進程,克隆的新進程繼續(xù)運行
如果是Shell程序,你可使用“&”符號后臺運行,但作為一個應用程序,使用“&”顯得不專業(yè)。
具體實現(xiàn)的代碼如下
if daemon :pid = os.fork()if pid > 0:#exit first parentsys.exit(0)程序一旦進入后臺,當前進程即將關閉,所以你必須保存PID,為后面的推出程序操作使用,這里我們可以通過 --pidfile 指定一個pid文件
2.4.?日志記錄
程序一旦進入后臺,你只能通過ps,pstree, top 等命令查看狀態(tài),運行情況必須通過日志的形式,打印出來
具體實現(xiàn)代碼如下:
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 實現(xiàn)多線程
class request_handler(asynchat.async_chat):def __init__(self, sock, resource):asynchat.async_chat.__init__(self, sock=sock)連接數(shù)限制
self.listen(10)可以將這個參數(shù)提出來,然后通過命令行設置。
nodekeeper --daemon --maxconn 100 --host localhost --port 7800self.max_connect = maxconnself.listen(self.max_connect)3.?配置文件
$ cat nodekeeper/etc/protocol.cfg [system] ls = ls os.hosts = cat /etc/hosts os.issue = cat /etc/issue os.memory = free os.who = who os.harddisk = df -h os.uptime = uptime os.cpuinfo = cat /proc/cpuinfo os.meminfo = cat /proc/meminfo os.dmesg = dmesg os.process = ps aux os.summary = echo network.status = netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' network.netstat = netstat -nlp network.ifconfig = ifconfig network.route = ip route[apache] apache.start = /usr/local/apache/bin/apachectl start apache.stop = /usr/local/apache/bin/apachectl stop apache.restart = /usr/local/apache/bin/apachectl restart apache.status = ps ax |grep httpd apache.conf = cat /usr/local/apache/conf/httpd.conf apache.conf.vhost = cat /usr/local/apache/conf/extra/httpd-vhosts.conf apache.logs.now = apache.logs.tail = [resin] resin.start = /usr/local/resin/bin/httpd.sh start resin.stop = /usr/local/resin/bin/httpd.sh stop resin.restart = /usr/local/resin/bin/httpd.sh restart resin.status = /usr/local/resin/bin/httpd.sh status resin.conf = cat /usr/local/resin/conf/resin.conf[www] www.list = ls -1 /www www.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 start samba.stop = /etc/init.d/smb stop samba.restart = /etc/init.d/smb restart samba.status = /etc/init.d/smb status[mysql] mysql.start = /etc/init.d/mysql start mysql.stop = /etc/init.d/mysql stop mysql.restart = /etc/init.d/mysql restart[memcache] memcache.start = /etc/init.d/memcache start memcache.stop = /etc/init.d/memcache stop memcache.restart = /etc/init.d/memcache restart[vsftpd] vsftpd.start = /etc/init.d/vsftpd start vsftpd.stop = /etc/init.d/vsftpd stop vsftpd.restart = /etc/init.d/vsftpd restart vsftpd.status = /etc/init.d/vsftpd status4.?init.d 腳本
Linux 所有守護進程都是用init.d下面的腳本來管理
當人你也可以直接運行命令:
nodekeeper --daemon --host localhost --port 7800但這樣只能算是一個半成品,也不夠專業(yè),我們寫的是linux運用程序,必須遵循Linux規(guī)范,所有要實現(xiàn)一個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:$PATH DAEMON=/srv/nodekeeper/bin/nodekeeper NAME=nodekeeper DESC=nodekeeper BASEDIR="/srv/nodekeeper" HOST=$(ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $1}'|head -n 1) PORT=7800 CONFIG=$BASEDIR/etc/$NAME.cfg LOGFILE=$BASEDIR/log/$NAME.log PIDFILE=$BASEDIR/run/$NAME.pid PIDFILE=/var/run/$NAME.pid PROTOCOL=$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 available if [ -f /etc/default/nodekeeper ] ; then. /etc/default/nodekeeper fiset -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" instart)echo -n "Starting $DESC: "start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \--exec $DAEMON -- $DAEMON_OPTS || trueecho "$NAME.";;stop)echo -n "Stopping $DESC: "start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \--exec $DAEMON || trueecho "$NAME.";;restart|force-reload)echo -n "Restarting $DESC: "start-stop-daemon --stop --quiet --pidfile \/var/run/$NAME.pid --exec $DAEMON || truesleep 1start-stop-daemon --start --quiet --pidfile \/var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS || trueecho "$NAME.";;reload)echo -n "Reloading $DESC configuration: "start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \--exec $DAEMON || trueecho "$NAME.";;configtest)echo -n "Testing $DESC configuration: "if test_nodekeeper_configthenecho "$NAME."elseexit $?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}" >&2exit 1;; esacexit 0我們將使用DAEMON_OPTS變量,提供所有需要的參數(shù)
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 stop4.2.?service start/stop
service nodekeeper start service nodekeeper stop總結
以上是生活随笔為你收集整理的Linux 应用程序开发入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王者荣耀百里玄策连招 2017年赵寅成
- 下一篇: linux 其他常用命令