上篇講協同程序,現在一起學習一個很有趣的例子,通過HTTP下載幾個遠程文件。首先我們確定如何下載單個文件,然後就是多個文件下載確定是使用順序一個一個下載還是併發下載。
一、下載單個文件。(本文從環球網協會下載《HTML3.2參考規範》:http://www.w3.org/TR/REC-html32)
require "socket" --加載LuaSocket庫
host = "www.w3.org"
file = "/TR/REC-html32.html" --定義主機和下載文件
function download(host,file)
local c = assert(socket.connect(host,80)) --打開一個TCP連接,連接到該站點的80端口
local count = 0 -- 計算字符個數
c:send("GET "..file.." HTTP/1.0\r\n\r\n") --發送請求
while true do
local s,status,partial = c:receive() --接收文件
count = count + #(s or partial)
if status == "closed" then
break
end
end
c:close() --關閉連接
print(file,count) -- 輸出文件名和接收到的字符個數
end
download(host,file) --調用主函數
運行結果:
如果你出現這樣的結果:
是由於請求命令格式出錯。
也就是GET後面應該有個空格,HTTP前面應該有個空格。這是HTTP協議規定的。
現在來總結一下用HTTP下載遠程文件的流程好了。
打開TCP連接 ——> 發送請求 ——> 接收文件 ——> 關閉請求
二、下載多個文件方式的確定
如果是按順序下載的話,優點是思路很清晰,缺點是需要等待一個下載完才能下載另一個,如果下載過程中出現接收超時等問
題,我們就必須等待直至接收完畢,這樣很耗時。第二種是併發下載,如果出現哪個文件接收超時或者其他情況,則馬上掛起
程序,下載下一個文件;缺點是耗CPU資源略多。所以用第二種方法。
require "socket"
host = "www.w3.org"
function download(host,file) -- 下載單個文件
local c = assert(socket.connect(host,80))
local count = 0
c:send("GET "..file.." HTTP/1.0\r\n\r\n")
while true do
local s,status,partial = receive(c) --這個receive與上面的不同,這裏是自己寫的函數
count = count + #(s or partial)
if status == "closed" then
break
end
end
c:close()
print(file,count)
end
function receive (connection) --接收函數
connection:settimeout(0)
local s,status,partial = connection:receive(2^10)
if status == "timeout" then --接收超時,掛起本程序
coroutine.yield(connection)
end
return s or partial , status
end
threads = {} -- table
function get(host,file) -- 創建協同程序
local co = coroutine.create(function()
download(host,file)
end)
table.insert(threads,co) --將協同程序加入threads table中
end
function dispatch() --主調用函數
local i = 1
while true do
if threads[i] == nil then --如果table中的第i個程序執行完畢了,即從本table中移除了,則從頭開始判斷
if threads[1] == nil then --第一個數據也爲空,表示此table中沒有數據了,即4個文件已經下載成功了
break
end
i = 1
end
local status,res = coroutine.resume(threads[i])如果第i個數據 --沒有移除,則再次啓動該程序
if not res then -- 數據接收完畢,移除本程序,table中下一個會向前移動一個
table.remove(threads,i)
else
i = i+1 -- 接收出現問題,沒有完成,下個程序繼續判斷
end
end
end
get(host,"/TR/html401/html40.txt")
get(host,"/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
get(host,"/TR/REC-html32.html")
get(host,"/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")
dispatch() --調用主程序
執行結果:
總結一下流程:
1.首先啓動四個協同程序:
get(host,"/TR/html401/html40.txt")
get(host,"/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
get(host,"/TR/REC-html32.html")
get(host,"/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")
如果它們中有誰在接收過程中遇到問題,即接收超時,就掛起該程序。
2.調用dispatch()函數
循環的查找這四個協同程序誰被掛起了,然後再次啓動該程序。成功下載後就從threads 中移除。這裏需要注意table中移除一個
數據後,下一個數據就會向前移動一個位置來填充這個空缺。例如:
buffer = {1,2,3,4} --創建table
table.remove(buffer,1)
for i = 1, 4 do
print(buffer[i])
end
這裏移除了第一個數據,即1被移除了,遍歷此table,結果如下:
三、總結:其實這個下載多個文件的例子,就是非搶先試的多線程問題的解決辦法了。協同程序提供了一種協作式的多線程,每個協同程序都等於是一個線程。