您好,登錄后才能下訂單哦!
這個項目主要用的是的CGI進行遠程編輯——在另一臺機器上通過Web來編輯 文檔。你在一臺機器上存儲了一個文檔,希望能夠在另一臺機器上通過Web來編輯它。這讓多個用 戶能夠協作編輯一個文檔,且無需使用FTP或類似的文件傳輸技術,也無需操心同步多個副本的 問題。要編輯文件,只要有Web瀏覽器就行。
這個簡單的程序的邏輯大概如下:
- 獲取CGI參數text(默認為數據文件的當前內容)
- 將text的值保存到數據文件中
- 打印表單,其中的文本區域包含text的值
要讓腳本能夠寫入數據文件,必須先創建這樣的文件(如simple_edit.dat)。這個文件可以為 空,也可包含初始文檔(純文本文件,其中可能包含一些標記,如XML或HTML)。
運行之前,首先小編先介紹一下如何在Tomcat中運行Python腳本:
① 修改Tomcat的配置文件:web.xml
<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是:
③ 重啟Tomcat
#腳本:
#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中。
###(4) 再次實現
至此,第一個原型已編寫好,它還缺什么呢?應讓用戶能夠編輯多個文件,并使用密碼保護 這些文件。相比于第一個原型,再次實現的主要不同在于,你將把它分成兩個CGI腳本,分別對應于系 統支持的兩種操作。新的原型包含如下文件:
#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發布和回復消息的簡單系統,它可作為論壇使用。這 個系統非常簡單,但提供了基本的功能,并能夠處理大量的帖子。
本章介紹的技術不僅可用于開發獨立論壇,還可用于實現更通用的協作系統、問題跟蹤系統、 帶評論功能的博客等。通過將支持在消息下方以縮放的方式顯示回復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)
);
字段介紹:
#連接數據庫腳本:
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>
<h2>The FooBar Bulletin Board</h2>
""")
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上:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。