最近遇到一个问题:实验室内部的网络是通过路由器分配IP的,但是经常又需要通过校园网远程实验室内部的电脑,而路由器的外网IP是由DHCP服务器动态分配的,IP地址无法绑定成静态的。RadminViewer远程的速度比较快,但是没办法穿墙,必须知道直连的IP地址,通过在实验室的路由器上设置转发端口,就可以实现实验室内部多台电脑同时远程。但是由于路由器上IP会变,自然想到在服务器上运行一个程序,每隔一段时间监测下路由器的IP,如果变化,就发送邮件通知。
使用Python编写,由于是一个后台的程序,自然想到要做出服务,就不会有窗口一直显示。将Python程序以Windows 服务方式启动,需要用到pywin32。
本来想实现可以获取每一层的IP,因为网络可能经过了多层的IP地址转换。但还不知道怎么做,最后参考了这里的方法后,目前是只能针对TP-Link的路由器获取外网IP,其他路由器没测试过。
后面还可以进一步扩展,实现很多功能,然后可以发邮件通知。使用的时候,需要先安装服务,然后再启动。服务安装后,默认是手动启动,如果需要设置为自动启动,还需要到Windows管理工具,服务设置中,将该服务设置为自动启动。
在开发过程中,可能需要不断调试以检测是否有bug,因此可以使用调试模式,Service debug,这样可以看到print输出的内容,用于测试服务是否能正常运行。
以下是代码
1 #-*- encoding: utf-8 -*- 2 3 #Service install 安装 4 #Service start 启动 5 #Service stop 停止 6 #Service debug 调试 7 #Service remove 删除 8 9 import win32serviceutil 10 import win32service 11 import win32event 12 import smtplib 13 import time, traceback 14 import threading 15 import logging 16 import win32evtlogutil 17 18 class Service(win32serviceutil.ServiceFramework): 19 _svc_name_ = "IPDetector" 20 _svc_display_name_ = "IPDetector" 21 _svc_description_ = "Detect the change status of router IP, and send mail to notify user." 22 _svc_deps_ = ["EventLog"] 23 _sleep_time_ = 20 * 60 #时间以秒为单位 24 _username_ = 'admin'#路由器用户名 25 _password_ = 'admin'#路由器密码 26 _routerIP_ = '192.168.1.1'#路由器内部IP 27 _mail_list_ = [ 28 "[email protected]", 29 "[email protected]" 30 ] 31 _currentIP_ = '' 32 33 def __init__(self, args): 34 win32serviceutil.ServiceFramework.__init__(self, args) 35 self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 36 print u'Service is running...' 37 38 def SvcDoRun(self): 39 import servicemanager 40 timer = threading.Timer(self._sleep_time_, self.process()) 41 timer.start() 42 # Write a 'started' event to the event log... 43 win32evtlogutil.ReportEvent(self._svc_name_, 44 servicemanager.PYS_SERVICE_STARTED, 45 0, # category 46 servicemanager.EVENTLOG_INFORMATION_TYPE, 47 (self._svc_name_, '')) 48 # wait for beeing stopped... 49 win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) 50 51 # and write a 'stopped' event to the event log. 52 win32evtlogutil.ReportEvent(self._svc_name_, 53 servicemanager.PYS_SERVICE_STOPPED, 54 0, # category 55 servicemanager.EVENTLOG_INFORMATION_TYPE, 56 (self._svc_name_, '')) 57 return 58 59 def SvcStop(self): 60 # Before we do anything, tell SCM we are starting the stop process. 61 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 62 # And set my event 63 win32event.SetEvent(self.hWaitStop) 64 return 65 66 def send_mail(self, mail_list, msg): 67 try: 68 handle = smtplib.SMTP('smtp.163.com', 25) 69 handle.login('[email protected]','password') 70 for mail in mail_list: 71 send_msg = "To:" + mail + "\r\nFrom:[email protected]\r\nSubject: The latest router IP \r\n\r\n"\ 72 + msg +"\r\n" 73 handle.sendmail('[email protected]', mail, send_msg) 74 handle.close() 75 return True 76 except: 77 print traceback.format_exc() 78 return False 79 80 def getRouterPublicIP(self, username, password, routerIP): 81 # this provide a way to get public ip address from tp-link 82 import httplib, re, base64 83 showErrorMessage = 0 84 85 # 192.168.1.1 86 conn = httplib.HTTPConnection(routerIP) 87 # set request headers 88 headers = {"User-Agent": "python host", 89 "Content-type": "application/x-www-form-urlencoded", 90 "Authorization": "Basic %s" % base64.encodestring('%s:%s' % (username, password))[:-1], 91 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 92 "Accept-Language": "zh-cn,zh;q=0.5", 93 "Accept-Encoding": "gzip, deflate", 94 "Accept-Charset": "GB2312,utf-8;q=0.7,*;q=0.7", 95 "Connection": "keep-alive"} 96 97 # get status page 98 conn.request("GET", "/userRpm/StatusRpm.htm", "", headers) 99 response = conn.getresponse() 100 keyword = re.search(' wanPara [^\)]*?\)', response.read()) 101 response.close() 102 conn.close() 103 104 # search the public ip address 105 found = 0 106 publicIP = "" 107 if keyword: 108 arr = re.findall('([\d]*?,)|(\"[^\"]*?\",)', keyword.group(0)) 109 if arr: 110 if len(arr) > 3: 111 publicIP = re.search('(?<=\")[^\"]*?(?=\")', arr[2][1]) 112 if publicIP: 113 publicIP = publicIP.group(0) 114 found = 1 115 116 if found == 1: 117 return publicIP 118 else: 119 if showErrorMessage == 1: 120 logging.info('router public ip address not found.') 121 #print "router public ip address not found." 122 123 def process(self): 124 latestIP = self.getRouterPublicIP(self._username_, self._password_, self._routerIP_) 125 logging.info('the latest router ip is: ' + latestIP) 126 #print 'the latest router ip is: ' + latestIP 127 if self._currentIP_ != latestIP: 128 _currentIP_ = latestIP 129 msg = u'The latest router IP is: ' + str(_currentIP_) 130 print time.strftime('%Y-%m-%d %X',time.localtime(time.time())) 131 if self.send_mail(self._mail_list_, msg): 132 logging.info('send mail success') 133 #print 'send mail success' 134 else: 135 #print 'send mail failed' 136 logging.info('send mail failed') 137 138 if __name__ == '__main__': 139 win32serviceutil.HandleCommandLine(Service)
这是另外一个版本,用于检测所在机器的本地IP和外网IP。
需要发送通知的邮件地址,需要以每行一个保存到当前目录下的mail.txt中,如
最新的本地IP地址会保存在latestIP.txt中。
IPDetector.py
1 #-*- encoding: utf-8 -*- 2 ''' 3 Created on 13-5-31 4 ''' 5 6 import smtplib 7 import time, traceback, sys, os 8 from email.mime.text import MIMEText 9 10 class IPDetector(): 11 def __init__(self): 12 pass 13 14 def send_mail(self, subject, content): 15 try: 16 handle = smtplib.SMTP('smtp.163.com', 25) 17 handle.login('[email protected]', 'password') 18 mail_list = self.getMailList() 19 time_str = time.strftime('%Y-%m-%d %X', time.localtime(time.time())) 20 msg = '<html><body>' + content + "<br><br><span style='color:#999;font-size:"\ 21 + "10px;font-family:Verdana;'>" \ 22 + time_str + " by servme</span>"+'</body></html>' 23 send_msg = MIMEText(msg, 'html', 'utf-8') 24 send_msg['Subject'] = subject 25 26 for mail in mail_list: 27 handle.sendmail('[email protected]', mail, send_msg.as_string()) 28 handle.close() 29 return True 30 except: 31 print traceback.format_exc() 32 return False 33 34 #读写最新IP信息的文件 35 def operIPFile(self, mode, data): 36 #获取脚本路径 37 path = sys.path[0] 38 #判断为脚本文件还是py2exe编译后的文件,如果是脚本文件,则返回的是脚本的目录, 39 #如果是py2exe编译后的文件,则返回的是编译后的文件路径 40 if os.path.isfile(path): 41 path = os.path.dirname(path) 42 43 if mode == 'read': 44 file = open(path + u'\latestIP.txt') 45 line = file.readline() 46 file.close() 47 return line 48 else: 49 file = open(path + u'\latestIP.txt', 'w') 50 file.write(data) 51 52 def getMailList(self): 53 mailList = [] 54 #获取脚本路径 55 path = sys.path[0] 56 #判断为脚本文件还是py2exe编译后的文件,如果是脚本文件,则返回的是脚本的目录, 57 #如果是py2exe编译后的文件,则返回的是编译后的文件路径 58 if os.path.isfile(path): 59 path = os.path.dirname(path) 60 file = open(path + u'\mail.txt') 61 while 1: 62 line = file.readline() 63 if not line: 64 break 65 mailList.append(line) 66 67 file.close() 68 return mailList 69 70 def getLocalPCIP(self): 71 import socket 72 localIP = socket.gethostbyname(socket.gethostname()) #得到本地ip 73 print localIP 74 75 import re, urllib2 76 #获取外网IP 77 class GetExtIP: 78 def getIP(self): 79 try: 80 extIP = self.visit("http://www.ip138.com/ip2city.asp") 81 except: 82 try: 83 extIP = self.visit("http://www.bliao.com/ip.phtml") 84 except: 85 try: 86 extIP = self.visit("http://www.whereismyip.com/") 87 except: 88 extIP = "So sorry!!!" 89 return extIP 90 91 def visit(self, url): 92 opener = urllib2.urlopen(url) 93 if url == opener.geturl(): 94 str = opener.read() 95 else: 96 str = '' 97 return re.search('\d+\.\d+\.\d+\.\d+', str).group(0) 98 99 externalIP = GetExtIP().getIP() 100 print externalIP 101 return localIP, externalIP 102 103 #取本地IP 104 def process(self): 105 localIP, externalIP = self.getLocalPCIP() 106 currentIP = self.operIPFile('read', None) 107 if currentIP != localIP: 108 self.operIPFile('write', localIP) 109 time_str = time.strftime('%Y-%m-%d %X', time.localtime(time.time())) 110 import socket 111 hostname = socket.gethostname() 112 print hostname 113 content = 'Host Name: '+ hostname + '<br>' \ 114 + 'Local IP address: ' + localIP + '<br>' \ 115 + 'External IP address: ' + externalIP + '<br>' 116 subject = "The IP address of " + hostname + " has Changed" 117 if self.send_mail(subject, content): 118 print time_str + ' send mail success' 119 else: 120 print time_str + ' send mail failed' 121 else: 122 print 'The IP address is same with the last detection' 123 124 if __name__=='__main__': 125 ipDetector = IPDetector() 126 sleep_time = 20 * 60 #时间以秒为单位 127 while True: 128 ipDetector.process() 129 time.sleep(sleep_time)
IPService.py
1 #-*- encoding: utf-8 -*- 2 ''' 3 Created on 13-6-1 4 ''' 5 #IPService install 安装 6 #IPService start 启动 7 #IPService stop 停止 8 #IPService debug 调试 9 #IPService remove 删除 10 11 import win32serviceutil 12 import win32service 13 import win32event 14 import threading 15 import win32evtlogutil 16 import time 17 18 class Service(win32serviceutil.ServiceFramework): 19 _svc_name_ = "IPDetector" 20 _svc_display_name_ = "IPDetector" 21 _svc_description_ = "Detect the change status of IP, and send mail to notify user." 22 _svc_deps_ = ["EventLog"] 23 _sleep_time_ = 20 * 60 #时间以秒为单位 24 25 def __init__(self, args): 26 win32serviceutil.ServiceFramework.__init__(self, args) 27 self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) 28 print 'Service is running...' 29 30 def SvcDoRun(self): 31 # import servicemanager 32 # from IPDetector import IPDetector 33 # ipDetector = IPDetector() 34 # timer = threading.Timer(self._sleep_time_, ipDetector.process()) 35 # timer.start() 36 # 37 # # Write a 'started' event to the event log... 38 # win32evtlogutil.ReportEvent(self._svc_name_, 39 # servicemanager.PYS_SERVICE_STARTED, 40 # 0, # category 41 # servicemanager.EVENTLOG_INFORMATION_TYPE, 42 # (self._svc_name_, '')) 43 # 44 # # wait for beeing stopped... 45 # win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) 46 # 47 # # and write a 'stopped' event to the event log. 48 # win32evtlogutil.ReportEvent(self._svc_name_, 49 # servicemanager.PYS_SERVICE_STOPPED, 50 # 0, # category 51 # servicemanager.EVENTLOG_INFORMATION_TYPE, 52 # (self._svc_name_, '')) 53 54 #---------------------------------------------------------------- 55 import servicemanager 56 # Make entry in the event log that this service started 57 servicemanager.LogMsg( 58 servicemanager.EVENTLOG_INFORMATION_TYPE, 59 servicemanager.PYS_SERVICE_STARTED, 60 (self._svc_name_, '') 61 ) 62 from IPDetector import IPDetector 63 ipDetector = IPDetector() 64 # Set an amount of time to wait (in milliseconds) between runs 65 self.timeout = 100 66 while 1: 67 # Wait for service stop signal, if I timeout, loop again 68 rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout) 69 # Check to see if self.hWaitStop happened 70 if rc == win32event.WAIT_OBJECT_0: 71 # Stop signal encountered 72 break 73 else: 74 # Put your code here 75 ipDetector.process() 76 time.sleep(self._sleep_time_) 77 # Only return from SvcDoRun when you wish to stop 78 return 79 #----------------------------------------------------------------- 80 81 def SvcStop(self): 82 # Before we do anything, tell SCM we are starting the stop process. 83 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 84 # And set my event 85 win32event.SetEvent(self.hWaitStop) 86 87 if __name__ == '__main__': 88 win32serviceutil.HandleCommandLine(Service)
参考资料
http://www.cnblogs.com/talywy/archive/2013/03/07/SynctimeTool.html
http://www.oschina.net/code/snippet_244244_9744