大多数现代Web组件栈允许通过栈式/组件式中间件“过滤”请求,这样就能干净地从web应用中分离出横切关注点(译注:面向方面程序设计中的概念?)。 本周我尝试在Go语言的 http.FileServer 中植入钩子,发现实现起来十分简便,让我非常惊讶。
目前创新互联已为近千家的企业提供了网站建设、域名、虚拟主机、网站改版维护、企业网站设计、昂仁网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
让我们从一个基本的文件服务器开始说起:
func main() {
http.ListenAndServe(":8080", http.FileServer(http.Dir("/tmp")))
}
对于 HTTP 协议, 我们在使用 POST 上传的时候, 其实是有好几种不同的处理方式的, 所以对于客户端和服务器端, 也分别都有不同的处理. 正常普通的网页在提交参数上传到服务器的时候, 主要会根据内容的不同来使用不同的处理. 所体现在不同的地方在 Content-Type 的类型.
比如我们常常用 Mojolicious 处理这类接收到的参数和内容的时候, 会让很多人晕掉, 所以我在这, 基于协议的头, 来给大家介绍一下在参数和上传的时候有什么不同.
客户端, 比如浏览器网页中的 form 的表格的参数的不同, 客户端比如 Linux 命令行的 curl 的参数的不同和程序接口提交参数的不同, HTTP 协议在上传的时候, 大约会有三种不同, 这些体现在 Content-Type 的三种类型:
application/x-www-form-urlencoded
multipart/form-data
post 的 body 的内容
下面我们来详细介绍
1. application/x-www-form-urlencoded 默认
浏览器: 在 HTML 中 form 有个参数是 enctype 属性用于指定编码方式, 常用有前面讲的两种: application/x-www-form-urlencoded 和 multipart/form-data. 但默认的时候, 我们并不指定. 不指定的时候, 默认是 "application/x-www-form-urlencoded" , 所以其实, 我们平时都是使用的这种格式来提交数据. 因为是默认就不写出来了. 注意, 这个会对空格和特别的符号进行 url 的 encode.
程序: 我们现在以 Mojo::UserAgent 这个模块为例子, 我们提交一个参数 args 值为 test.
$ua-post('ht tp:/ /w ww.php-oa.co m/a/b' = form = { args = 'test'});
命令:
$curl -svo /dev/null -d "args=test" htt p:/ /w ww.php-oa.co m/a/b
HTTP 协议状态
这个时候所发送的 HTTP 的头和内容分别如下. body 中会存着参数, 会有一个特别的 Header.
-- Blocking request (ht tp:/ /w ww.php-oa.co m/a/b)
-- Connect (ht tp:ww w.php-oa.c om:80)
-- Client Server (htt p://w ww.php-oa.c om/a/b)
POST /a/b HTTP/1.1
User-Agent: Mojolicious (Perl)
Connection: keep-alive
Content-Type: application/x-ww w-form-urlencoded
Accept-Encoding: gzip
Content-Length: 9
Host:
args=test
服务端接收方式
这个时候, 服务器会根据因为是 POST 的方法, 并且头部的 Content-Type: application/x-www-form-urlencoded 会去解析 body 的参数. 这样在 Mojolicious 服务器
post '/a/b' = sub {
my $self = shift;
my $foo = $self-param('args');
$self-render(text = "Hello from $foo.");
}
2. multipart/form-data 大文件, 媒体文件
对于比较大的, 有一些二进制数据和象视频文件之类大文件, 建议使用这种方式上传.
浏览器:
普通的 HTTP 的写法如果要使用 enctype 的话, 只要象下面一样就行.
form action="/path/to/login" enctype="multipart/form-data"
input disabled="disabled" name="first_name" type="text" /
input value="Ok" type="submit" /
/form
客户端:
在 Mojo::UserAgent 考虑得非常周全, 当你提交的内容中包含二进制文件之类时, 就会自动帮你转换成 "multipart/form-data" 格式提交. 这个格式会生成一个随机字符来分割不同参数. 区分是否使用这种格式主要是, 当你提交的参数中, 又是一个引用, 并且引中可以使用 content 来指定内容或者 file 来指定路径.
$ua-post('htt p:/ /w ww.php-oa.c om/a/b' = form = { args = 'test', args_file = { file = '/root/.bash_history' } });
# or
$ua-post('ht tp://w ww.php-oa.c om/a/b' = form = { args = 'test', args_file = { content = 'test' } });
HTTP 协议状态
这个地方我们可以见到 Content-Type: multipart/form-data 的请求头, 告诉文件和参数是这种格式上传过来的.并且 boundary 用于指定一个参数之间的分割符.
-- Blocking request (ht tp://w ww.php-oa.c om/a/b)
-- Connect (ht tp:w ww.php-oa.co m:80)
-- Client Server (ht p://ww w.php-oa.co m/a/b)
POST /a/b HTTP/1.1
User-Agent: Mojolicious (Perl)
Content-Type: multipart/form-data; boundary=WRoHX
Connection: keep-alive
Accept-Encoding: gzip
Content-Length: 14428
Host:
--WRoHX
Content-Disposition: form-data; name="args"
test
--WRoHX
Content-Disposition: form-data; name="args_file"; filename=".bash_history"
........文件本身
服务器接收方式
在后端的服务器接收的时候 Mojolicious 想得非常周到. 对于这种格式能自动解析, 并且全程异步.不会多占内存. 这个会自动给大的文件使用一个叫 Mojo::Upload 的对象来处理, 我们可以通过 $self-req-upload('args_file'); 这个方法取得这个内容的对象, 这个内容的对象是Mojo::Asset::File 这个对象, 存文件和取大文件之类可以直接调用.
post '/a/b' = sub {
my $self = shift;
my $upload = $self-req-upload('args_file');
my $foo = $self-param('args');
$self-render(text = "Hello from $foo.");
}
3. POST 的 body 的内容
最后一种, 是有时我们做大文件上传, 和提交内容之类.这个时候, 整个 body 都是文件本体. 参数象 get 一样通过 url 传过去.
这个就不用抓头了, 没任何转换, 直接整个 body 是个大文件.
客户端提交:
我们来看看客户端在这个时候怎么上传送. 同样, 我们使用 Mojo::UserAgent 为例子.
my $ua = Mojo::UserAgent-new;
$ua-transactor-add_generator(stream = sub {
my ($transactor, $tx, $path) = @_;
$tx-req-content-asset(Mojo::Asset::File-new(path = $path));
});
$ua-post('ht tp://w ww.php-oa.co m/a/b' = stream = '/root/.bash_history' );
服务器接收
这个时候, 在服务器端怎么接收啦?
post '/a/b' = sub {
my $self = shift;
my $body = $self-req-body;
my $foo = $self-param('args');
$self-render(text = "Hello from $foo.");
}
这个, 我们直接取请求的 body 就可以了, 但这有个小问题, 这是这个文件上传完, 这个 body 会存着所有的文件, 比如这个上传的文件有 1G , 这个 1G 就都会占着内存. 这个情况, Mojolicious 并没有实现事件来根据块取文件. 晚点, 我有个有于大文件上传的文章, 会分享我在 Mojolicious 中实现异步以块方式存储文件. 这样用户上传多少, 我存多少, 并不会占用更多的内存.
好了整个三种方式都介绍完了, 大家一定注意区分哦 .
get请求一般是在你请求的地址后边加上?par=''par2=''例如请求的页面是a.do(假设你后台是java)那么请求地址是这样的a.do?par='123'par2='456'这个请求传递的参数就par和par2他俩的值分别为123,456
import java.io.*; import jxl.*; … … … … try { //构建Workbook对象, 只读Workbook对象 //直接从本地文件创建Workbook //从输入流创建Workbook InputStream is = new FileInputStream(sourcefile); jxl.Workbook rwb = Workbook.getWorkbook(is); } catch (Exception e) { e.printStackTrace(); } 一旦创建了Workbook,我们就可以通过它来访问Excel Sheet(术语:工作表)。参考下面的代码片段: //获取第一张Sheet表 Sheet rs = rwb.getSheet(0); 我们既可能通过Sheet的名称来访问它,也可以通过下标来访问它。如果通过下标来访问的话,要注意的一点是下标从0开始,就像数组一样。 一旦得到了Sheet,我们就可以通过它来访问Excel Cell(术语:单元格)。参考下面的代码片段: //获取第一行,第一列的值 Cell c00 = rs.getCell(0, 0); String strc00 = c00.getContents(); //获取第一行,第二列的值 Cell c10 = rs.getCell(1, 0); String strc10 = c10.getContents(); //获取第二行,第二列的值 Cell c11 = rs.getCell(1, 1); String strc11 = c11.getContents(); System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType()); System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType()); System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType()); 如果仅仅是取得Cell的值,我们可以方便地通过getContents()方法,它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型,Cell(1, 0)是数字型,Cell(1,1)是日期型,通过getContents(),三种类型的返回值都是字符型。 如果有需要知道Cell内容的确切类型,API也提供了一系列的方法。参考下面的代码片段: String strc00 = null; double strc10 = 0.00; Date strc11 = null; Cell c00 = rs.getCell(0, 0); Cell c10 = rs.getCell(1, 0); Cell c11 = rs.getCell(1, 1); if(c00.getType() == CellType.LABEL) { LabelCell labelc00 = (LabelCell)c00; strc00 = labelc00.getString(); } if(c10.getType() == CellType.NUMBER) { NmberCell numc10 = (NumberCell)c10; strc10 = numc10.getValue(); } if(c11.getType() == CellType.DATE) { DateCell datec11 = (DateCell)c11; strc11 = datec11.getDate(); } System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType()); System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType()); System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType()); 在得到Cell对象后,通过getType()方法可以获得该单元格的类型,然后与API提供的基本类型相匹配,强制转换成相应的类型,最后调用相应的取值方法getXXX(),就可以得到确定类型的值。API提供了以下基本类型,与Excel的数据格式相对应,如下图所示: 每种类型的具体意义,请参见Java Excel API Document。 当你完成对Excel电子表格数据的处理后,一定要使用close()方法来关闭先前创建的对象,以释放读取数据表的过程中所占用的内存空间,在读取大量数据时显得尤为重要。参考如下代码片段: //操作完成时,关闭对象,释放占用的内存空间 rwb.close(); Java Excel API提供了许多访问Excel数据表的方法,在这里我只简要地介绍几个常用的方法,其它的方法请参考附录中的Java Excel API Document。 • Workbook类提供的方法 1. int getNumberOfSheets() 获得工作薄(Workbook)中工作表(Sheet)的个数,示例: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); int sheets = rwb.getNumberOfSheets(); 2. Sheet[] getSheets() 返回工作薄(Workbook)中工作表(Sheet)对象数组,示例: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); Sheet[] sheets = rwb.getSheets(); 3. String getVersion() 返回正在使用的API的版本号,好像是没什么太大的作用。 jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); String apiVersion = rwb.getVersion(); • Sheet接口提供的方法 1. String getName() 获取Sheet的名称,示例: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); jxl.Sheet rs = rwb.getSheet(0); String sheetName = rs.getName(); 2. int getColumns() 获取Sheet表中所包含的总列数,示例: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); jxl.Sheet rs = rwb.getSheet(0); int rsColumns = rs.getColumns(); 3. Cell[] getColumn(int column) 获取某一列的所有单元格,返回的是单元格对象数组,示例: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); jxl.Sheet rs = rwb.getSheet(0); Cell[] cell = rs.getColumn(0); 4. int getRows() 获取Sheet表中所包含的总行数,示例: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); jxl.Sheet rs = rwb.getSheet(0); int rsRows = rs.getRows(); 5. Cell[] getRow(int row) 获取某一行的所有单元格,返回的是单元格对象数组,示例子: jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); jxl.Sheet rs = rwb.getSheet(0); Cell[] cell = rs.getRow(0); 6. Cell getCell(int column, int row) 获取指定单元格的对象引用,需要注意的是它的两个参数,第一个是列数,第二个是行数,这与通常的行、列组合有些不同。 jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile)); jxl.Sheet rs = rwb.getSheet(0); Cell cell = rs.getCell(0, 0); 生成新的Excel工作薄 下面的代码主要是向大家介绍如何生成简单的Excel工作表,在这里单元格的内容是不带任何修饰的(如:字体,颜色等等),所有的内容都作为字符串写入。(完整代码见ExcelWriting.java) 与读取Excel工作表相似,首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象,这里要注意的是,只能通过API提供的工厂方法来创建Workbook,而不能使用WritableWorkbook的构造函数,因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下: import java.io.*; import jxl.*; import jxl.write.*; … … … … try { //构建Workbook对象, 只读Workbook对象 //Method 1:创建可写入的Excel工作薄 jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile)); //Method 2:将WritableWorkbook直接写入到输出流 /* OutputStream os = new FileOutputStream(targetfile); jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os); */ } catch (Exception e) { e.printStackTrace(); } API提供了两种方式来处理可写入的输出流,一种是直接生成本地文件,如果文件名不带全路径的话,缺省的文件会定位在当前目录,如果文件名带有全路径的话,则生成的Excel文件则会定位在相应的目录;另外一种是将Excel对象直接写入到输出流,例如:用户通过浏览器来访问Web服务器,如果HTTP头设置正确的话,浏览器自动调用客户端的Excel应用程序,来显示动态生成的Excel电子表格。 接下来就是要创建工作表,创建工作表的方法与创建工作薄的方法几乎一样,同样是通过工厂模式方法获得相应的对象,该方法需要两个参数,一个是工作表的名称,另一个是工作表在工作薄中的位置,参考下面的代码片段: //创建Excel工作表 jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0); "这锅也支好了,材料也准备齐全了,可以开始下锅了!",现在要做的只是实例化API所提供的Excel基本数据类型,并将它们添加到工作表中就可以了,参考下面的代码片段: //1.添加Label对象 jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell"); ws.addCell(labelC); //添加带有字型Formatting的对象 jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true); jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf); jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF); ws.addCell(labelCF); //添加带有字体颜色Formatting的对象 jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.RED); jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC); ws.addCell(labelCF); //2.添加Number对象 jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926); ws.addCell(labelN); //添加带有formatting的Number对象 jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##"); jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf); jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN); ws.addCell(labelNF); //3.添加Boolean对象 jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false); ws.addCell(labelB); //4.添加DateTime对象 jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date()); ws.addCell(labelDT); //添加带有formatting的DateFormat对象 jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss"); jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df); jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF); ws.addCell(labelDTF); 这里有两点大家要引起大家的注意。第一点,在构造单元格时,单元格在工作表中的位置就已经确定了。一旦创建后,单元格的位置是不能够变更的,尽管单元格的内容是可以改变的。第二点,单元格的定位是按照下面这样的规律(column, row),而且下标都是从0开始,例如,A1被存储在(0, 0),B1被存储在(1, 0)。 最后,不要忘记关闭打开的Excel工作薄对象,以释放占用的内存,参见下面的代码片段: //写入Exel工作表 wwb.write(); //关闭Excel工作薄对象 wwb.close(); 这可能与读取Excel文件的操作有少少不同,在关闭Excel对象之前,你必须要先调用write()方法,因为先前的操作都是存储在缓存中的,所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象,那么只能得到一张空的工作薄了。 拷贝、更新Excel工作薄 接下来简要介绍一下如何更新一个已经存在的工作薄,主要是下面二步操作,第一步是构造只读的Excel工作薄,第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄,参考下面的代码片段:(完整代码见ExcelModifying.java) //创建只读的Excel工作薄的对象 jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile)); //创建可写入的Excel工作薄对象 jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile), rw); //读取第一张工作表 jxl.write.WritableSheet ws = wwb.getSheet(0); //获得第一个单元格对象 jxl.write.WritableCell wc = ws.getWritableCell(0, 0); //判断单元格的类型, 做出相应的转化 if(wc.getType() == CellType.LABEL) { Label l = (Label)wc; l.setString("The value has been modified."); } //写入Excel对象 wwb.write(); //关闭可写入的Excel对象 wwb.close(); //关闭只读的Excel对象 rw.close(); 之所以使用这种方式构建Excel对象,完全是因为效率的原因,因为上面的示例才是API的主要应用。为了提高性能,在读取工作表时,与数据相关的一些输出信息,所有的格式信息,如:字体、颜色等等,是不被处理的,因为我们的目的是获得行数据的值,既使没有了修饰,也不会对行数据的值产生什么影响。唯一的不利之处就是,在内存中会同时保存两个同样的工作表,这样当工作表体积比较大时,会占用相当大的内存,但现在好像内存的大小并不是什么关键因素了。 一旦获得了可写入的工作表对象,我们就可以对单元格对象进行更新的操作了,在这里我们不必调用API提供的add()方法,因为单元格已经于工作表当中,所以我们只需要调用相应的setXXX()方法,就可以完成更新的操作了。 尽单元格原有的格式化修饰是不能去掉的,我们还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现。 新生成的工作表对象是可写入的,我们除了更新原有的单元格外,还可以添加新的单元格到工作表中,这与示例2的操作是完全一样的。 最后,不要忘记调用write()方法,将更新的内容写入到文件中,然后关闭工作薄对象,这里有两个工作薄对象要关闭,一个是只读的,另外一个是可写入的。 小结 本文只是对Java Excel API中常用的方法作了介绍,要想更详尽地了解API,请大家参考API文档,或源代码。Java Excel API是一个开放源码项目,请大家关注它的最新进展,有兴趣的朋友也可以申请加入这个项目,或者是提出宝贵的意见。
于HTTP 协议的多线程下载和断点续传的实现 学 生:叶升路 指导教师:覃 颖 (三峡大学 电气信息学院) 摘 要:本文介绍了网络下载软件中的最新技术——多线程下载和断点续传技术,同时 也介绍了HTTP 协议的发展、特点以及WinSock 编程技术。最后在这些技术的基础上成 功设计并实现了基于HTTP 协议的具有多线程下载和断点续传功能的下载软件。本软件 的实现代码未使用任何WinInet API 函数如InternetOpen , InternetConnect 等,而是直接 使用WinSock 编程,逐步解析HTTP 协议来完成会话和文件下载等功能。经测试,下载 速度有所提高。 关键词:下载;多线程;断点续传;HTTP;WinSock; Abstract:This paper introduces the latest downloading technology called multi-threaded downloading and resume in network downloading software. But also descripes the development of HTTP protocol, characteristics and WinSock programming. Finally, based on these technologies successfully designed and implemented a downloading tool based on the HTTP protocol with multi-threaded and resume features. The realization of the software code does not use any WinInet API functions such as InternetOpen, InternetConnect, etc., but directly use WinSock to programming, and complete the functions of conversation and file downloads and others by parse HTTP protocol steply. After tested, the speed of downloading has increased. Keywords: Downloading;Multi-thread;Resume;HTTP;WinSock; 前言 最近几年,随着计算机网络的飞速发展,因特网(Internet)已经逐渐成为人们生活、 工作、学习必不可缺的一部分。因特网上存储了大量丰富的信息资源,我们可以使用下载 工具,把需要的信息资源下载到本地。但是由于受到各种因素的限制,例如服务器性能、 网络带宽、下载的信息量以及下载工具等等,下载速度受到不同程度的影响。因此人们不 断地提高服务器性能,扩展网络带宽,开发效率更高的下载工具以达到最大化提高下载 速度的目的。 在限制下载速度的众多因素中,研究新的网络下载技术开发出更高效的下载工具无 疑是其中最节约,环保以及方便的方式。网络下载技术,也可以称为网络文件共享技术, 10 1 它一直是网络发展的重要推动力之一。早期人们共享资源的普遍方法是将资源文件上传 至服务器上,然后其他用户可以通过HTTP 或FTP 等协议将其下载到本地电脑。这种模 式称为客户机/服务器模式即C/S 模式,它对服务器的依赖性很大,当下载用户很少时, 比如说一个,他将独享服务器的带宽,很显然其下载速度会非常快。然而当下载的人数 较多而服务器带宽有限时,比如服务器带宽为3MB/S,而下载人数为100 人,则众多下 载用户不得不共享一个带宽(3MB/S)最终结果是下载速度均分(30KB/S),普遍不高。 P2P 技术的出现使得人们终于摆脱了服务器的枷锁。它的主要特点是资源分散、负 载均衡、和非中心化,它将共享的文件存储在各个客户机节点上,用户之间可以直接共 享和传输文件而不需要通过服务器。客户机不再只利用服务器带宽进行下载,它同时也 可以利用其他客户机节点的带宽,这样大大提高了下载速度。 纵观网络下载技术发展的历史,可以将其划分为四个阶段:单线程下载阶段、多线 程下载及断点续传阶段、P2P阶段、P2SP阶段。 一、单线下载时代:应对有限时间流量的办法 早在上个世纪90年代,当时互联网并不普及,很多人使用Modem拨号,通过Telnet 软件连接到拨接式BBS上获取资讯并与别人交流(收发邮件等),由于服务器的电话线 路数量有限,因此都会限制连接时间,一般新注册用户只有10分钟左右。这点时间用来 看帖回帖显然不够的,因此有人就开发了软件,进入BBS后,能够将整个BBS上所有内 容都下载回来,然后可以断线慢慢看慢慢回,最后再次拨入BBS上传回复。 二、断点续传与多线程下载时代:大幅度提高速度 进入Windows与WWW(World Wide Web,互联网)时代之后,IE,Netscape等浏览 器都可以通过点击左键下载,那个时候网络速度最快不过5KB/s,下载一首5MB的MP3 歌曲要15分钟以上!中途万一断线就前功尽弃,于是有人开发了支持断点续传的下载软 件。 世界上第一款支持断点续传的下载软件应该是GetRight。它可让你用浏览器下载文 件时有续传功能,可设定时间来下载文件或是中断Modem拨接,下载完毕时自动中断 Modem拨接或关机。 为了更好的利用带宽,在断点续传的基础上,多线程下载软件逐渐发展了起来。最 早出现的多线程下载软件是中国人开发的NetAnts(网络蚂蚁)。网络蚂蚁其实也是一 个断点续传软件,但它对断点续传功能进行了扩展:可进行多点续传,即利用断点续传 的原理同时建立多个连接下载同一个软件并最终将其合并为一个完整的软件。
能,但是要通过相应的非HTML脚本服务(比如ASP,PHP,CGI等等)
ASP,PHP,CGI是服务器端的代码解释器
不但要服务器端有这个解释器,而且站点的页面里也要有相应功能的代码
要更详细的话你恐怕真的要去学习一下ASP,PHP,CGI,JSP,XML等等比HTML更高级的脚本语言
售后响应及时
7×24小时客服热线数据备份
更安全、更高效、更稳定价格公道精准
项目经理精准报价不弄虚作假合作无风险
重合同讲信誉,无效全额退款