声明:本文大概需要30分钟,如果只依据本文不看源码就能写出Web服务器就算学会了~如有错误欢迎指正~
首先我们要知道web服务器是什么?
一般指网站服务器,是指驻留于因特网上某种类型计算机的程序
服务器有什么作用:
1.放置网站文件,让别人浏览
2.可以放置数据文件,供别人下载
服务器分类:
1.Apache(例如TomCat)
2.Nginx
3.IIS
Web服务器的工作原理,分四步:
1.连接过程
2.请求过程
3.应答过程
4.关闭连接
手撸web服务器就是根据web服务器的工作原理去手写代码以实现例如Tomcat的部分核心功能
根据这两天的学习可以分为基础和进阶版本:
基础就是简单实现,灵活度不高
进阶就是把部分固定功能的代码封装,再做一些动态的方法以供调用
下面我总结一下手撸Web服务器的业务逻辑
1 /** 2 * 极其简易的版本 3 * @author shaking 4 * 5 */ 6 public class WebServer { 7 8 public static void main(String[] args) { 9 WebServer webServer = new WebServer(); 10 webServer.start(); 11 } 12 13 private ServerSocket server; 14 15 public WebServer() { 16 try { 17 server = new ServerSocket(8080); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 public void start() { 24 try { 25 while(true) { 26 Socket socket = server.accept(); 27 OutputStream outputStream = socket.getOutputStream(); 28 outputStream.write("abcaaa".getBytes()); 29 outputStream.flush(); 30 socket.close(); 31 } 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } 35 36 } 37 38 }
基础实现:
一、创建服务器类即WebServer类(连接过程)
·1声明ServerSocket类,代表服务器;ServerSocket作用是监听特定端口,例如:8080,8086等;端口号总共用65535个
绑定端口方法:ServerSocket server = new ServerSocket(8080);
利用构造方法初始化ServerSocket;
端口可以传入0,表示操作来为服务器分配一个任意可用的端口,也称为匿名端口,但不推荐使用;
如果端口被占用会抛出BindException异常
解决办法:win + r进入运行,输入CMD进入命令行模式
输入 netstat -ano查看所有被占用端口 找到想要关闭的端口对应的Listening后的值
输入 taskkill -f -pid (Listening后的值)
·2创建开始方法start()(请求和应答过程)
(请求)调用ServerSocket的accept()方法监听并接受套接字(socket)的连接,返回值是Socket对象
因为服务器是被动程序,需要等有请求的时候才会响应,所以accept()方法应该是持续运行的;所以要用到while(true)
(应答)接收到请求之后根据请求的不同应该回应不同的信息,此处基础实现回应相同的信息
·调用socket的getOutputStream()返回这个套接字的输出流(OutputStream)
·然后调用OutputStream的方法write(byte[] b)输入想写内容;因为我们输入的是字符串,而write方法要求传入字节数组,
所以调用字符串的getBytes()方法返回字符数组
·然后调用OutputStream的方法flush()刷新流并强制写出所有缓冲的输出字节
·3关闭连接
调用Socket的close()方法关闭连接
·4测试该基础服务器能否成功运行
利用HttpWatch监听该连接过程,查看请求和响应;如果响应的是你在write中写的内容,该服务器即创建成功!
但是其中页面一直处于加载过程,原因是你的响应不符合Http协议,浏览器一直在等待想要的内容(即Http的标准响应格式)
·5修改程序不当的响应方式
·这个时候就要修改write以符合Http的标准响应格式
·调用PrintStream对象,该对象继承了FilterOutputStream,而FilterOutputStream继承了OutputStream;
·该对象与其他输出流不同的是永远不会抛出IOException,并且会自动调用flush()方法(一般在执行print,println,write时自动执行),在需要写入字符的时候推荐使用;因为IO流是基于装饰者模式,所以使用该对象必须两种类型(File或OutputStream)参数传入一种,此处传入的是OutputStream;而OutputStream可以通过Socket的getOutputStream()方法得到
·调用PrintStream的println()方法拼接出标准的Http协议响应格式(状态行,响应头,空行,响应内容)如果在调试过程中没有响应,可以尝试以下方法,查看方法调用顺序是否有问题、重新启动浏览器、换一个浏览器、重启Eclipse
1 /** 2 * 修改符合HTTP协议的版本 3 * @author shaking 4 * 5 */ 6 public class WebServer { 7 8 public static void main(String[] args) { 9 WebServer webServer = new WebServer(); 10 webServer.start(); 11 } 12 13 private ServerSocket server; 14 15 public WebServer() { 16 try { 17 server = new ServerSocket(8080); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 public void start() { 24 try { 25 while(true) { 26 Socket socket = server.accept(); 27 PrintStream ps = new PrintStream(socket.getOutputStream()); 28 ps.println("HTTP/1.1 200 OK"); 29 ps.println("Content-Type:text/html"); 30 String s = "server is running->->->->"; 31 ps.println("Content-Length:" + s.length()); 32 33 ps.println(""); 34 35 ps.write(s.getBytes()); 36 socket.close(); 37 } 38 } catch (IOException e) { 39 e.printStackTrace(); 40 } 41 42 } 43 44 }
这个时候浏览器上应该有write()里的内容:“server is running->->->->”;如果出错了检查是不是没有加空行
进阶实现:
具体的Web服务器结构
·cn.itlou----core 核心包: WebServer ,ClientHandler
|
---http 封装Http协议相关内容:HttpRequest ,HttpResponse
|
---common 参数配置信息:ServletContext ,HttpContext
config配置文件:web.xml
一、基础实现有许多许多的不足,单线程不能同时接收过多的请求,所以我们加入多线程技术
具体的服务器架构不用变,增加线程池对象的引用并初始化线程池;
ExecutorService threadPool = Executors.newFixedThreadPool(int a);线程池的创建方法,记得数字不要给的过高,可能电脑不行导致程序出错;
在start()方法中加入线程的应用,调用execute(Runnable command)方法;传入实现Runnable的对象ClientHandler
而该对象应该包含所有我们希望通过利用多线程提高性能的方法(应答,关闭连接)
我们把该对象命名为ClientHandler它实现了Runnable接口,重写run方法并写入应答,关闭连接的代码;
·1声明一个代表客户端的对象Socket,并将该对象传入ClientHandler构造方法;
·2提取响应代码写入run方法中
·3利用线程池执行写好的ClientHandler类
注意:重写时写入网页数据应该用PrintStream的write方法,而不是println()方法,使用println方法会输出地址
1 /** 2 * 进阶利用多线程的版本 3 * @author shaking 4 * 5 */ 6 public class WebServer { 7 8 public static void main(String[] args) { 9 WebServer webServer = new WebServer(); 10 webServer.start(); 11 } 12 13 private ServerSocket server; 14 private ExecutorService threadPool; 15 16 public WebServer() { 17 try { 18 server = new ServerSocket(8080); 19 threadPool = Executors.newFixedThreadPool(100); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 public void start() { 26 try { 27 while(true) { 28 Socket socket = server.accept(); 29 threadPool.execute(new ClientHandler(socket)); 30 } 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 35 } 36 37 }
1 /** 2 * 多线程部分代码 3 * @author shaking 4 * 5 */ 6 public class ClientHandler implements Runnable{ 7 8 private Socket socket; 9 10 public ClientHandler(Socket socket) { 11 this.socket = socket; 12 } 13 14 public void run() { 15 try { 16 PrintStream ps = new PrintStream(socket.getOutputStream()); 17 ps.println("HTTP/1.1 200 OK"); 18 ps.println("Content-Type:text/html"); 19 String s = "server is running------->>>"; 20 ps.println("Content-Length:" + s.length()); 21 22 ps.println(""); 23 24 ps.write(s.getBytes()); 25 socket.close(); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 30 } 31 32 }
这个时候浏览器上应该有write()里的内容:“server is running------->>>”;如果出错了检查是不是线程池加入过多线程数;
二、修改程序使其能输出具体的网页
这时候只需要修改响应部分的部分代码;即修改ClientHandler类中run方法的部分内容
·1自制一个简单网页或者已有的网页,例如index.html,把其放入WebContent文件夹下
思考一下在网页中怎么输出的字符串,网页同理(传入文件,写出文件内容)
·2 -1)传入文件,用File类,new File(); 在其中传入一个String类型的pathName,然后使用BufferedInputStream字节缓冲流,使用该类需要传入一个FileInputStream类型的对象,这里我们使用FileInputStream传入我们的File对象即想要输出的网页,将字节数组byte[] bs = new byte[(int)file.length()],传入BufferedInputStream的read(byte[])方法将文件读入
-2)写出文件内容,调用PrintStream的write方法,write要求传入字符数组,调用PrintStream的write(byte[])方法传入已经创建好的字符数组,关闭BufferedInputStream流
·3关闭socket连接
1 /** 2 * 输出具体页面的代码 3 * @author shaking 4 * 5 */ 6 public class ClientHandler implements Runnable{ 7 8 private Socket socket; 9 10 public ClientHandler(Socket socket) { 11 this.socket = socket; 12 } 13 14 public void run() { 15 try { 16 PrintStream ps = new PrintStream(socket.getOutputStream()); 17 ps.println("HTTP/1.1 200 OK"); 18 ps.println("Content-Type:text/html"); 19 String pathName = "WebContent/index.html"; 20 File file = new File(pathName); 21 ps.println("Content-Length:" + file.length()); 22 23 ps.println(""); 24 25 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 26 27 byte[] bs = new byte[(int) file.length()]; 28 29 bis.read(bs); 30 ps.write(bs); 31 bis.close(); 32 socket.close(); 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } 36 37 } 38 39 }
三、我们这个服务器还是很低端,只能显示一个固定的网页,我们希望服务器能够动态的响应输入的所有网页,有的就显示,没有就404
根据地址栏输入的网址不同,请求行也会相应的改变
·1获取请求行的部分内容,例如: GET /abc.html HTTP/1.1
这里我们使用字符输入流BufferedReader,传入新的对象InputStreamReader并在新的对象中传入socket的输入流;
调用BufferedReader的方法readline()读取这一行数据,如果需要完整的HTTP请求可以写死循环
我们利用字符串的split()方法切割请求行,以" "为目标切割成3份再用String类型数组接收,其中索引为1的数组就是我们想要的/abc.html
·2修改pathName的值使其可以动态的变化
·3设置index默认页面和404错误页面
判断split切割后的地址,决定如何显示页面;如果为空显示index,如果没有对应的网页显示404
index需要判断切割后的地址是否为空
404需要判断切割后的地址对应的文件是否存在
注意:如果控制台报错:FileNotFoundException:WebContent\favicon.ico在WebCont下放入一个后缀名为ico的图片文件即可
1 /** 2 * 输出具体页面的代码 3 * 包括默认首页与404页面 4 * @author shaking 5 * 6 */ 7 public class ClientHandler implements Runnable{ 8 9 private Socket socket; 10 11 public ClientHandler(Socket socket) { 12 this.socket = socket; 13 } 14 15 public void run() { 16 try { 17 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 18 String line = reader.readLine(); 19 String[] s = line.split(" "); 20 String uri = s[1]; 21 22 if(uri.equals("/")) { 23 uri = "/index.html"; 24 } 25 26 PrintStream ps = new PrintStream(socket.getOutputStream()); 27 ps.println("HTTP/1.1 200 OK"); 28 ps.println("Content-Type:text/html"); 29 String pathName = "WebContent" + uri; 30 File file = new File(pathName); 31 32 if(!file.exists()) { 33 uri = "/404.html"; 34 file = new File("WebContent" + uri); 35 } 36 37 ps.println("Content-Length:" + file.length()); 38 39 ps.println(""); 40 41 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 42 43 byte[] bs = new byte[(int) file.length()]; 44 45 bis.read(bs); 46 ps.write(bs); 47 bis.close(); 48 socket.close(); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 } 52 53 } 54 55 }
转载请注明出处:http://www.cnblogs.com/shak1ng/