這個項目主要用的是的CGI進行遠程編輯——在另外一臺機器上經過Web來編輯 文檔。你在一臺機器上存儲了一個文檔,但願可以在另外一臺機器上經過Web來編輯它。這讓多個用 戶可以協做編輯一個文檔,且無需使用FTP或相似的文件傳輸技術,也無需操心同步多個副本的 問題。要編輯文件,只要有Web瀏覽器就行。html
這個簡單的程序的邏輯大概以下:
- 獲取CGI參數text(默認爲數據文件的當前內容)
- 將text的值保存到數據文件中
- 打印表單,其中的文本區域包含text的值
要讓腳本可以寫入數據文件,必須先建立這樣的文件(如simple_edit.dat)。這個文件能夠爲 空,也可包含初始文檔(純文本文件,其中可能包含一些標記,如XML或HTML)。
運行以前,首先小編先介紹一下如何在Tomcat中運行Python腳本:
① 修改Tomcat的配置文件:web.xmlnode
<servlet> <servlet-name>cgi</servlet-name> <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>cgiPathPrefix</param-name> <param-value>WEB-INF/cgi</param-value> </init-param> <init-param> <param-name>executable</param-name> <param-value>C:\Users\Administrator\AppData\Local\Programs\Python\Python36-32\python.exe</param-value> </init-param> <init-param> <param-name>passShellEnvironment</param-name> <param-value>true</param-value> </init-param> <load-on-startup>5</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cgi</servlet-name> <url-pattern>/cgi-bin/*</url-pattern> </servlet-mapping> #passShellEnvironment: 與Python解析器解析CGI腳本有關,可是必定要配置好Python的環境變量; #cgiPathPrefix: 配置訪問的腳本目錄 #executable: (本地python的安裝路徑)配置Python的解析器
② 修改context.xml
在標籤中加入:
因爲上面web.xml是這樣配置的:
因此在Tomcat的webapps中建立一個cgitest,而後在cgitest中建立一個WEB-INF,而後在WEB-INF中建立一個cgi文件夾,將編寫的Python腳本放入cgi文件夾中:
腳本:test.py
最終路徑:webapps/cgitest/WEB-INF/cgi/test.py
而後訪問時:http://localhost:8080/cgitest/cgi-bin/test.py
注意這裏的cgi-bin是:
③ 重啓Tomcatpython
#腳本: #C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe import cgi form = cgi.FieldStorage() text = form.getvalue('text', open('simple_edit.dat').read()) f = open('simple_edit.dat', 'w') f.write(text) f.close() print("Content-type: text/html\n\n") print(""" <html> <head> <title>A Simple Editor</title> </head> <body> <form action='simple_edit.py' method='POST'> <textarea rows='10' cols='20' name='text'>{}</textarea><br /> <input type='submit' /> </form> </body> </html> """.format(text))
效果:
當在輸入框中編輯而後提交後,內容會更新到simple_edit.dat中。mysql
###(4) 再次實現
至此,第一個原型已編寫好,它還缺什麼呢?應讓用戶可以編輯多個文件,並使用密碼保護 這些文件。相比於第一個原型,再次實現的主要不一樣在於,你將把它分紅兩個CGI腳本,分別對應於系 統支持的兩種操做。新的原型包含以下文件:git
#C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe print('Content-type: text/html\n\n') import cgi, sys form = cgi.FieldStorage() print(''' <html> <head> <title>File Editor</title> </head> <body> <form action='edit.py' method='POST'> <b>File name:</b><br/> <input type='text' name='filename'/> <input type='submit' value='Open'/> </form> </body> </html> ''')
文本框名爲filename,這確保其內容將經過CGI參數filename提供給腳本edit.cgi,若是在文本框中輸入文件名,再 單擊Open按鈕,將運行腳本edit.cgi。
② 編寫編輯器腳本
腳本edit.cgi顯示的頁面應包含一個文本區域和一個文本框,其中前者包含當前編輯的文件的 內容,然後者用於輸入密碼。這個腳本須要的惟一輸入是文件名,它是從index.html中的表單中得到的。然而,可在不提交index.html中表單的狀況下直接運行腳本edit.py。在這種狀況下, cgi.FieldStorage的字段將是未設置的。所以,你須要檢查是否得到了文件名;若是得到了,就 打開指定目錄中的這個文件。咱們將這個目錄命名爲data(固然,你必須建立這個目錄)。
③ 編寫保存腳本
這個簡單系統的最後一部分是執行保存的腳本。它接收文件名、密碼和一些文本,並檢查密 碼是否正確;若是正確,就將這些文本存儲到指定的文件中。咱們將使用模塊sha來處理密碼。web
本項目實現的是基於Web的論壇,雖然其功能與複雜的社交媒體平臺相距甚遠,但提供了評論系統的基本功能。sql
在這個項目中,你將建立一個經過Web發佈和回覆消息的簡單系統,它可做爲論壇使用。這 個系統很是簡單,但提供了基本的功能,並可以處理大量的帖子。
本章介紹的技術不只可用於開發獨立論壇,還可用於實現更通用的協做系統、問題跟蹤系統、 帶評論功能的博客等。經過將支持在消息下方以縮放的方式顯示回覆CGI(或相似的技術)和可靠的數據庫(這裏是SQL數據庫)結合 起來使用,可實現很是強大的功能,並且用途很是普遍。
最終這個項目要實現的功能:數據庫
-- 建立MySQL數據庫 CREATE TABLE messages ( id INT NOT NULL AUTO_INCREMENT, subject VARCHAR(100) NOT NULL, sender VARCHAR(15) NOT NULL, reply_to INT, text MEDIUMTEXT NOT NULL, PRIMARY KEY(id) );
字段介紹:apache
#鏈接數據庫腳本: import pymysql def create_table(cursor,table_name,create_sql): # 使用 execute() 方法執行 SQL,若是表存在則刪除 drop_table = 'drop table if EXISTS '+table_name cursor.execute(drop_table) # 建表 cursor.execute(sql) def insert(cursor,table_name): reply_to = input('Reply to: ') subject = input('Subject: ') sender = input('Sender: ') text = input('Text: ') sql='insert into {}(reply_to, sender, subject, text) values({},"{}","{}","{}")'\ .format(table_name,reply_to, sender, subject, text) cursor.execute(sql) if __name__=='__main__': server_host="localhost" user="xxxx" passwd= "xxxx" db_name="test" # 打開數據庫鏈接 db = pymysql.connect(server_host, user,passwd,db_name) # 使用 cursor() 方法建立一個遊標對象 cursor cursor = db.cursor() #建表 sql = ''' CREATE TABLE messages ( id INT NOT NULL AUTO_INCREMENT, subject VARCHAR(100) NOT NULL, sender VARCHAR(15) NOT NULL, reply_to INT, text MEDIUMTEXT NOT NULL, PRIMARY KEY(id) ) ''' create_table(cursor,"messages",sql) #插入數據 insert(cursor,db_name+".messages") db.commit() # 關閉鏈接 cursor.close()
#cgi腳本: ''' 公告板主頁 ''' print('Content-type: text/html\n') import cgitb; cgitb.enable() import pymysql server_host = "localhost" user = "root" passwd = "123456" db_name = "test" # 打開數據庫鏈接 db = pymysql.connect(server_host, user, passwd, db_name) curs = db.cursor() print(""" <html> <head> <title>The FooBar Bulletin Board</title> </head> <body> <h1>The FooBar Bulletin Board</h1> """) toplevel = [] children = {} curs.execute('SELECT * FROM test.messages') rows = curs.fetchall() for row in rows: parent_id = row[3] if parent_id is None: toplevel.append(row) else: children.setdefault(parent_id, []).append(row) def format(row): print(row[1]+'</br>') try: kids = children[row[0]] except KeyError: pass else: print('<blockquote>') for kid in kids: format(kid) print('</blockquote>') print('<p>') for row in toplevel: format(row) print(""" </p> </body> </html> """)
初次實現的功能頗有限,用戶甚至不能發佈消息。這裏咱們將內容豐富一些:編程
本項目是一個簡單的文件共享應用程序。咱們將使用的主要技術是XML-RPC,這是一種遠程調用過程(函數)的協議, 這種調用多是經過網絡進行的。若是你願意,可以使用普通的套接字編程輕鬆地實現這個項目的功能。
咱們要建立一個P2P(peer-to-peer)文件共享程序。大體而言,文件共享意味着在運行於不 同計算機上的程序之間交換文件。在P2P交互中,任何對等體(peer)均可鏈接到其餘對等體。在 這樣一個由對等體組成的網絡中,不存在中央權威,這讓網絡更健壯,由於除非你關閉大部分對等體,不然這樣的網絡不可能崩潰。
項目知足條件:
這個RPC簡單的說就是遠程調用服務端的方法,接下來咱們簡單是實現如下,看看效果:
#Server: ''' 對下面案例的解釋: 方法register_instance註冊一個實現了其「遠程方法」的實例,也 可以使用方法register_function註冊各個函數。爲運行服務器作好準備(讓它可以響應來自外部的 請求)後,調用其方法serve_forever ,方法serve_forever解釋器看起來就像「掛起」了同樣,但實際上它是在等待RPC請求。 ''' from xmlrpc.server import SimpleXMLRPCServer s=SimpleXMLRPCServer(("",16888)) #localhost和端口4242 def twice(x): return x*2 s.register_function(twice) # 給服務器添加功能,也就是客戶端調用的函數 s.serve_forever() # 啓動服務器
#Server: ''' 對下面案例的解釋: 方法register_instance註冊一個實現了其「遠程方法」的實例,也 可以使用方法register_function註冊各個函數。爲運行服務器作好準備(讓它可以響應來自外部的 請求)後,調用其方法serve_forever ,方法serve_forever解釋器看起來就像「掛起」了同樣,但實際上它是在等待RPC請求。 ''' from xmlrpc.server import SimpleXMLRPCServer s=SimpleXMLRPCServer(("",16888)) #localhost和端口4242 def twice(x): return x*2 s.register_function(twice) # 給服務器添加功能,也就是客戶端調用的函數 s.serve_forever() # 啓動服務器
先運行server在運行client,咱們發現:
在server中出現:
而client成功調用了sever的方法,並輸出告終果:4。
是否是挺簡單的,好像URL請求同樣,接下來咱們就要根據上面的需求實現相應的功能了:
① 定義node
回顧需求,咱們關心的主要有兩點:Node 必須存儲哪些信息(屬性);Node必須可以執行哪些操做(方法)。
全部node須要有如下屬性:
- 目錄名:讓Node知道到哪裏去查找文件或將文件存儲到哪裏。
-密碼:供其餘節點用來將本身標識爲可信任方。
- 一組已知的對等體(URL)。
- URL:可能加入到查詢歷史記錄中或提供給其餘節點
Node能執行什麼操做呢,必須定義一些方法:
- 用於查詢的方法(query)
- 獲取和存儲文件的方法(fetch)
- 向其餘節點介紹本身的方法(hello)
② fetch方法解讀
這個方法必須接受參數query和 secret,其中secret是必不可少的, 可避免節點被其餘節點隨便操縱。調用fetch將致使節 點下載一個文件。若是提供的密碼不一樣於(啓動時指定的)self.secret,fetch將直接返回FAIL; 不然它將調 用query來獲取指定的文件。調用query時,你但願可以知道查詢是否 成功,並在成功時返回指定文件的內容。所以,咱們將query的返回值定義爲元組(code, data), 其中code的可能取值爲OK和FAIL,而data是一個字符串。若是code爲OK,這個字符串將包含找到 的文件的內容;不然爲一個隨意的值,如空字符串。若是查詢成功,而且返回OK,則開始建立一個文件並開始向其中寫入內容。
③ query方法解讀
query的結構:query→self._handle→_broadcast
首先調用_handle方法,它負責查詢的內容處理(檢查節點是否包含指定的文件,獲取數據 等),它像query同樣返回一個編碼和一些數據。若是code爲OK,則直接返回,若是爲FAIL,就須要其餘節點的幫助所以它須要將self.url 添加到history中,而後經過history向方法_broadcast向全部已知的對等體廣播查詢,它迭代self.known的副本,若是當前對等體包含在history中, 就使用continue語句跳到下一個對等體,不然建立一個ServerProxy對象,並對其調用方法query。 若是方法query成功,就將其返回值做爲_broadcast的返回值。
因爲代碼比較長這裏就不給出了,能夠在小編的git中下載。
最後咱們演示必定這個初級版的程序如何運行:
mypeer:python xxx.py http://localhost:16888 file1 123456 otherpeer:python xxx.py http://localhost:16889 file2 123456
#建立mypeer
它訪問file2下的文件:
而後建立otherpeer:
它訪問file1下的文件:
把mypeer介紹給otherpeer:
而後在訪問file1下的文件:
經過otherpeer拉去file1下的文件:
發現file2下有test1.txt:
有朋友想問,這個有啥用啊,咱們不妨拓展一下思惟,若是這裏是一個集羣,咱們用client去訪問server中的文件,可是文件分佈在server集羣的某個節點上,想一想咱們可不可經過這種方法,把文件傳遞到訪問的節點上,而後從訪問節點中拿取,個人天一不當心瞭解分佈式的原理,低調低調。
最終版咱們須要在初次實現中修改一些內容:
① 建立客戶端界面
客戶端界面是使用模塊cmd中的Cmd類實現,咱們在這裏只實現命令fetch(下載文件)和exit(退出程序)。命令fetch調用服務器的方 法fetch,並在文件沒有找到時打印一條錯誤消息。命令exit打印一個空行(這只是出於美觀考 慮)並調用sys.exit。
② 引起異常
不返回表示成功仍是失敗的編碼,而是假定確定會成功,並在失敗時引起異常。這裏咱們使用200正常的失敗(請求未獲得處理)和500表示 請求被拒絕(拒絕訪問)。
UNHANDLED = 200 ACCESS_DENIED = 500 class UnhandledQuery(Fault): """ 表示查詢未獲得處理的異常 """ def __init__(self, message="Couldn't handle the query"): super().__init__(UNHANDLED, message) class AccessDenied(Fault): """ 用戶試圖訪問未得到受權的資源時將引起的異常 """ def __init__(self, message="Access denied"): super().__init__(ACCESS_DENIED, message)
異常是xmlrpc.client.Fault的子類。在服務器中引起的異常將傳遞到客戶端,並保持 faultCode不變。若是在服務器中引起了普通異常(如IOException),也將建立一個Fault類實例, 所以你不能在服務器中隨意地使用異常。
③ 驗證文件名
檢查指定的文件是否包含在指定的目錄中。咱們這裏採用較爲簡單的方法實現:
根據目錄名和文件名建立絕對路徑(例如,這將把'/foo/bar/../ baz'轉換爲'/foo/baz'),將目錄名與空文件名合併以確保它以文件分隔符(如'/')結尾,再檢 查絕對文件名是否以絕對路徑名打頭。若是是這樣的,就說明指定的文件包含在指定的目錄中。
這裏在pychram中運行有諸多bug,使用cmd感受不是那麼好使,或者能夠在Linux下運行試試,這裏小編也給出了一個Cmd運行的dome,大概是這樣的:
在Miller2>後面輸入,定義的do_xxx的方法中的xxx,它就會執行相應方法中的內容。
最後小編在這裏說一下這個項目的運行規則:
運行:python client.py urls.txt directory http://servername.com:16888
執行錯誤操做:
首先查詢全部的能關聯的全部的urls列表,沒有的返回Couldn't find the file 123。
輸入exit 或者EOF會退出程序:
這個項目較小,你將看到給既 有Python程序添加GUI很是容易。
開發的文件共享系統:添加GUI客戶端,讓它使用起來更 容易。這意味着可能有更多的人選擇使用它,他實現的需求是:
開發這個項目時,最好是將項目8作一遍以後,而後在熟悉熟悉GUI的工具包的使用。
因爲這裏是結合着項目8的代碼,這裏小編給出運行結果:
第一個原型很是簡單,它確實實現了文件共享功能,但對用戶不太友好。若是用戶可以知道 有哪些文件可用(這些文件多是程序啓動時就位於文件目錄中,也多是後來從其餘節點那裏 下載的),將大有裨益。再次實現將實現這種列出文件的功能,要獲取節點包含的文件的列表。
到了最後一個項目了,這也是Python權威指南的最後一課了,通過一個多月的學習對Python也有了大體的瞭解,但願之後能夠運用到工做中,至少目前看代碼時徹底沒有問題。
最後一個項目,應該說是全部項目中代碼量最多的,並且也比較有趣,這個項目將學習如何使用Pygame,這個擴展讓你可以使用Python編寫功能齊備的全屏街機遊戲。
這個遊戲中,咱們將讓玩家控制一支香蕉。這支香蕉要躲開從天而 降的16噸鉛錘,盡力在防護戰中活下來。這個項目的目標是圍繞着遊戲設計展開的。這款遊戲必須像設計的那樣:香蕉可以移動,16 噸的鉛錘從天而降。另外,與往常同樣,代碼必須是模塊化的,且易於擴展。一個重要的需求是, 設計應包含一些遊戲狀態(如遊戲簡介、關卡和「遊戲結束」狀態),同時可輕鬆地添加新狀態。
這個項目須要的工具只有一個,那就是pygame。模塊pygame自動導入其餘全部的Pygame模塊,所以只要在程序開頭包含語句import pygame, 就能使用其餘模塊,如pygame.display和pygame.font。
模塊pygame包含函數Surface,它返回一個新的Surface對象。Surface對象其實就是一個指定 尺寸的空圖像,可用來繪畫和傳送。傳送(調用Surface對象的方法blit)意味着在Surface之間傳輸內容。
函數init是Pygame遊戲的核心,必須在遊戲進入主事件循環前調用。這個函數自動初始化其餘全部模塊(如font和image)。
下載:pip install pygame
若是要捕獲Pygame特有的錯誤,就須要使用error類。
接下來咱們再來了解幾個pygame重要的函數:
在初次實現的時候,咱們只實現其中一些簡單的功能:
# C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe # -*- coding: utf-8 -*- ''' 簡單的「鉛錘從天而降」動畫 ''' import sys,pygame from random import randrange from pygame.locals import * class Weight(pygame.sprite.Sprite): def __init__(self,speed): pygame.sprite.Sprite.__init__(self) self.speed=speed #繪製Sprite對象時要用到的圖像和矩形: self.image=weight_image self.rect=self.image.get_rect() self.reset() def reset(self): """ 將鉛錘移到屏幕頂端的一個隨機位置 """ self.rect.top=-self.rect.height self.rect.centerx=randrange(screen_size[0]) def update(self): """ 更新下一幀中的鉛錘 """ if self.rect.top> screen_size[1]: self.reset() self.rect.top += self.speed #初始化 pygame.init() screen_size=1366,768 pygame.display.set_mode(screen_size,FULLSCREEN) pygame.mouse.set_visible(0) # 加載鉛錘圖像 image_path="images/jack.jpg" weight_image=pygame.image.load(image_path) weight_image=weight_image.convert() # 以便與顯示匹配 # 你可能想設置不一樣的速度 #speed=5 # 建立一個Sprite對象編組,並在其中添加一個Weight實例 sprites = pygame.sprite.RenderUpdates() #同時添加三個鉛塊 sprites.add(Weight(speed=2)) sprites.add(Weight(speed=3)) sprites.add(Weight(speed=5)) # 獲取並填充屏幕表面 screen=pygame.display.get_surface() bg=(255, 255, 255) #白色 #以白色填充屏幕表面 screen.fill(bg) #顯示所作的修改 pygame.display.flip() clock=pygame.time.Clock() # 設置時鐘,限制移動速度 # 用於清除Sprite對象: def clear_back(surf,rect): surf.fill(bg, rect) while True: clock.tick(60) # 而後在while循環中設置多長時間運行一次,每秒執行60次 # 檢查退出事件: for event in pygame.event.get(): if event.type==QUIT: sys.exit() if event.type== KEYDOWN and event.key == K_ESCAPE: sys.exit() # 清除之前的位置: sprites.clear(screen,clear_back) # 更新全部的Sprite對象: sprites.update() #方法update調用Weight實例的方法update # 繪製全部的Sprite對象: updates=sprites.draw(screen) #調用sprites.draw並將屏幕表面做爲參數,以便在當前位置繪製鉛錘 # 更新必要的顯示部分: pygame.display.update(updates)
最終咱們將實現這樣的一個遊戲:
這裏咱們將實現這樣幾個模塊:
config.py:可根據偏好隨意修改配置變量,若是遊戲的節奏太快或太慢,可嘗試修改與速度相關的變量
objects.py:這個模塊包含遊戲Squish使用的遊戲對象
squish.py:這個模塊包含遊戲Squish的主遊戲邏輯
開始界面:
遊戲界面:
闖關界面:
結束界面:
到此全部的項目都已結束!!!
全部的代碼 小編都上傳到gitlab上: