# My_WEB_Server **Repository Path**: qin-yanchao/My_WEB_Server ## Basic Information - **Project Name**: My_WEB_Server - **Description**: WEB服务器项目(个人练习) - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 0 - **Created**: 2023-11-04 - **Last Updated**: 2025-07-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # WEB服务器项目 ## 一、项目内容 > 实现一个Web服务器 > > 访问电脑上的**静态资源**与**动态资源** > > 局域网访问到服务器资源+公网(内网穿透NATAPP)将服务器放置在公网访问 ## 二、技术栈 - Java - 网络编程 - IO流 - 线程 - 封装思想 ## 三、学习目标 - 巩固Java所学知识 - 将所学知识灵活的运用到项目中 - 理解Web服务器运行原理 - 提高动手写代码的能力(多想,多动) - 锻炼分析和解决问题的能力 ## 四、什么是Web? **Web(World Wide Web)即全球广域网,也称为万维网**,它的本意是蜘蛛网和网的意思,在网页设计中我们称为网页的意思。现广泛译作网络、互联网等技术领域。**表现为三种形式,即超文本(hypertext)、超媒体 (hypermedia)、超文本传输协议(HTTP)。** ![image-20231110144012475](README.assets/image-20231110144012475.png) ## 五、Web的特点 - Web是图形化的和易于导航的 - Web与平台无关 - Web是分布式的 - Web是动态的 - Web是交互的 ## 六、什么是服务器? 所谓的**服务器是指安装了能够管理资源并为用户提供服务的计算机软件的pc机**。我们常说的,**文件服务器** ,提供文件上传下载服务器的pc机。**数据库服务器**,就是安装了数据库软件,能帮我们管理数据资源,并且提供crud(增删改查)操作的pc机。 ![image-20231110144646262](README.assets/image-20231110144646262.png) ## 七、什么是Web服务器 Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,**可以向浏览器等Web客户端提供文档,也可以放置网站文件,让网络用户浏览;可以放置数据文件,提供下载。** ![image-20231110144710551](README.assets/image-20231110144710551.png) ## 八、HTTP协议 **超文本传输协议** (HTTP-Hypertext transfer protocol) 是一种详细**规定了浏览器和万维网服务器之间互相通信的规则**,通过因特网传送万维网文档的数据传送协议。HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。**HTTP是一个无状态的协议,无状态是指协议对于事务处理没有记忆能力**。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。 ## 九、URL **统一资源定位符**(Uniform Resource Locator,缩写为URL)是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。 > 基本格式: ```http schema://host[:port#]/path/.../[;url-params][?query-string][#a nchor] ``` > - **schema** 指定所使用的协议(http、https、ftp...) > - **host** HTTP服务器的IP地址或者域名 > - **port#** HTTP服务器的默认端口是80,这种情况下端口号可以省略 > - **path** 访问的资源的路径 > - **query-string** 发送给http服务器的数据 > - **anchor** 锚 http://localhost:80/briup/login.html?username=briup#login ## 十、Request **客户端发送到服务器端的请求消息**,我们称之为**请求**(request),其实就是一个按照HTTP协议的规则拼接而成的字符串。 Request请求消息包含三部分: - 请求行 - 消息报头 - 请求正文 结构如下图: ![image-20231110144733124](README.assets/image-20231110144733124.png) > **第一部分:请求行** > - 格式: > - Method Request-URI HTTP-Version \r\n > - 各部分分别为: > - Method表示请求方法;一般为GET或者POST > - Request-URI是一个统一资源标识符 > - HTTP-Version表示请求的HTTP协议版本 > - CRLF表示回车和换行 > > 例如: GET /test.html HTTP/1.1 > **第二部分:消息报头** > > - 参照: http协议详解附件 > > **第三部分:请求正文 ** > > - 请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的字符串信息 。**注意:在第二部分header和第三部分body之间有个空行,除非没有请求正文。** > > 当使用"GET" 方法的时候, body是为空的,当使用"POST"**,body不为空,但是没有换行,readline不能读** ## 十一、Response **服务器在接收和解释客户端的请求消息后,服务器会返回给客户端一个HTTP响应消息**,我们称之为**响应**(response)。其实也是一个按照http协议的规则拼接而成的一个字符串。 HTTP响应也是由三个部分组成,分别是: - 响应状态行 - 消息报头 - 响应正文 结构如下图: ![image-20231110144754436](README.assets/image-20231110144754436.png) > **第一部分:响应状态行** > - 格式: > - HTTP-Version Status-Code Reason-Phrase CRLF > - 各部分分别为: > - HTTP-Version表示服务器HTTP协议的版本 > - Status-Code表示服务器发回的响应状态代码 > - Reason-Phrase表示状态代码的文本描述 > - CRLF表示回车和换行 > 例如:HTTP/1.1 200 OK > **第二部分:消息报头 ** > - 参照: http协议详解附件 > **第三部分:响应正文** > - 响应正文就是服务器返回的资源的内容 ## 十二、项目搭建 1、新建JAVA项目 ![image-20231110144915799](README.assets/image-20231110144915799.png) 2、项目配置 ![image-20231110145011223](README.assets/image-20231110145011223.png) 3、项目创建成功 ![image-20231110145027350](README.assets/image-20231110145027350.png) 4、创建前基本包结构 > 选中src-->右键-->New-->Package ![image-20231110145056587](README.assets/image-20231110145056587.png) > 输入包结构即可,我这里以com.qinyc.server为例,回车创建包结构。 ![image-20231110145117493](README.assets/image-20231110145117493.png) ![image-20231110145135250](README.assets/image-20231110145135250.png) 5、创建服务器类 > 选中最底层包(server)-->右键-->New-->Java Class ![image-20231110145156065](README.assets/image-20231110145156065.png) > 输入类名,我这里以ServerMain为例,类名确认后,回车即可创建成功。 ![image-20231110145224860](README.assets/image-20231110145224860.png) ## 十三、第一关(单线程读取请求): 1、搭建一台TCP服务器 2、接受客户端的连接,使得服务器与客户端可以成功建立连接 3、接收客户端发送过来的数据,输出在控制台上(使用字节流) 4、源码实现 ```java package com.qinyc.server.test1; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * @Classname ServerMain * @Description 服务器主机-----单线程(字节流) * @Date 2023/11/4 14:55 * @Created by qinyc * @Version v1.0.0 */ public class ServerMain { public static void main(String[] args){ //搭建TCP服务器( 端口号 9999)---声明 ServerSocket serverSocket = null; //声明套接字 Socket socket = null; //声明内存输出流 ByteArrayOutputStream baos = null; try { //搭建TCP服务器( 端口号 9999)---创建 serverSocket = new ServerSocket(9999); //输出提示:服务器启动成功 System.out.println("服务器已启动等待客户端的连接!"); //接收客户端的连接 socket = serverSocket.accept(); if (socket != null){ //客户端连接成功 System.out.println("客户端已经成功连接到服务器!客户端IP:"+socket); } //创建内存输出流 baos = new ByteArrayOutputStream(); //接收客户端发送过来的数据,输出在控制台上 byte[] data = new byte[1024]; //创建变量记录存储数据的长度 int length; //循环读取输入流中的信息并保证存在data中 while ((length = socket.getInputStream().read(data)) != -1){ //将读到的内容写在内存输出流对象中 baos.write(data,0,length); } //输出内存流中的字符串 System.out.println(baos.toString()); } catch (IOException e) { e.printStackTrace(); }finally { //关闭资源 try { //关闭内存输出流 if (baos != null){ baos.close(); } if (socket != null){ //关闭socket连接 socket.close(); } if (serverSocket != null){ //关闭ServerSocket serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` > 运行程序,在浏览器地址栏输入http://127.0.0.1:9999观察控制台输出: ![image-20231110145247821](README.assets/image-20231110145247821.png) ## 十四、第二关(单线程读取请求+返回响应): 1、 搭建一台TCP服务器 2、接受客户端的连接,使得服务器与客户端可以成功建立连接 3、接收客户端发送过来的数据,输出在控制台上(使用字符流、包装流) 4、解析出资源路径,按要求返回响应(字节流) - 响应行 - 响应头(可以为空) - 响应空行 - 响应体 - 若请求资源路径为"/",将文件index.html以流的形式响应给客户端 - 若文件存在的话,将文件以流的形式响应给客户端 - 不存在的话,将文件error.html以流的形式响应给客户端。 5、效果展示 - 访问首页 ![image-20231110145604322](README.assets/image-20231110145604322.png) - 访问指定资源 ![image-20231110145625362](README.assets/image-20231110145625362.png) - 访问不存在资源 ![image-20231110145644535](README.assets/image-20231110145644535.png) 6、源码实现 ```java package com.qinyc.server.test2; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @Classname ServerMain * @Description 服务器主机-----单线程(字符流+包装流+响应) * @Date 2023/11/5 0:04 * @Created by qinyc * @Version v1.0.0 */ public class ServerMain { public static void main(String[] args) { //搭建TCP服务器( 端口号 9999)---声明 ServerSocket serverSocket = null; //声明套接字 Socket socket = null; //声明缓冲字符输入流 BufferedReader br = null; //声明缓冲字节输出流 BufferedOutputStream bos = null; //声明缓冲字节输入流 BufferedInputStream bis = null; try { //搭建TCP服务器( 端口号 9999)---创建 serverSocket = new ServerSocket(9999); //输出提示:服务器启动成功 System.out.println("服务器已启动等待客户端的连接!"); //接收客户端的连接 socket = serverSocket.accept(); if (socket != null){ //客户端连接成功 System.out.println("客户端已经成功连接到服务器!客户端IP:"+socket); } //创建缓冲字符输入流 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //创建变量记录每一行数据 String line = null; //读取第一行数据(请求行) String requestLine = br.readLine(); System.out.println(requestLine); //循环读取输入流中的信息并保证存在data中 while ((line = br.readLine()) != null){ //打印到请求空行"",需要手动跳出循环 if (line.equals("")){ //打印请求空行 System.out.println(); //跳出循环 break; }else { //输出请求头信息 System.out.println(line); } } //从请求行提取资源路径信息,按照空格进行分割 GET / HTTP/1.1 String[] requestLineInfo = requestLine.split("[ ]"); //数组下标为1的就是请求资源路径 System.out.println("请求方式:"+requestLineInfo[0]); System.out.println("请求路径:"+requestLineInfo[1]); System.out.println("协议版本:"+requestLineInfo[2]); //创建文件 File file = null; //如果请求资源路径为 / if (requestLineInfo[1].equals("/")){ //指向首页 file = new File("D:/resource/index.html"); }else { //指向资源 file = new File("D:/resource"+requestLineInfo[1]); } //初始化缓冲字符输出流 bos = new BufferedOutputStream(socket.getOutputStream()); //定义字节数组,存储读取到的数据 byte[] data = new byte[1024]; //定义变量记录读取到的长度 int length; //判断文件是否存在 if (file.exists()){ //存在:响应成功 //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(file)); //响应行 bos.write("HTTP/1.1 200 OK \r\n".getBytes()); }else{ //不存在:响应404页面 //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(new File("D:/resource/error.html"))); //响应行 bos.write("HTTP/1.1 404 NOT FOUND \r\n".getBytes()); } //响应头(可省略) //响应空行 bos.write("\r\n".getBytes()); //读取文件响应文件回去 while ((length = bis.read(data)) != -1){ bos.write(data,0,length); } } catch (IOException e) { e.printStackTrace(); }finally { //关闭资源 try { //关闭缓冲输出流 if (bos != null){ bos.close(); } //关闭缓冲字节输出流 if (bis != null){ bis.close(); } //关闭缓冲输入流 if (br != null) { br.close(); } if (socket != null) { //关闭socket连接 socket.close(); } if (serverSocket != null) { //关闭ServerSocket serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } } } ``` ## 十五、第三关(多线程【创建线程】读取请求+返回响应): 1、 搭建一台TCP服务器 2、一直可以接受客户端的连接,使得服务器与客户端可以成功建立连接。 3、为每一个客户端分配一个线程去响应客户端(多线程) 4、接收客户端发送过来的数据,输出在控制台上(使用字符流、包装流) 5、解析出请求路径,按要求返回响应(字节流) - 响应行 - 响应头(可以为空) - 响应空行 - 响应体 - 若请求资源路径为"/",将文件index.html以流的形式响应给客户端 - 若文件存在的话,将文件以流的形式响应给客户端 - 不存在的话,将文件error.html以流的形式响应给客户端。 6、源码实现 ```java package com.qinyc.server.test3; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Objects; /** * @Classname ServerMain * @Description 服务器主机-----多线程(字符流+包装流+响应) * @Date 2023/11/5 12:33 * @Created by qinyc * @Version v1.0.0 */ public class ServerMain { public static void main(String[] args) { //声明ServerSocket ServerSocket serverSocket = null; try { //创建ServerSocket serverSocket = new ServerSocket(9999); //输出提示:服务器启动成功 System.out.println("服务器已启动等待客户端的连接!"); //服务器要一直接收客户端 while (true){ //初始化套接字 Socket socket = serverSocket.accept(); if (socket != null){ //客户端连接成功 System.out.println("客户端已经成功连接到服务器!客户端IP:"+socket); } //创建线程并启动 new Thread(new Runnable() { @Override public void run() { //声明缓冲字符输入流 BufferedReader br = null; //声明缓冲字节输入流 BufferedInputStream bis = null; //声明缓冲字节输出流 BufferedOutputStream bos = null; try { //初始化缓冲字符输入流 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //读取请求行数据 String requestLine = br.readLine(); //按照空格分割字符串 String[] requestLineData = null; if (!Objects.isNull(requestLine)){ requestLineData = requestLine.split("[ ]"); } /** * 拿到请求资源路径创建文件对象 */ //声明文件对象 File file = null; if (requestLineData[1].equals("/")){ //指向首页 file = new File("D:/resource/index.html"); }else{ //指向指定文件 file = new File("D:/resource"+requestLineData[1]); } //创建字节数组,存放读取到的数据 byte[] data = new byte[1024]; //定义变量记录每次读取到的数据长度 int length; //初始化缓冲字节输出流 bos = new BufferedOutputStream(socket.getOutputStream()); //判断文件是否存在 if (file.exists()){ //文件存在 响应指定文件回去 //写请求行 bos.write("HTTP/1.1 200 OK \r\n".getBytes()); //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(file)); }else{ //文件不存在 响应error页面回去 //写请求行 bos.write("HTTP/1.1 404 NOT FOUND \r\n".getBytes()); //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(new File("D:/resource/error.html"))); } //响应头(可省略) //响应空行 bos.write("\r\n".getBytes()); //读取文件 while ((length = bis.read(data)) != -1){ //写回读到的内容 bos.write(data,0,length); } } catch (IOException e) { throw new RuntimeException(e); }finally { //关闭资源 try { //关闭缓冲输出流 if (bos != null) { bos.close(); } //关闭缓冲字节输出流 if (bis != null) { bis.close(); } //关闭缓冲输入流 if (br != null) { br.close(); } if (socket != null) { //关闭socket连接 socket.close(); } }catch (IOException e){ e.printStackTrace(); } } } }).start(); } } catch (IOException e) { e.printStackTrace(); }finally { if (serverSocket != null) { //关闭ServerSocket try { serverSocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } } ``` ## 十六、第四关(多线程【线程池】读取请求+返回响应): 1、 搭建一台TCP服务器 2、一直可以接受客户端的连接,使得服务器与客户端可以成功建立连接。 3、为每一个客户端分配一个线程去响应客户端(多线程【线程池】) 4、接收客户端发送过来的数据,输出在控制台上(使用字符流、包装流) 5、解析出请求路径,按要求返回响应(字节流) - 响应行 - 响应头(可以为空) - 响应空行 - 响应体 - 若请求资源路径为"/",将文件index.html以流的形式响应给客户端 - 若文件存在的话,将文件以流的形式响应给客户端 - 不存在的话,将文件error.html以流的形式响应给客户端。 6、源码实现 ```java package com.qinyc.server.test4; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Classname ServerMain * @Description 服务器主机-----多线程(线程池) * @Date 2023/11/5 18:34 * @Created by qinyc * @Version v1.0.0 */ public class ServerMain { public static void main(String[] args) { //声明ServerSocket ServerSocket serverSocket = null; //创建线程池对象 ExecutorService service = Executors.newFixedThreadPool(10); try { //创建ServerSocket对象 serverSocket = new ServerSocket(9999); //输出提示:服务器启动成功 System.out.println("服务器已启动等待客户端的连接!"); //循环接收客户端请求 while (true){ //拿到套接字对象 Socket socket = serverSocket.accept(); if (socket != null){ //客户端连接成功 System.out.println("客户端已经成功连接到服务器!客户端IP:"+socket); } //创建任务 Runnable runnable = new Runnable(){ @Override public void run() { //声明缓冲字符输入流 BufferedReader br = null; //声明缓冲字节输入流 BufferedInputStream bis = null; //声明缓冲字节输出流 BufferedOutputStream bos = null; try { //初始化缓冲字符输入流 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //读取请求行数据 String requestLine = br.readLine(); System.out.println(requestLine); String line; //循环读取输入流中的信息并保证存在data中 while ((line = br.readLine()) != null){ //打印到请求空行"",需要手动跳出循环 if (line.equals("")){ //打印请求空行 System.out.println(); //跳出循环 break; }else { //输出请求头信息 System.out.println(line); } } //按照空格分割字符串 String[] requestLineData = null; if (!Objects.isNull(requestLine)){ requestLineData = requestLine.split("[ ]"); } /** * 拿到请求资源路径创建文件对象 */ //声明文件对象 File file = null; if (requestLineData[1].equals("/")){ //指向首页 file = new File("D:/resource/index.html"); }else{ //指向指定文件 file = new File("D:/resource"+requestLineData[1]); } //创建字节数组,存放读取到的数据 byte[] data = new byte[1024]; //定义变量记录每次读取到的数据长度 int length; //初始化缓冲字节输出流 bos = new BufferedOutputStream(socket.getOutputStream()); //判断文件是否存在 if (file.exists()){ //文件存在 响应指定文件回去 //写请求行 bos.write("HTTP/1.1 200 OK \r\n".getBytes()); //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(file)); }else{ //文件不存在 响应error页面回去 //写请求行 bos.write("HTTP/1.1 404 NOT FOUND \r\n".getBytes()); //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(new File("D:/resource/error.html"))); } //响应头(可省略) //响应空行 bos.write("\r\n".getBytes()); //读取文件 while ((length = bis.read(data)) != -1){ //写回读到的内容 bos.write(data,0,length); } } catch (IOException e) { throw new RuntimeException(e); }finally { //关闭资源 try { //关闭缓冲输出流 if (bos != null) { bos.close(); } //关闭缓冲字节输出流 if (bis != null) { bis.close(); } //关闭缓冲输入流 if (br != null) { br.close(); } if (socket != null) { //关闭socket连接 socket.close(); } }catch (IOException e){ e.printStackTrace(); } } } }; //从线程池调用线程完成指定任务 service.submit(runnable); } } catch (IOException e) { throw new RuntimeException(e); }finally { //关闭资源 if (service != null){ service.shutdown(); } //关闭资源 if(serverSocket != null){ try { serverSocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } } ``` ## 十六、第五关(封装参数+抽离任务): 1、封装参数两个方案: - 创建系统常量类(每一个属性都要是public static final进行修饰) - 将参数封装在.properties文件中,自定义工具类提供方法,通过key拿到value 2、抽离任务 - 自定义了一个Runnable接口的实现类 - 将核心代码写在实现类的run方法里面 - 主线程负责从线程池拿到线程对象并提交自定义任务 > ServerMain.java ```java package com.qinyc.server.test5; import com.qinyc.server.test5.task.RunnableTask; import com.qinyc.server.test5.util.LoadArgs; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Classname ServerMain * @Description 服务器主机---(封装参数、封装线程、常数文件化) * 1.参数信息保存在config.properties、并且封装一个方法能够访问到这些参数或者创建一个系统常量类,psf常数 * 2.自定义线程(线程池分配并提交任务) * @Date 2023/11/6 16:39 * @Created by qinyc * @Version v1.0.0 */ public class ServerMain { public static void main(String[] args) { //搭建TCP服务器 ServerSocket serverSocket = null; Socket socket = null; ExecutorService service = Executors.newFixedThreadPool(Integer.parseInt(LoadArgs.getValue("thread_count"))); try { serverSocket = new ServerSocket(Integer.parseInt(LoadArgs.getValue("server_port"))); System.out.println("服务器启动成功,等待客户端连接!"); while (true){ socket = serverSocket.accept(); System.out.println("客户端"+socket+"连接成功!"); //分配线程 service.submit(new RunnableTask(socket)); } } catch (IOException e) { throw new RuntimeException(e); }finally { //关闭资源 if (service != null){ service.shutdown(); } if (serverSocket != null){ try { serverSocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } } ``` > Constant.java ```java' package com.qinyc.server.test5.system; /** * @Classname Constant * @Description 系统常量类 * @Date 2023/11/9 15:22 * @Created by qinyc * @Version v1.0.0 */ public class Constant { /** * 端口号 */ public static final int PORT = 9999; /** * 线程数 */ public static final int THREAD_COUNT = 10; /** * 根路径 */ public static final String ROOT_PATH = "D:/resource"; /** * 首页路径 */ public static final String HOME_PATH = "/index.html"; /** * 错误路径 */ public static final String ERROR_PATH = "/error.html"; /** * 默认路径 */ public static final String DEFAULT_PATH = "/"; /** * 成功响应行 */ public static final String SUCCESS_RESPONSE_LINE = "HTTP/1.1 200 OK \r\n"; /** * 错误响应行 */ public static final String ERROR_RESPONSE_LINE = "HTTP/1.1 404 NOT FOUND \r\n"; /** * 换行 响应空行 */ public static final String PRINTLN_RESPONSE_NULL_LINE = "\r\n"; } ``` > RunnableTask.java ```java package com.qinyc.server.test5.task; import com.qinyc.server.test5.util.LoadArgs; import java.io.*; import java.net.Socket; /** * @Classname RunnableTask * @Description Runnable接口实现类(完成的任务) * @Date 2023/11/7 23:26 * @Created by qinyc * @Version v1.0.0 */ public class RunnableTask implements Runnable{ /** * 套接字 */ private Socket socket; /** * 根路径 */ String rootPath; /** * 首页文件 */ String indexFile; /** * 错误文件 */ String errorFile; public RunnableTask(Socket socket) { this.socket = socket; this.rootPath = LoadArgs.getValue("root_path"); this.indexFile = LoadArgs.getValue("index_file"); this.errorFile = LoadArgs.getValue("error_file"); } //核心逻辑 @Override public void run() { /** * 请求处理 */ //声明缓冲字符输入流 BufferedReader br = null; //声明缓冲字节输入流 BufferedInputStream bis = null; //声明缓冲字节输出流 BufferedOutputStream bos = null; try { //初始化缓冲字符输入流 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //初始化缓冲字节输出流 bos = new BufferedOutputStream(socket.getOutputStream()); //读取请求行 String requestLine = br.readLine(); //分割字符串 String[] requestLineData = requestLine.split("[ ]"); //创建文件 File file = "/".equals(requestLineData[1]) ? new File(rootPath+indexFile) : new File(rootPath + requestLineData[1]); //判断文件是否存在 if (file.exists()){ //存在--->指向对应文件 //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(file)); //写回响应状态行 bos.write("HTTP/1.1 200 OK \r\n".getBytes()); }else { //不存在-----> 指向error页面 //初始化缓冲字节输入流 bis = new BufferedInputStream(new FileInputStream(new File(rootPath+errorFile))); //写回响应状态行 bos.write("HTTP/1.1 404 NOT FOUND \r\n".getBytes()); } //写回响应消息报头(省略) //写回响应空行 bos.write("\r\n".getBytes()); //写回响应体 //创建字节数组 byte[] data = new byte[1024]; //定义一个变量记录读取的长度 int length; //读文件 while ((length = bis.read(data)) != -1){ //写回客户端 bos.write(data,0,length); } } catch (IOException e) { throw new RuntimeException(e); }finally { //关闭资源 if (bos != null){ try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } if (bis != null){ try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } if (br != null){ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (socket != null){ try { socket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } } ``` > LoadArgs.java ```java package com.qinyc.server.test5.util; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; /** * @Classname LoadArgs * @Description 加载参数 * @Date 2023/11/7 20:53 * @Created by qinyc * @Version v1.0.0 */ public class LoadArgs { private static Properties properties; static { try { //初始化 properties = new Properties(); //加载文件 properties.load(new FileInputStream("D:\\code\\idea_code\\My_WEB_Server\\config\\config.properties")); } catch (IOException e) { throw new RuntimeException(e); } } public static String getValue(String key) { String value = (String) properties.get(key); return value; } } ``` > D:\code\idea_code\xsyb_Server\config\config.properties ```properties # 系统常量 # 端口号 server_port=9999 # 线程个数 thread_count=10 # 根路径 root_path=D:/resource # 首页路径 home_path=/index.html # 错误路径 error_path=/error.html # 成功响应行 success_response_line=HTTP/1.1 200 OK \r\n # 失败响应行 error_response_line=HTTP/1.1 404 NOT FOUND\R\N # 换行 println_response_null_line=\r\n ``` ## 十七、第六关(封装请求): 1、将请求相关的行为封装成接口(抽象方法) 2、创建接口的实现类(属性+方法) ## 十八、第七关(封装响应): ## 十九、第八关(动态资源): ## 二十、第九关(内网访问): - 打开系统设置 ![image-20231110151423989](README.assets/image-20231110151423989.png) - 打开Windows安全中心 ![image-20231110151644912](README.assets/image-20231110151644912.png) - 选择防火墙和网络保护 ![image-20231110151726437](README.assets/image-20231110151726437.png) - 打开正在使用的防火墙 ![image-20231110151821338](README.assets/image-20231110151821338.png) - 关闭正在使用的防火墙 ![image-20231110151857559](README.assets/image-20231110151857559.png) - WIN+R,输入CMD查看当前设备的ipv4地址 ![image-20231110151948257](README.assets/image-20231110151948257.png) - 输入ipconfig查看局域网ip ![image-20231110152053229](README.assets/image-20231110152053229.png) - 启动服务器项目,浏览器访问局域网ip+端口即可 ![image-20231110152227019](README.assets/image-20231110152227019.png) ![image-20231110152241643](README.assets/image-20231110152241643.png) ## 二十一、第十关(公网访问【内网穿透】): > 使用该工具:NATAPP > > [NATAPP官网](https://natapp.cn/) > > [NATAPP官方说明文档](https://natapp.cn/article/natapp_newbie) - 官网 ![image-20231110152540363](README.assets/image-20231110152540363.png) > 第一步:注册账号,完成登录并且进行实名认证 ![image-20231110152910472](README.assets/image-20231110152910472.png) > 购买隧道(参考官方说明文档) > > - 每个账号可以购买两条免费隧道 > - 根据项目需要选择合适的通道 > - Web协议:通常用于HTTP协议的测试 > - TCP协议:通常用于安全传输下的测试 > - UDP协议:通常用于效率传输下的测试 ![image-20231110153625675](README.assets/image-20231110153625675.png) ![image-20231110153732929](README.assets/image-20231110153732929.png) > 购买成功后,在我的隧道里可以看见购买的隧道 ![image-20231110154024260](README.assets/image-20231110154024260.png) > 下载客户端,将压缩包里的Natapp.exe文件解压到任意目录 ![image-20231110154140150](README.assets/image-20231110154140150.png) ![image-20231110154203657](README.assets/image-20231110154203657.png) > 双击打开Natapp.exe文件,会自动打开命令行窗口 ![image-20231110154310417](README.assets/image-20231110154310417.png) > 输入命令: > > ```shell > natapp -authtoken=自己的authtoken > ``` ![image-20231110154439543](README.assets/image-20231110154439543.png) ![image-20231110154527588](README.assets/image-20231110154527588.png) > 访问成功 ![image-20231110154603354](README.assets/image-20231110154603354.png) > 退出工具: > > - CTRL+C > - 关闭命令行窗口 ![image-20231110154658380](README.assets/image-20231110154658380.png)