1、web多线程多线程Web服务器的实现(Java)一、问题描述设计要求:1.客户端通过浏览器访问Web服务器。2.Web服务器可以并发地服务多个客户。3.Web Server可以处理客户端浏览器通过GETURL方法传来的参数,根据参数生成相应的Web页面,并传递到客户端浏览器。 3.1 定义通过URL传递参数的规则(至少包含三种不同的参数); 3.2 定义参数格式和处理流程; 3.3 根据参数创建Web页面(HTML)。4.Web服务器必须对出现的问题或错误创建相应的响应消息(Web Page),以对用户进行提示。二、基本要求1.熟悉HTTP/HTML规范;2.熟练运用Eclipse开发环境;3
2、.掌握Java多线程机制;4.理解Web服务器工作原理;5.掌握Java Socket程序设计;三、设计思想服务器和客户机通过http协议进行通信,通过socket服务器时刻监听客户端发送的请求,然后为每个请求创建一个线程。1. http协议HTTP(The Hypertext Transfer Protocol)是请求-响应协议,客户端发送请求,与服务器建立可靠的tcp连接,服务器通过此连接将响应结果发生给客户端。 一个 HTTP 请求包含三个部分:Method-URI-Protocol/Version-方法-地址-版本Request header-请求头Entity body-请求实体其中
3、GET 是请求的类型,URI 完整地指定了 Internet 资源。一个 URI 通常被解析为相对服务器的根目录。这样,它应该总是以一个 / 前缀开始,一个 URL 实际上是 URI 的一种类型。Version 指的是该 HTTP 请求所用到的 HTTP 协议版本。请求头包含了客户端环境与请求实体的一些有用的信息。例如它包含浏览器设定的语言、实体的长度等等。每条请求头用回车换行符(CRLF)分开。HTTP 响应与请求相似,HTTP 响应也由三部分组成:Protocol-Status code-Description-协议状态 描述代码Response headers-响应头Entity bod
4、y-响应实体响应头的第一行类似请求头的第一行,显示协议版本是 HTTP 1.1 ,请求成功(200=success)不成功(404=not fonunt)。响应的实体是 HTML 内容。头与实体之间由回车换行的空行(CRLF)分开。2. Socket套接字在Java应用程序中包提供Socket类和ServerSocket类分别用于客户端和服务器端,在服务器和客户端之间建立连接,实现数据交换。通过套接字建立连接的过程分为以下3个步骤:(1)服务器建立ServerSocket对象,负责监听并接收客户端请求。(2)客户端创建一个Socket对象,向服务器发送请求试图与之建立连接。(3)服务器接收到客
5、户端请求,为它创建一个单独的进程来处理请求, 创建套接字InputStream和OutputStream流对象,套接字输入、输出流完全可以连接作为一个I/O流对象来对待。在使用套接字编写客户机/服务器应用程序时,建立客户机和服务器两端相互通信的过程是一样的,该过程的主要工作可归纳为以下4个方面: 打开到套接字的输入、输出流。 根据服务器协议读写数据。关闭套接字。3.程序编写:(1)调用ServerSocket(int port)创建一个服务器端套接字,并前台输入端口号;(2)监听连接请求,如果客户端有请求连接,调用accept()方法,接受连接,返回通信套接字;(3)调用Socket类的get
6、OutputStream()和getInputStream()获取输出流和输入流,来响应请求;(4)最后关闭流和通信套接字。四、系统结构整个程序设计结构包括2个java类(1)WebServer11类实现监听并接受请求(2)和HttpRequest11处理请求并处理,产生响应的结果五、程序流程(或模块划分)根据HTTP协议的作用原理,Socket编程,Web服务器的工作原理,实现GET请求的Web服务器程序的流程如下:(1) 创建ServerSocket类对象,监听端口9090。这是为了区别于HTTP的标准TCP/IP端口80而取的特定端口;(2) 等待、接受客户机连接到端口9090,得到与客
7、户机连接的socket;(3) 创建一个HttpRequest 对象,将标征着所建立TCP连接的Socket作为参数传递到它的构造函数中(4) 为了让HttpRequest对象在一个单独的线程中处理随后的HTTP请求,我们首先创建一个Thread对象,将HttpRequest对象作为参数传递给Thread的构造函数,然后调用Thread的start()方法启动线程(5) HttpRequest必须实现Runnable接口。因此,必须定义HttpRequest的public方法run(),实现多线程(6) 利用类StringTokenizer从Request行中解析出文件名字。 (7) 从请求信
8、息中获得参数的值,使用ProcessRequest处理请求方法进行参数处理(8) 经过处理请求信息将得到一个html的文件名,如果该文件存在,则打开文件,把HTTP头信息和文件内容通过socket传回给Web浏览器,然后关闭文件。否则向服务器发送错误信息;(9)关联的输入流和输出流,关闭socket六、源程序MultiThreadWebServer.java:创建服务器套接字,侦听客户端请求,为每个请求创建一个线程/* * MultiThreadedWebServer的启动采用如下形式: * java MultiThreadedWebServer port_number * port_numb
9、er为一整数,(1024=port_number=65535) * 在客户端浏览器地址栏中输入如下形式地址访问服务器: * http:/localhost:port_number/textbook.jpg */import .* ;public final class MultiThreadedWebServer public static void main(String argv) throws Exception / 从命令行中获得WebServer的监听端口号 /int port = (new Integer(argv0).intValue(); /todo: 添加代码以创建监听Soc
10、ket ServerSocket socket = new ServerSocket(8989); while (true) / Listen for a TCP connection request. Socket connection = socket.accept(); / 创建对象,处理收到的HttpRequest消息 HttpRequest request = new HttpRequest(connection); / 创建线程,不同的消息用不同的线程进行处理 Thread thread = new Thread(request); / 启动线程. thread.start();
11、HttpRequest.java 实现了Runnable接口,对请求进行处理并做出响应/* 1.通过实现Runnable接口实现多线程 2.网络相关处理机制包含在包中 3.HttpRequest类中使用的某些对象包含在java.util包中 4.将收到的HTTP request消息作为参数来创建HttpRequest类的实例 5.线程一启动将处理完该Request消息 */*需要添加代码的地方用/todo:标注*/import java.io.* ;import .* ;import java.util.* ;final class HttpRequest implements Runnabl
12、e /定义回车换行符 final static String CRLF = rn; Socket socket; / 类HttpRequest的构造函数,用于对socket进行初始化. public HttpRequest(Socket socket) throws Exception /todo: 添加一行代码,以便初始化socket this.socket=socket; / 必须实现Runnable接口中的run()方法,实现对HTTP协议 Request消息的处理 public void run() try /利用方法processRequest实现对Request消息的处理 proc
13、essRequest(); catch (Exception e) System.out.println(e); private void processRequest() throws Exception /todo: 创建socket所关联的输入流is和输出流os InputStreamReader is=new InputStreamReader(socket.getInputStream(); DataOutputStream os=new DataOutputStream(socket.getOutputStream(); BufferedReader br = new Buffer
14、edReader(is); / todo: 添加一行代码,获取所收到HTTP Request消息的Request Line String requestLine; requestLine=br.readLine(); System.out.println(); System.out.println(requestLine); / Request消息的Request line中包含: Method URL Version三个字段. 利用StringTokenizer类简化读取字段的操作 StringTokenizer tokens = new StringTokenizer(requestLin
15、e); / 跳过Method字段,因为它应该是GET tokens.nextToken(); / 获取头部行中的URL字段值 String fileName = tokens.nextToken(); /todo: 在URL标征的文件名前添加.,因为所请求的对象在当前目录下(即该Web服务器的主目录). fileName=.+fileName; / 打开请求的文件. FileInputStream fis = null ; boolean fileExists = true ; try fis = new FileInputStream(fileName); catch (FileNotFou
16、ndException e) fileExists = false ; System.out.println(到达了一个Request消息!); /todo: 添加用于调试的信息,以便在控制台输出HTTP Request消息全部header line的内容 String headerLine = null; while (headerLine = br.readLine().length() != 0) System.out.println(headerLine); / 创建Reponse消息. String statusLine = null; /状态行 String contentType
17、Line = null; /Content-Type行 String entityBody = null; /Entity body部分 if (fileExists) /如果请求的文件存在 /todo: 创建Reponse消息的Status 行和Content-Type行 statusLine=HTTP/1.1 200 OK; contentTypeLine = Content-type: + contentType( fileName ) + CRLF; else /如果文件不存在 /todo: 创建Reponse消息的Status 行和Content-Type行 statusLine=H
18、TTP/1.1 404 NotFound+ CRLF; contentTypeLine=text/html; /todo: 利用HTML创建Web页面,提示用户所请求的文件不存在. Web页面作为Response消息的entity body部分 entityBody = + 不存在 + 请求文件不在服务器上; / todo: 发送Reponse消息的头部行. os.writeBytes(statusLine); os.writeBytes(contentTypeLine); os.writeBytes(CRLF); / 发送Reponse消息的entity body 部分(响应消息内容). i
19、f (fileExists) /调用后面所编写的sendBytes方法,实现文件数据的发送 sendBytes(fis, os); fis.close(); else /todo: 如果请求的文件不存在,直接发送前面创建的,用于提示用户文件不存在的Web页面 os.writeBytes(entityBody); /todo: 关闭所使用的流和Socket. os.close(); br.close(); socket.close(); private static void sendBytes(FileInputStream fis,OutputStream os) throws Except
20、ion /todo: 创建buffer,将请求的文件数据读入buffer,然后通过输出流os发送到客户端 byte buffer = new byte1024; int bytes=0; while(bytes=fis.read(buffer)!=-1) os.write(buffer,0,bytes); /contentType方法用于根据文件的扩展名,确定Content-Type头部行 private static String contentType(String fileName) /todo:根据文件的扩展名,返回填写在conten-type中的内容 if(fileName.ends
21、With(.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.输入服务器的端口号:89892.打开浏览器在地址栏输入:http:/localhost:8989/filename八、测试情况1.在地址栏输入
22、http:/localhost:8989/yizi.jpg 2、在地址栏输入http:/localhost:8989/text.txt 3. 在地址栏输入http:/localhost:8989/1.jpg4. 在地址栏输入http:/localhost:8989/haokan.jpg结 论设计中遇到的问题如何实现服务器的多线程,如何使客户端和服务器实现通信,及响应结果如何发送给客户端。1. Web服务器将是多线程的,所收到的每个Request消息将交由单独的线程进行处理。这使得服务器可以并发地为多个客户服务, 或者是并发地服务于一个客户的多个请求.当创建一个新线程时,需要向线程的构造函数传递
23、实现了Runnable 接口的类的一个实例(即通过实现接口Runnable来实现多线程)。这正是我们定义单独的类HttpRequest的原因 。创建监听端口以等待TCP连接请求。由于Web服务器将不间断地提供服务,我们将侦听操作放在一个无穷循环的循环体中。这意味着需要通过在键盘上输入C来结束Web服务器的运行。2. 为了将类HttpRequest的实例作为参数传输传递到Thread的构造函数中,HttpRequest必须实现Runnable接口。因此,必须定义HttpRequest的public方法run(),其返回值类型为void。我们在run()中调用实现Request消息处理绝大部分操作
24、的方法 processRequest()。 3.由于之前对HTTP的协议格式不熟悉,在发送HTTP响应消息实体时出现问题:显示页面与发送的页面有出入,请教了马老师后,发现了自己的错误:每个头信息都是一行,“Content-Length”头信息是必需的,它表示要读取多长的实体,而且头信息和实体之间有一个空行,这些都正确后才能看到想要的页面效果。4.运行时发现无法显示请求存在的文件,经过请教指导老师,调试发现使输入输出量方面使用错误,还有相关的方法不知具体如何应用。经过课程设计发现自己的知识掌握的还不牢固,应用能力需要加强。Java编程要经常复习和练习。在计算机网络方面,两台机器如何实现通信,socket及http具体的功能和作用, 参考文献1、 RFC 2616 - Hypertext Transfer Protocol - HTTP/1.12、 RFC 1866 - Hypertext Markup Language - 2.03、 RFC 2854 - The text/html Media Type4、 Java实例入门刘勇, 中国青年出版社, 2002-01-015、 Java程序设计与案例刘宝林 ;高等教育出版社6、 Java TCP/IP Socket编程(原书第2版)机械工业,(美)卡尔弗特,周恒民 译
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1