web多线程.docx
《web多线程.docx》由会员分享,可在线阅读,更多相关《web多线程.docx(14页珍藏版)》请在冰豆网上搜索。
web多线程
多线程Web服务器的实现(Java)
一、问题描述
设计要求:
1.客户端通过浏览器访问Web服务器。
2.Web服务器可以并发地服务多个客户。
3.WebServer可以处理客户端浏览器通过GET+URL方法传来的参数,根据参数生成相应的Web页面,并传递到客户端浏览器。
3.1定义通过URL传递参数的规则(至少包含三种不同的参数);
3.2定义参数格式和处理流程;
3.3根据参数创建Web页面(HTML)。
4.Web服务器必须对出现的问题或错误创建相应的响应消息(WebPage),以对用户进行提示。
二、基本要求
1.熟悉HTTP/HTML规范;
2.熟练运用Eclipse开发环境;
3.掌握Java多线程机制;
4.理解Web服务器工作原理;
5.掌握JavaSocket程序设计;
三、设计思想
服务器和客户机通过http协议进行通信,通过socket服务器时刻监听客户端发送的请求,然后为每个请求创建一个线程。
1.http协议
HTTP(TheHypertextTransferProtocol)是请求---响应协议,客户端发送请求,与服务器建立可靠的tcp连接,服务器通过此连接将响应结果发生给客户端。
一个HTTP请求包含三个部分:
Method-URI-Protocol/Version-------方法-地址-版本
Requestheader-------请求头
Entitybody------请求实体
其中GET是请求的类型,URI完整地指定了Internet资源。
一个URI通常被解析为相对服务器的根目录。
这样,它应该总是以一个'/'前缀开始,一个URL实际上是URI的一种类型。
Version指的是该HTTP请求所用到的HTTP协议版本。
请求头包含了客户端环境与请求实体的一些有用的信息。
例如它包含浏览器设定的语言、实体的长度等等。
每条请求头用回车换行符(CRLF)分开。
HTTP响应
与请求相似,HTTP响应也由三部分组成:
Protocol-Statuscode-Description-------协议状态描述代码
Responseheaders------响应头
Entitybody------响应实体
响应头的第一行类似请求头的第一行,显示协议版本是HTTP1.1,请求成功(200=success)不成功(404=notfonunt)。
响应的实体是HTML内容。
头与实体之间由回车换行的空行(CRLF)分开。
2.Socket套接字
在Java应用程序中包提供Socket类和ServerSocket类分别用于客户端和服务器端,在服务器和客户端之间建立连接,实现数据交换。
通过套接字建立连接的过程分为以下3个步骤:
(1)服务器建立ServerSocket对象,负责监听并接收客户端请求。
(2)客户端创建一个Socket对象,向服务器发送请求试图与之建立连接。
(3)服务器接收到客户端请求,为它创建一个单独的进程来处理请求,
创建套接字InputStream和OutputStream流对象,套接字输入、输出流完全可以连接作为一个I/O流对象来对待。
在使用套接字编写客户机/服务器应用程序时,建立客户机和服务器两端相互通信的过程是一样的,该过程的主要工作可归纳为以下4个方面:
打开到套接字的输入、输出流。
根据服务器协议读写数据。
关闭套接字。
3.程序编写:
(1)调用ServerSocket(intport)创建一个服务器端套接字,并前台输入端口号;
(2)监听连接请求,如果客户端有请求连接,调用accept()方法,接受连接,返回通信套接字;
(3)调用Socket类的getOutputStream()和getInputStream()获取输出流和输入流,来响应请求;
(4)最后关闭流和通信套接字。
四、系统结构
整个程序设计结构包括2个java类
(1)WebServer11类实现监听并接受请求
(2)和HttpRequest11处理请求并处理,产生响应的结果
五、程序流程(或模块划分)
根据HTTP协议的作用原理,Socket编程,Web服务器的工作原理,实现GET请求的Web服务器程序的流程如下:
(1)创建ServerSocket类对象,监听端口9090。
这是为了区别于HTTP的标准TCP/IP端口80而取的特定端口;
(2)等待、接受客户机连接到端口9090,得到与客户机连接的socket;
(3)创建一个HttpRequest对象,将标征着所建立TCP连接的Socket作为参数传递到它的构造函数中
(4)为了让HttpRequest对象在一个单独的线程中处理随后的HTTP请求,我们首先创建一个Thread对象,将HttpRequest对象作为参数传递给Thread的构造函数,然后调用Thread的start()方法启动线程
(5)HttpRequest必须实现Runnable接口。
因此,必须定义HttpRequest的public方法run(),实现多线程
(6)利用类StringTokenizer从Request行中解析出文件名字。
(7)从请求信息中获得参数的值,使用ProcessRequest处理请求方法进行参数处理
(8)经过处理请求信息将得到一个html的文件名,如果该文件存在,则打开文件,把HTTP头信息和文件内容通过socket传回给Web浏览器,然后关闭文件。
否则向服务器发送错误信息;
(9)关联的输入流和输出流,关闭socket
六、源程序
MultiThreadWebServer.java:
创建服务器套接字,侦听客户端请求,为每个请求创建一个线程
/**************************************************************
*MultiThreadedWebServer的启动采用如下形式:
*
*javaMultiThreadedWebServerport_number*
*port_number为一整数,(1024<=port_number<=65535)*
*在客户端浏览器地址栏中输入如下形式地址访问服务器:
*
*http:
//localhost:
port_number/textbook.jpg*
***************************************************************/
import.*;
publicfinalclassMultiThreadedWebServer{
publicstaticvoidmain(Stringargv[])throwsException{
//从命令行中获得WebServer的监听端口号
//intport=(newInteger(argv[0])).intValue();
//todo:
添加代码以创建监听Socket
ServerSocketsocket=newServerSocket(8989);
while(true){
//ListenforaTCPconnectionrequest.
Socketconnection=socket.accept();
//创建对象,处理收到的HttpRequest消息
HttpRequestrequest=newHttpRequest(connection);
//创建线程,不同的消息用不同的线程进行处理
Threadthread=newThread(request);
//启动线程.
thread.start();
}
}
}
HttpRequest.java实现了Runnable接口,对请求进行处理并做出响应
/*
1.通过实现Runnable接口实现多线程
2.网络相关处理机制包含在包中
3.HttpRequest类中使用的某些对象包含在java.util包中
4.将收到的HTTPrequest消息作为参数来创建HttpRequest类的实例
5.线程一启动将处理完该Request消息
*/
/*需要添加代码的地方用//todo:
标注*/
importjava.io.*;
import.*;
importjava.util.*;
finalclassHttpRequestimplementsRunnable{
//定义回车换行符
finalstaticStringCRLF="\r\n";
Socketsocket;
//类HttpRequest的构造函数,用于对socket进行初始化.
publicHttpRequest(Socketsocket)throwsException{
//todo:
添加一行代码,以便初始化socket
this.socket=socket;
}
//必须实现Runnable接口中的run()方法,实现对HTTP协议Request消息的处理
publicvoidrun(){
try{
//利用方法processRequest实现对Request消息的处理
processRequest();
}catch(Exceptione){
System.out.println(e);
}
}
privatevoidprocessRequest()throwsException{
//todo:
创建socket所关联的输入流is和输出流os
InputStreamReaderis=newInputStreamReader(socket.getInputStream());
DataOutputStreamos=newDataOutputStream(socket.getOutputStream());
BufferedReaderbr=newBufferedReader(is);
//todo:
添加一行代码,获取所收到HTTPRequest消息的RequestLine
StringrequestLine;
requestLine=br.readLine();
System.out.println();
System.out.println(requestLine);
//Request消息的Requestline中包含:
MethodURLVersion三个字段.利用StringTokenizer类简化读取字段的操作
StringTokenizertokens=newStringTokenizer(requestLine);
//跳过Method字段,因为它应该是GET
tokens.nextToken();
//获取头部行中的URL字段值
StringfileName=tokens.nextToken();
//todo:
在URL标征的文件名前添加".",因为所请求的对象在当前目录下(即该Web服务器的主目录).
fileName="."+fileName;
//打开请求的文件.
FileInputStreamfis=null;
booleanfileExists=true;
try{
fis=newFileInputStream(fileName);
}catch(FileNotFoundExceptione){
fileExists=false;
}
System.out.println("到达了一个Request消息!
!
!
");
//todo:
添加用于调试的信息,以便在控制台输出HTTPRequest消息全部headerline的内容
StringheaderLine=null;
while((headerLine=br.readLine()).length()!
=0){
System.out.println(headerLine);
}
//创建Reponse消息.
StringstatusLine=null;//状态行
StringcontentTypeLine=null;//Content-Type行
StringentityBody=null;//Entitybody部分
if(fileExists){
//如果请求的文件存在
//todo:
创建Reponse消息的Status行和Content-Type行
statusLine="HTTP/1.1200OK";
contentTypeLine="Content-type:
"+contentType(fileName)+CRLF;
}else{
//如果文件不存在
//todo:
创建Reponse消息的Status行和Content-Type行
statusLine="HTTP/1.1404NotFound"+CRLF;
contentTypeLine="text/html";
//todo:
利用HTML创建Web页面,提示用户所请求的文件不存在.Web页面作为Response消息的entitybody部分
entityBody=""+
"
不存在"+
"
请求文件不在服务器上";
}
//todo:
发送Reponse消息的头部行.
os.writeBytes(statusLine);
os.writeBytes(contentTypeLine);
os.writeBytes(CRLF);
//发送Reponse消息的entitybody部分(响应消息内容).
if(fileExists){
//调用后面所编写的sendBytes方法,实现文件数据的发送
sendBytes(fis,os);
fis.close();
}else{
//todo:
如果请求的文件不存在,直接发送前面创建的,用于提示用户文件不存在的Web页面
os.writeBytes(entityBody);
}
//todo:
关闭所使用的流和Socket.
os.close();
br.close();
socket.close();
}
privatestaticvoidsendBytes(FileInputStreamfis,OutputStreamos)throwsException{
//todo:
创建buffer,将请求的文件数据读入buffer,然后通过输出流os发送到客户端
byte[]buffer=newbyte[1024];
intbytes=0;
while((bytes=fis.read(buffer))!
=-1){
os.write(buffer,0,bytes);
}
}
//contentType方法用于根据文件的扩展名,确定Content-Type头部行
privatestaticStringcontentType(StringfileName){
//todo:
根据文件的扩展名,返回填写在conten-type中的内容
if(fileName.endsWith(".htm")||fileName.endsWith(".html")){
return"text/html";
}
if(fileName.endsWith(".jpg")){
return"image/jpeg";
}
if(fileName.endsWith(".gif")){
return"image/gif";
}
return"application/octet-stream";
}
}
此外还有textbook,Topology,Topology2,text11等文件
七、测试数据
1.输入服务器的端口号:
8989
2.打开浏览器在地址栏输入:
http:
//localhost:
8989/filename
八、测试情况
1.在地址栏输入http:
//localhost:
8989/yizi.jpg
2、在地址栏输入http:
//localhost:
8989/text.txt
3.在地址栏输入http:
//localhost:
8989/1.jpg
4.在地址栏输入http:
//localhost:
8989/haokan.jpg
结论
设计中遇到的问题如何实现服务器的多线程,如何使客户端和服务器实现通信,及响应结果如何发送给客户端。
1.Web服务器将是多线程的,所收到的每个Request消息将交由单独的线程进行处理。
这使得服务器可以并发地为多个客户服务,或者是并发地服务于一个客户的多个请求.当创建一个新线程时,需要向线程的构造函数传递实现了Runnable接口的类的一个实例(即通过实现接口Runnable来实现多线程)。
这正是我们定义单独的类HttpRequest的原因。
创建监听端口以等待TCP连接请求。
由于Web服务器将不间断地提供服务,我们将侦听操作放在一个无穷循环的循环体中。
这意味着需要通过在键盘上输入^C来结束Web服务器的运行。
2.为了将类HttpRequest的实例作为参数传输传递到Thread的构造函数中,HttpRequest必须实现Runnable接口。
因此,必须定义HttpRequest的public方法run(),其返回值类型为void。
我们在run()中调用实现Request消息处理绝大部分操作的方法processRequest()。
3.由于之前对HTTP的协议格式不熟悉,在发送HTTP响应消息实体时出现问题:
显示页面与发送的页面有出入,请教了马老师后,发现了自己的错误:
每个头信息都是一行,“Content-Length”头信息是必需的,它表示要读取多长的实体,而且头信息和实体之间有一个空行,这些都正确后才能看到想要的页面效果。
4.运行时发现无法显示请求存在的文件,经过请教指导老师,调试发现使输入输出量方面使用错误,还有相关的方法不知具体如何应用。
经过课程设计发现自己的知识掌握的还不牢固,应用能力需要加强。
Java编程要经常复习和练习。
在计算机网络方面,两台机器如何实现通信,socket及http具体的功能和作用,
参考文献
1、RFC2616-HypertextTransferProtocol--HTTP/1.1
2、RFC1866-HypertextMarkupLanguage-2.0
3、RFC2854-The'text/html'MediaType
4、《Java实例入门》刘勇,中国青年出版社,2002-01-01
5、《Java程序设计与案例》刘宝林;高等教育出版社
6、JavaTCP/IPSocket编程(原书第2版)机械工业,(美)卡尔弗特,周恒民译