上篇讲协同程序,现在一起学习一个很有趣的例子,通过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,结果如下:
三、总结:其实这个下载多个文件的例子,就是非抢先试的多线程问题的解决办法了。协同程序提供了一种协作式的多线程,每个协同程序都等于是一个线程。