实验目的
1. 熟悉简单网络的搭建与基本配置;
2. 熟悉socket、多线程编程;
3. 熟悉JDK编程工具的基本使用;
4. 熟悉HTTP协议;
5. 熟悉Web服务器的基本工作原理和配置。
实验任务
以JDK为开发工具,利用Socket通信机制实现一个多线程的WEB服务器,该服务器具有以下功能:
1, 能够并行服务于多个请求。
2,对于每个请求,显示接收到的HTTP请求报文的内容,并产生适当的响应(若找到用户请求对象,则返回该对象。否则发送一个包含适当提示信息的响应消息,从而可以在浏览器窗口中显示差错信息。
实验思路和过程
1,首先了解多线程编程机制,了解多线程编程的两种使用方法:(1)继承(2)接口。下面看看这两种实现方法的区别:
(1)继承Thread类
public class FirstThread extends Thread {
private String name; //线程的名字
public FirstThread(String name) {
super(name);
System.out.println(name+"创建成功");
}
public void run() {
for(int i=0;i<3;i++) {
System.out.println(Thread.currentThread().getName()+"第"+i+"次运行");
Thread.yield();
}
}
public static void main(String[] args) {
FirstThread t1=new FirstThread("第一个线程");
FirstThread t2=new FirstThread("第二个线程");
System.out.println("开始启动t1,t2线程");
t1.start();
t2.start();
System.out.println("main方法运行完毕");
}
}
(2)使用Runnable接口
public class SecondThread implements Runnable {
private String name;
public SecondThread(String name) {
this.name=name;
System.out.println(name+"创建成功");
}
@Override
public void run() {
for(int i=0;i<3;i++) {
System.out.println(name+"第"+i+"次运行");
Thread.yield();
}
}
public static void main(String[] args) {
SecondThread r1=new SecondThread("第一个线程");
SecondThread r2=new SecondThread("第二个线程");
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
System.out.println("开始启动t1.t2进程");
t1.start();
t2.start();
System.out.println("main方法运行完毕");
}
}
因为最后的结果会不尽相同,所以这里就先不用图片展示打印结果。不了解的话可以多运行几次代码。
2,要了解并熟悉套接字编程,懂得如何使用Socket类,以及ServerSocket类,建议使用套接字编程自己写一个聊天程序,在本机上实现文本的实时交流!
3,关于HTTP协议,只需要稍微了解一下内容就行。(可以使用WireShark进行抓包,也可以直接在浏览器的开发者工具上进行观察)
实验代码以及分析
import java.net.ServerSocket;
import java.net.Socket;
public final class WebServer {
public static void main(String args[]) throws Exception {
ServerSocket socket=new ServerSocket(1412);//打开服务套接字,监听1412端口
while(true) {
Socket s=socket.accept();
HttpRequest http=new HttpRequest(s);
Thread thread=new Thread(http);
thread.start();
}
}
}
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Date;
import java.util.StringTokenizer;
final class HttpRequest implements Runnable {
final static String CRLF="\r\n"; //“回车与换行”,用于在http响应报文中
Socket socket;
public HttpRequest(Socket socket)throws Exception {
this.socket=socket;
}
public void run() {
try {
requestprocess();
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
public void requestprocess() throws Exception{
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));//将从套接字得到的字节输入流,利用转换流,转换成字符流 ,便于之后更好的读取
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
String requestLine=br.readLine(); //得到请求行
System.out.println(requestLine);
String headerLine;
while((headerLine=br.readLine()).length()!=0) {
System.out.println(headerLine);
}
StringTokenizer stokizer=new StringTokenizer(requestLine); //该类用于分解字符串,默认分隔符有:空格,回车,换行等
String requestMethod=stokizer.nextToken(); //这里得到的是Get,但是后面我们不会用到
String fileName="D:\\"+stokizer.nextToken(); //在后面寻找文件中,将会在D盘的根目录进行寻找
FileInputStream fis=null;
boolean fileIsExisted=true;
try {
fis=new FileInputStream(fileName); //利用这个类相当于进行了寻找,若没有找到,则会捕获异常。
}catch(FileNotFoundException e1) {
fileIsExisted=false;
}
String statusLine = null; //状态行
String contentTypeLine = null; //Content-type行
Date date=new Date();
String dates="Date: "+date.toString()+CRLF; //Date行
String entityBody = null; //实体部分
if(fileIsExisted) {
statusLine="HTTP/1.1 200 OK"+CRLF;
if(fileName.endsWith(".html") || fileName.endsWith(".htm") || fileName.endsWith(".txt")) {
contentTypeLine="Content-type: text/html;charset=utf-8"+CRLF;
}
else if(fileName.endsWith(".jpg")) {
contentTypeLine="Content-type: image/jpg;charset=utf-8"+CRLF;
}
else if(fileName.endsWith(".gif")) {
contentTypeLine="Content-type: image/gif;charset=utf-8"+CRLF;
}
else {
contentTypeLine="Content-type: Unknown;charset=utf-8"+CRLF;
}
dos.writeBytes(statusLine);
dos.writeBytes(contentTypeLine);
dos.writeBytes(dates);
dos.writeBytes(CRLF);
while(fis.available()>0) {
byte by[]=new byte[1024];
fis.read(by);
dos.write(by);
}//将我本地的文件内容写入到套接字的输出流中,也就是将我的文件传给浏览器
}else {
statusLine="HTTP/1.1 404 NotFound"+CRLF;
contentTypeLine = "Content-type: text/html;charset=utf-8"+CRLF;
entityBody ="<html><title>Not found</title><h1>404 NotFound</h1></html>";
dos.writeBytes(statusLine);
dos.writeBytes(contentTypeLine);
dos.writeBytes(dates);
dos.writeBytes(CRLF);
dos.writeBytes(entityBody);
}
fis.close();
dos.close();
br.close();
socket.close();
}
}
代码测试
问题总结
1,关于以下代码:
while((headerLine=br.readLine()).length()!=0) {
System.out.println(headerLine);
}
我第一次用的是这个
while((headerLine=br.readLine())!=null) {
System.out.println(headerLine);
}
在服务器,也就是我的电脑本地,是可以打印出全部的请求信息的,可是在请求文件的过程中,发现打不开,
并且查看浏览器,发现根本没有收到任何的响应信息。最后改动了这一部分,发现请求成功。可还是不懂原因,
感觉差不多。
2,
while(fis.available()>0) {
byte by[]=new byte[1024];
fis.read(by);
dos.write(by);
}
以上代码可以换成:
while(fis.available()>0) {
dos.write(fis.read());
}
在加载文本的时候,时间差不多,可是在加载图片的时候,第一个要比第二个快很多,当然也有更快的方法。
3,可以使用浏览器自带的工具进行测试代码!
打开开发者工具:network
然后输入网址,回车
点击我要分析的文件
这样就可以直接看到浏览得到的响应信息!