Programming Assignment 1: Building a
Multi-Threaded Web Server
本试验中我们将通过两个阶段来开发一个web服务器,最后完成一个能够并行服务与多个请求的多线程Web服务器。
我们将实现在RFC 1945定义的HTTP1.0。根据定义,每个Web page中的对象将通过单独的HTTP 消息来获取。所实现Web服务器将能够并发地服务于多个请求,这意味着Web服务器是多线程的。Web服务器的主线程负责侦听某个端口,当收到TCP连接请求时,将创建一个新的socket负责与该TCP连接,并创建新的线程具体负责通过该连接的消息传递。为了简化程序设计任务,我们分两阶段来设计Web服务器。第一阶段:编写仅仅显示所收到HTTP Request消息所有头部行的一个多线程Web服务器。当该程序运行正确后,将添加适当的代码以实现对Request消息的适当响应。
开发Web服务器时,可以通过Web浏览器来测试它。不过,所编写的Web服务器通常并不工作于80端口,因此,测试时在浏览器的地址栏中需要指定Web服务器的工作端口。例如:假设Web服务器运行在域名为https://www.wendangku.net/doc/dc12514870.html,的主机上,监听端口6789,我们想获取文件index.html。需要在浏览器的地址栏中输入如下的URL:
https://www.wendangku.net/doc/dc12514870.html,:6789/index.html
如果忽略了":6789", 浏览器则默认地认为Web服务器监听80端口。
当Web服务器遇到问题,将向浏览器发送包含适当响应消息的HTML页面,以便在浏览器中显示错误信息。[由谁负责生成错误响应消息HTML页面。。。,由本Web服务器。]
Web Server in Java: Part A
下面,我们将实现第一阶段的编程任务。当看到"?"时,你需要在该处添加相应的代码。
我们的第一个Web服务器将是多线程的,所收到的每个Request消息将交由单独的线程进行处理。这使得服务器可以并发地为多个客户服务, 或者是并发地服务于一个客户的多个请求.当创建一个新线程时,需要向线程的构造函数传递实现了Runnable 接口的类的一个实例(即通过实现接口Runnable来实现多线程)。这正是我们定义单独的类HttpRequest的原因。
Web服务器的结构如下:
import java.io.* ;
import https://www.wendangku.net/doc/dc12514870.html,.* ;
import java.util.* ;
public final class WebServer
{
public static void main(String argv[]) throws Exception
{
. . .
}
}
final class HttpRequest implements Runnable
{
. . .
}
通常,Web服务器为通过周知端口80收到的请求提供服务。你可以选择大于1024的任意端口作为Web服务器的监听端口,但需要记着在浏览器地址栏中输入URL时指定Web服务器的动作端口。
public static void main(String argv[]) throws Exception
{
// Set the port number.
int port = 6789;
. . .
}
下面,创建监听端口以等待TCP连接请求。由于Web服务器将不间断地提供服务,我们将侦听操作放在一个无穷循环的循环体中。这意味着需要通过在键盘上输入^C来结束Web服务器的运行。
// Establish the listen socket.
? ////ServerSocket socket = new ServerSocket(port);
// Process HTTP service requests in an infinite loop.
while (true) {
// Listen for a TCP connection request.
? ////Socket connection = socket.accept();
. . .
}
当收到请求后,我们创建一个HttpRequest对象,将标征着所建立TCP连接的Socket作为参数传递到它的构造函数中。
// Construct an object to process the HTTP request message.
HttpRequest request = new HttpRequest( ? ////connection);
// Create a new thread to process the request.
Thread thread = new Thread(request);
// Start the thread.
thread.start();
为了让HttpRequest对象在一个单独的线程中处理随后的HTTP请求,我们首先创建一个Thread对象,将HttpRequest对象作为参数传递给Thread的构造函数,然后调用Thread的start()方法启动线程。
当一个Thread创建并启动后,主线程回到了循环体的首部。主线程将被阻塞block在accept处,以等待另一个TCP 连接请求的到达。此时,刚刚创建的线程正在运行。当另一个TCP连接请求到达时,主线程将不管前面创建的线程是否结束,重复上面的操作,创建新线程负责新连接的请求处理。
到这为止,主线程的工作就完成了,后面我们将集中精力设计类HttpRequest。
我们声明HttpRequest类中的两个变量:CRLF and socket。根据HTTP规范, 我们需要用”回车换行”作为Response消息头部行的结束。因此,为了使用方便,我们定义了一个CRLR字符串变量。变量socket用作connection socket, 它将被类HttpRequest的构造函数初始化。
////不需要是其他类或其他类的子类吗?////也有说是从其他类继承并使用runnable接口。
final class HttpRequest implements Runnable
{
final static String CRLF = "\r\n";
Socket socket;
// Constructor ////HttpRequest的构造函数
public HttpRequest(Socket socket) throws Exception
{
////将参数socket即Socket connection赋给当前对象的(this)成员变量socket即this.socket
this.socket = socket;
}
// Implement the run() method of the Runnable interface.
public void run()
{
. . .
}
private void processRequest() throws Exception
{
. . .
}
}
为了将类HttpRequest的实例作为参数传输传递到Thread的构造函数中,(////用实现了Runnable接口的类的对象中所定义的run()方法, 来覆盖新创建的线程对象的run()方法
)HttpRequest必须实现Runnable接口。因此,必须定义HttpRequest的public方法run(),其返回值类型为void。我们在run()中调用实现Request消息处理绝大部分操作的方法processRequest()。
直到现在,我们其实一直在抛出异常, 而不是catching他们。不过,我们不能从方法run()中抛出异常,因为我们必须严格遵守Runnable接口对run()的声明。Runnable接口的run()方法不抛出任何异常。我们将在processRequest中放置处理代码,并从此在run方法中利用try/catch块处理异常。
// Implement the run() method of the Runnable interface.
public void run()
{
try {
processRequest();
} catch (Exception e) {
System.out.println(e);
}
}
现在,设计processRequest()中的代码。首先获得socket的输入/出流的reference引用。然后,我们给input stream包装过滤器(filters)。但是,输出流无须包装任何过滤器,主要原因是我们将向输出流直接写入bytes。
private void processRequest() throws Exception
{
// Get a reference to the socket's input and output streams.
InputStream is = ?; ////socket.getInputStream();
DataOutputStream os = ?;//// socket.getOutputStream();
// Set up input stream filters.
BufferedReader br = ?; ////new BufferedReader(new InputStreamReader(is));
. . .
}
现在我们已经准备好来获得客户发来的HTTP Request消息了(通过从socket的输入流读取消息)。类BufferedReader的方法readLine()方法将从输入流中读取字符,直到遇到CRLF为止(也就是从input stream中读取一行,行的结束符为CRLF)。
从input stream中读出的第一行为HTTP Request消息的请求行(参看教材的2.2 部分,了解请求行的定义)。
// Get the request line of the HTTP request message.
String requestLine = ?; ////br.readLine();
// Display the request line.
System.out.println();
System.out.println(requestLine);
读取消息的请求行后,读取消息的其它头部行。由于我们并不知道客户发送消息中有多少头部行,必须利用一个循环操作来获取Request消息的所有头部行。
// Get and display the header lines.
String headerLine = null;
while ((headerLine = br.readLine()).length() != 0) {
System.out.println(headerLine);
}
由于除了需要将头部行中的内容显示在屏幕上外,现阶段无须针对头部行做其它的处理,我们仅仅利用临时变量headerLine来保存头部行的信息。循环操作直到下面的表达式值等于0时停止。也就是读取的头部行的长度如果为零,表示读出了一个空行,意味着所有的头部行已经全部读出(参看教材的2.2 部分,头部行和entity body之间利用一个空行作为分割)。
(headerLine = br.readLine()).length()
后面我们将添加分析客户Request消息的代码,并发送Response消息。在进行后面的程序设计前,我们先完成第一阶段的任务,并通过浏览器来测试它。添加如下代码以关闭输入/出流和connection socket。
// Close streams and socket.
os.close();
br.close();
socket.close();
当程序编译成功后,以适当的端口作为参数运行Web服务器,并利用浏览器访问它。在浏览器地址栏中输入下面的示例:
https://www.wendangku.net/doc/dc12514870.html,:6789/
Web服务器将显示HTTP Request消息的内容。检查请求消息的格式是否与教材2.2中描述的HTTP Request消息格式相符。[问:即当用户的请求到达服务器后,服务器显示出的请求行各个头部行是否与所学格式一致?]
Web Server in Java: Part B
Web服务器不能仅仅显示收到的Request消息的内容,而是应该分析收到的Request消息并产生适当的Response消息。我们将忽略Request消息头部行中包含的信息,仅仅关注Request消息的
请求行中包含的文件名字。我们将假设客户发送的Request消息中的Request行总是使用GET方法,实际上,一个浏览器可能使用GET、POST和HEAD方法(HTTP1.0)
利用类StringTokenizer从Request行中解析出文件名字。首先,创建一个StringTokenizer 对象来容纳Request行;第二步:跳过Method字段(因为总是GET方法);第三步,解析出文件名字。
// Extract the filename from the request line.
StringTokenizer tokens = new StringTokenizer(requestLine);
tokens.nextToken(); // skip over the method, which should be "GET" String fileName = tokens.nextToken();
// Prepend a "." so that file request is within the current directory. fileName = "." + fileName;
由于浏览器在文件名字前加了一个“/“,我们在它前面加上一个字符”.”,从而限定从当前目录开始获取文件。
现在有了客户请求的文件名字,我们可以打开该文件作为向客户发送该文件的第一步。如果文件不存在,构造函数FileInputStream()将抛出异常FileNotFoundException,为了在抛出此可能的异常后不终止线程的执行,利用一个try/catch块将布尔型变量fileExists设置为false。后面我们将使用该变量来构建一个错误响应消息,而不是发送一个根本不存在的文件。
// Open the requested file.
FileInputStream fis = null;
boolean fileExists = true;
try {
fis = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
fileExists = false;
}
////显示头部行:String headerLine = null;
////while ((headerLine = br.readLine()).length() != 0){
//// System.out.println(headerLine);}
////自加部分,来自PART A。
Response消息有三部分:the status line, the response headers, 和entity body。状态行、头部行以CRLF作为结束。利用变量statusLine来保存响应消息的statusline、contentTypeLine 保存Content-Type头部行信息。当文件不存在时,Web服务器将返回状态行为“404 Not Found “,entity body中保存利用HTML创建的错误消息????是的,用HTML构建一个消息,作为返回给用户的出错提示,该消息由entity body承载。
// Construct the response message.
String statusLine = null;
String contentTypeLine = null;
String entityBody = null;
if (fileExists) {
statusLine = ?//// "HTTP/1.1 200 OK" + CRLF;
contentTypeLine = "Content-type: " +
contentType( fileName ) + CRLF;
} else {
statusLine = ?; //// "HTTP/1.1 404 Not Found"+CRLF;
contentTypeLine = ?//// "Content-type: txt/html" + CRLF;
entityBody = "" +
"
"
Not Found";}
当文件存在,需要确定文件的MIME类型和发送适当的MIME-Type指示符,利用private方法contentType()实现该上述任务。该方法将返回包含在Conten-Type头部行的信息(字符串)。
现在,我们可以通过向socket的输出流写入status line 和唯一的一个header line 来向客户浏览器发送信息。
// Send the status line.
os.writeBytes(statusLine);
// Send the content type line.
os.writeBytes(?////contentTypeLine);
// Send a blank line to indicate the end of the header lines.
os.writeBytes(CRLF);
下面需要发送消息的entity body了。如果请求的文件存在,我们调用另一个方法来发送文件;如果请求的文件不存在,我们向客户发送一个HTML编码的错误消息(前面已经准备好,即在变量entityBody中。
// Send the entity body.
if (fileExists) {
sendBytes(fis, os);
fis.close();
} else {
os.writeBytes(?////entityBody);
}
发送完entitybody后,线程的任务已经全部完成,在结束线程前需要关闭流和socket.
我们还需要实现前面提到的两个方法:contentType()和sendBytes()。
private static void sendBytes(FileInputStream fis, OutputStream os) throws Exception
{
// Construct a 1K buffer to hold bytes on their way to the socket.
byte[] buffer = new byte[1024];
int bytes = 0;
// Copy requested file into the socket's output stream.
while((bytes = fis.read(buffer)) != -1 ) {
os.write(buffer, 0, bytes);
}
}
read()和write()均抛出异常,我们在sendBytes中并不处理这些异常,而是将异常处理的任务交给调用sendBytes的方法。
变量buffer,用于作为文件和输出流之间的中间存储空间。当从FileInputStream中读取字节时,,检查读取的字节是否为-1(即文件结束标识EOF)。如果[未???]读到了EOF,read()
返回已经放入buffer的字节数。利用方法类OutputStream的方法write()将保存在buffer 中的字节数据发送到输出流, write的参数buffer、0、bytes分别为byte数组的名字、第一个字节的位置、需要写出的字节数。
Web Server中需要完成最后一部分代码为contentType,实现根据文件的扩展名来确定所代表的MIME 类型。如果文件扩展名未知,则方法返回application/octet-stream.
private static String contentType(String fileName)
{
if(fileName.endsWith(".htm") || fileName.endsWith(".html")) {
return "text/html";
}
if(?) {////fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ?;//// return "image/jpeg";
}
if(?) {////fileName.endsWith(".txt")
?; ////return "text/plain";
}
return "application/octet-stream";
}
到现在未知,我们完成了Web Server的第二阶段任务。尝试从保存有homepage的目录运行Web 服务器,记住在URL中包含Web服务器的工作端口。
一.实验目的 实现学生信息管理系统,学生登录身份验证,信息的录入和信息的查询。并在实验的过程中熟练掌握网页设计的各种工具,如Dreamwawer,tomcat等,提高网页设计的能力。 二.实验过程 1.实现基本页面的设计,使用的工具:Dreamwawer。 2.实现服务器端程序设计,对页面数据进行处理。 3.与数据库进行连接,实现对学生数据的操作,如查询,存储, 修改等。 4.进一步完善系统,如页面的美化等等。 本系统有三个模块组成,学生登录模块,学生信息数据库模块,出错处理模块,学生登录以后输入相应的学号,密码,登陆学生信息界面,队学生的成绩等基本信息进行查询,若学号或密码出错,或者不符合,弹出相应的出错界面。本系统采用Jsp+JavaBean+SQL2000设计方式,其中JavaBean担当数据库连接以及逻辑控制,这样在Jsp就省去了繁琐的数据库连接,以及复杂的逻辑控制,使Jsp成为表示逻辑。 三.运用软件 Windows环境下的Tomcat7.0,SQL2000。 四.过程截图: 1.学生登录界面的设计:
2.与数据库系统的连接:
3.实现对学生数据的操作:
4.对学生数据的保护的完善。 1,验证学生身份: Student: import java.sql.*; public class student {
private String name; private String password; private String id; private String jiguan; private String sex; private String dep; public void setDep(String s){dep=s;} public String getDep(){return dep;} public void setSex(String s){sex=s;} public String getSex(){return sex;} public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { https://www.wendangku.net/doc/dc12514870.html, = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getJiguan() { return jiguan; } public void setJiguan(String jiguan) { this.jiguan = jiguan; }
实验一JSP开发环境构建 实验目的:了解动态页面技术及B/S系统 掌握开发环境的构建 理解Eclipse开发WEB应用 实验内容: 实训项目一:安装JDK并配置环境变量 请阐述配置环境变量的方法: 实训项目二:安装TOMCAT并配置Server.xml修改端口号为8090 问题一:如何测试TOMCAT是否已经成功启动? 问题二:在浏览器地址栏输入什么地址可以访问到TOMCA T的测试页? 请阐述配置Server.xml修改端口号为8090基本实验步骤: 实训项目三:应用Eclipse建立项目并浏览一个JSP页面 请阐述应用Eclipse建立项目并浏览一个JSP页面基本实验步骤: 实验心得:(遇到了哪些问题,如何解决的,有那些体会) 实验二JSP语法 实验目的:了解JSP程序的组成元素 掌握JSP中使用JA V A程序片段的方法 实验内容: 实训项目一:编写一个JSP页面输出26个小写英文字母表 实训项目二:编写页面实现九九乘法表 实训项目三:利用成员变量被所有客户共享这一性质,实现一个简单的计数器 实训项目四:使用JA V A表达式输出系统当前时间 实训项目五:编写程序shijian2_9.jsp和computer.jsp两个页面,在第一个页面中使用include动作标记动态包含文件computer.jsp,并向它传递一个矩形的长和宽,computer.jsp 收到参数后,计算矩形的面积,并显示结果。 实训项目六:编写3个JSP页面:main.jsp,first.jsp和second.jsp,将3个JSP文件保存在同一个WEB工程中,main.jsp使用include动作标记加载first.jsp和second.jsp页面。First.jsp 页面可以画一张表格,second.jsp页面可以计算两个正整数的最大公约数。当first.jsp被加载时,获取main.jsp页面include动作标记的param子标记提供的表格行数和列数,当second.jsp 被加载时,获取main.jsp页面include动作标记的param子标记提供的两个正整数的值。 要求:上机编程完成上述实训项目,上机演示给教师检查,从中挑选三个程序的核心代码写在实训报告上 实验核心代码:
Web程序设计实验报告 姓名:冯刚 学号:200905030324 班级:计科3班
Html代码: 1.首页代码