Lua 學習筆記(七) —— 協同程序(三)

上篇講協同程序,現在一起學習一個很有趣的例子,通過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,結果如下:


三、總結:其實這個下載多個文件的例子,就是非搶先試的多線程問題的解決辦法了。協同程序提供了一種協作式的多線程,每個協同程序都等於是一個線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章