多线程Web服务器的搭建与实现

实验目的

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
在这里插入图片描述
然后输入网址,回车
在这里插入图片描述
点击我要分析的文件
在这里插入图片描述

这样就可以直接看到浏览得到的响应信息!

谢谢观看

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值