一、项目介绍
1.1功能
当客户端向服务器发送http请求时,服务器根据客户端http请求中的url跳转到指定页面显示内容
1.2需要用到的基本知识
1.xml文件解析
2.集合
3.IO流
4.基础socket网络编程
5.多线程
二、项目目录的介绍
2.1包的介绍
1.core包
服务器程序的核心,后期进入javaweb开发时,此包下的类是不用程序员自己写的。
2.user包
存放url反射的类,对于不同的url,根据xml文件中的配置,加载对应的类,其中每个类都实现了Servlet接口,通过Servlet接口中的service方法,加载不同的html文件。
3.xml包
存放xml文件,可以灵活增加修改xml中的信息,解耦了页面与代码,便于维护和扩展。
2.2类的介绍
1.service01类
package com.lyz.server.core;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server01 {
//服务器端的socket,用于阻塞式接收客户端socket发来的请求
private ServerSocket serverSocket;
//用于判断程序是否还需要运行
private boolean isRunning;
public static void main(String[] args) {
Server01 server01 = new Server01();
server01.start();
}
public void start() {
try {
//服务器监听端口8888
serverSocket = new ServerSocket(8888);
isRunning = true;
receive();
} catch (IOException e) {
e.printStackTrace();
System.out.println("连接建立失败");
}
}
public void receive() {
while(isRunning) {
try {
Socket accept = serverSocket.accept();
//开启一个新线程
new Thread(new Dispacher(accept)).start();
} catch (IOException e) {
//这里可以添加对应的http错误代码
e.printStackTrace();
System.out.println("客户端出了问题");
}
}
}
//在适当的时候需要结束某个客户端连接的线程,释放资源
public void stop() {
isRunning = false;
try {
serverSocket.close();
System.out.println("服务器已停止");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.Dispacher类
/* 用于实现多线程访问 */
package com.lyz.server.core;
import java.io.IOException;
import java.net.Socket;
public class Dispacher implements Runnable {
//服务器端的socket
private Socket accept;
//封装好的http请求消息,会在下面介绍
private Request request;
//封装好的http响应消息
private Response response;
public Dispacher(Socket accept) {
this.accept = accept;
}
@Override
public void run() {
try {
//在创建Request对象的时候,完成了通过socket读入http请求的过程
request = new Request(accept);
/* 创建Response对象后必须用pushToBrowser(int)返回一个状态码,
响应消息才会发出 */
response = new Response(accept);
/* WebApp中只封装了一个通过url创建并返回对应的Servlet实现类*/
Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
if(servlet!=null) {
/*这里注意service方法必须先执行,因为响应正文是
在service方法中执行的*/
servlet.service(request,response);
response.pushToBrowser(200);
} else {
response.pushToBrowser(404);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//响应消息法出后记得释放资源哦
release();
}
}
public void release() {
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.Request类
必须说明的是这个请求消息的处理,作者完成的并不好,写之前还没有学过正则表达式处理,如果使用split分割字符串,则可以避免复杂又低效的循环处理。
/*必须说明的是
*/
package com.lyz.server.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
public class Request {
//http请求消息全文
private String protocol;
//请求方式
private String method;
//url
private String url;
//参数列表
private String queryStr;
//将参数封装进Map集合,因为存在多选输入的情况故采用Map
private Map<String,String> parameterMap = new HashMap<>();
public Request(Socket accept) {
InputStream inputStream = null;
try {
//在构造方法中读取客户端通过socket发来的请求消息
inputStream = accept.getInputStream();
byte[] buffer = new byte[1024*1024];
int len;
len = inputStream.read(buffer);
//这里编码的方式完全取决于客户端发送的消息编码,出现乱码就是客户端的原因
this.protocol = new String(buffer,0,len,"utf-8");
} catch (IOException e) {
e.printStackTrace();
}
try {
//解析字符串
parseHttp();
} catch (UnsupportedEncodingException e) {
System.out.println("url解析出错");
e.printStackTrace();
}
}
//这里可以采用更简单高效的split分割函数处理哟
private void parseHttp() throws UnsupportedEncodingException {
int id1 = protocol.indexOf("/");
int id2 = protocol.indexOf("HTTP/");
int id3,id4,id5;
String queryTemp;
method = protocol.substring(0,id1-1);
url = protocol.substring(id1,id2-1);
if("GET".equals(method)) {
if((id3=url.indexOf('?'))!=-1) {
queryTemp = queryStr = url.substring(id3+1);
while((id4=queryTemp.indexOf('&'))!=-1) {
id5 = queryTemp.indexOf('=');
/*注意url采用自己独有的编码方式,必须采用URLDecoder类的
decode方法解码,否则url中的中文会出现乱码*/
parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
URLDecoder.decode(queryTemp.substring(id5+1,id4),"utf-8"));
queryTemp = queryTemp.substring(id4+1);
}
id5 = queryTemp.indexOf('=');
parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
URLDecoder.decode(queryTemp.substring(id5+1),"utf-8"));
}
} else if("POST".equals(method)) {
//POST方式中参数不出现在url中,用两个回车换行去定位它
id3=protocol.lastIndexOf("\r\n\r\n");
if(id3!=-1) {
queryTemp = protocol.substring(id3).trim();
queryStr = queryTemp;
while((id4=queryTemp.indexOf('&'))!=-1) {
id5 = queryTemp.indexOf('=');
/*这里可以不用decode解码,因为POST方式中请求参数不出现在url
中而是出现在请求正文中
*/
parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
URLDecoder.decode(queryTemp.substring(id5+1,id4),"utf-8"));
queryTemp = queryTemp.substring(id4+1);
}
id5 = queryTemp.indexOf('=');
parameterMap.put(URLDecoder.decode(queryTemp.substring(0,id5),"utf-8"),
URLDecoder.decode(queryTemp.substring(id5+1),"utf-8"));
}
}
}
public String getProtocol() {
return protocol;
}
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getQueryStr() {
return queryStr;
}
public Map<String, String> getParameterMap() {
return parameterMap;
}
}
4.Response类
package com.lyz.server.core;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.Date;
public class Response {
//响应消息头部
private StringBuilder headInfo;
//响应消息正文
private StringBuilder content;
//记录正文长度,因为http必须根据正文长度判断响应消息是否结束
private int len;
//缓冲输出字符流,将响应消息通过soket返回客户端
private BufferedWriter bufferedWriter;
private static final String CRLF = "\r\n";
private static final String BLANK = " ";
private Response() {
headInfo = new StringBuilder();
content = new StringBuilder();
len = 0;
}
public Response(Socket accept) throws IOException {
this();
/*注意输出字符的编码格式,若这里出现问题,则响应消息中正文会出现乱码*/
bufferedWriter = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream(),"UTF-8"));
}
//此方法用于添加响应消息正文
public Response addContent(String info) {
this.content.append(info);
try {
//注意正文长度是字节长度
len += info.getBytes("utf-8").length;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return this;
}
//每一个响应消息都附带一个状态码用来标识连接的状态
public Response pushToBrowser(int code) throws IOException {
createHeadInfo(code);
bufferedWriter.write(headInfo.toString());
bufferedWriter.write(content.toString());
bufferedWriter.flush();
return this;
}
public void createHeadInfo(int code) {
//1、响应行: HTTP/1.1 200 OK
headInfo.append("HTTP/1.1").append(BLANK);
headInfo.append(code).append(BLANK);
switch(code) {
case 200:
headInfo.append("OK").append(CRLF);
break;
case 404:
headInfo.append("NOT FOUND").append(CRLF);
break;
case 505:
headInfo.append("SERVER ERROR").append(CRLF);
break;
}
//2、响应头(最后一行存在空行):
headInfo.append("Date:").append(new Date()).append(CRLF);
headInfo.append("Server:").append("shsxt?Server/0.0.1;charset=UTF-8").append(CRLF);
headInfo.append("Content-type:text/html").append(CRLF);
headInfo.append("Content-length:").append(len).append(CRLF);
headInfo.append(CRLF);
}
}
5.WebApp类
package com.lyz.server.core;
import java.lang.reflect.InvocationTargetException;
public class WebApp {
/*WebContext类中封装了两个Map集合,
分别存储url->servlet-name映射和servlet-name->servlet-class映射*/
private static WebContext webContext;
static {
/*XMLParse用来解析xml文件,将得到的信息封装为
包含url、servlet-name属性类的List集合mappings和包含
servlet-class、servlet-name属性类的List集合entities*/
//xml加载路径是以IDEA下的项目目录为根,而不是模板为根
XMLParse xmlParse = new XMLParse("Server\\xml\\web.xml");
//WebContext将两个List集合转换为Map集合
webContext = new WebContext(xmlParse.entities,xmlParse.mappings);
}
public static Servlet getServletFromUrl(String url) {
Servlet servlet =null;
String servletClassName = webContext.getEntityMap().get(webContext.getMappingMap().get(url));
try {
servlet = (Servlet) Class.forName(servletClassName).getConstructor().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
return servlet;
}
}
6.WebContext类
package com.lyz.server.core;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class WebContext {
private Map<String,String> entityMap = new HashMap<>();
private Map<String,String> mappingMap = new HashMap<>();
public WebContext(List<Entity> entityList,List<Mapping> mappingList) {
Iterator<Entity> iterator = entityList.iterator();
while(iterator.hasNext()) {
Entity entity = iterator.next();
this.entityMap.put(entity.getServlet_name(),entity.getServlet_class());
}
Iterator<Mapping> iterator1 = mappingList.iterator();
while(iterator1.hasNext()){
Mapping mapping = iterator1.next();
Iterator<String> iterator2 = mapping.getUrl_pattern().iterator();
while(iterator2.hasNext()) {
String url = iterator2.next();
this.mappingMap.put(url,mapping.getServlet_name());
}
}
}
public Map<String, String> getEntityMap() {
return entityMap;
}
public Map<String, String> getMappingMap() {
return mappingMap;
}
public String urlIndexClass(String url) {
return this.entityMap.get(this.mappingMap.get(url));
}
}
7.XMLParse类
采用SAX解析技术
package com.lyz.server.core;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class XMLParse {
List<Entity> entities;
List<Mapping> mappings;
public XMLParse(String pathXML){
EntityServletMappingHandler handler = null;
try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParser saxParser = saxParserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
handler = new EntityServletMappingHandler();
xmlReader.setContentHandler(handler);
xmlReader.parse(pathXML);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
entities = handler.getEntities();
mappings = handler.getMappings();
}
}
class EntityServletMappingHandler extends DefaultHandler {
private Entity entity;
private Mapping mapping;
private List<Entity> entities;
private List<Mapping> mappings;
private String tag;
private String classTag;
@Override
public void startDocument() throws SAXException {
super.startDocument();
entities = new ArrayList<>();
mappings = new ArrayList<>();
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
this.tag = qName;
if("servlet".equals(qName)) {
this.classTag = "servlet";
this.entity = new Entity();
} else if("servlet-mapping".equals(qName)) {
this.classTag = "servlet-mapping";
this.mapping = new Mapping();
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if("servlet".equals(qName)) {
this.entities.add(this.entity);
this.classTag = null;
} else if("servlet-mapping".equals(qName)) {
this.mappings.add(mapping);
this.classTag = null;
}
this.tag = null;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
String contents = new String(ch,start,length);
if(contents.length()>0) {
if("servlet".equals(classTag)) {
if ("servlet-name".equals(this.tag)) {
this.entity.setServlet_name(contents);
} else if ("servlet-class".equals(this.tag)) {
this.entity.setServlet_class(contents);
}
} else if("servlet-mapping".equals(this.classTag)) {
if("servlet-name".equals(this.tag)) {
this.mapping.setServlet_name(contents);
} else if("url-pattern".equals(this.tag)) {
this.mapping.getUrl_pattern().add(contents);
}
}
}
}
public List<Entity> getEntities() {
return entities;
}
public List<Mapping> getMappings() {
return mappings;
}
}
8.Entity类与Mapping类
作为记录xml文件内容的类
package com.lyz.server.core;
public class Entity {
private String servlet_name;
private String servlet_class;
@Override
public String toString() {
return "Entity{" +
"servlet_name='" + servlet_name + '\'' +
", servlet_class='" + servlet_class + '\'' +
'}';
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Entity) {
Entity tempEntity = (Entity)obj;
return this.servlet_name.equals(tempEntity.servlet_name);
} else {
return false;
}
}
public String getServlet_name() {
return servlet_name;
}
public void setServlet_name(String servlet_name) {
this.servlet_name = servlet_name;
}
public String getServlet_class() {
return servlet_class;
}
public void setServlet_class(String servlet_class) {
this.servlet_class = servlet_class;
}
public Entity() {
}
}
package com.lyz.server.core;
import java.util.HashSet;
public class Mapping {
private String servlet_name;
private HashSet<String> url_pattern = new HashSet<>();
@Override
public String toString() {
return "Mapping{" +
"servlet_name='" + servlet_name + '\'' +
", url_pattern=" + url_pattern +
'}';
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Mapping) {
Mapping obj1 = (Mapping) obj;
return this.servlet_name.equals(obj1.servlet_name);
} else {
return false;
}
}
public String getServlet_name() {
return servlet_name;
}
public void setServlet_name(String servlet_name) {
this.servlet_name = servlet_name;
}
public HashSet<String> getUrl_pattern() {
return url_pattern;
}
public void setUrl_pattern(HashSet<String> url_pattern) {
this.url_pattern = url_pattern;
}
public Mapping() {
}
public void addPattern(String pattern) {
this.url_pattern.add(pattern);
}
}
9.Servlet接口
package com.lyz.server.core;
public interface Servlet {
void service(Request request,Response response);
}
10.LoginSrvlet与RegistServlet
package com.lyz.server.user;
import com.lyz.server.core.Request;
import com.lyz.server.core.Response;
import com.lyz.server.core.Servlet;
public class LoginServlet implements Servlet {
@Override
public void service(Request request, Response response) {
response.addContent("<html>");
response.addContent("<head>");
response.addContent("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
response.addContent("</head>");
response.addContent("</html>");
response.addContent("登录页面");
}
}
package com.lyz.server.user;
import com.lyz.server.core.Request;
import com.lyz.server.core.Response;
import com.lyz.server.core.Servlet;
public class RegisterServlet implements Servlet {
@Override
public void service(Request request, Response response) {
response.addContent("<html>");
response.addContent("<head>");
response.addContent("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
response.addContent("</head>");
response.addContent("</html>");
response.addContent("注册页面");
}
}
前端页面可以采用更方便的jsp书写。
2.3 xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.lyz.server.user.LoginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>com.lyz.server.user.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
</web-app>
三、效果展示
Get请求
POST请求
四、收获与总结
通过这个小项目,熟悉了服务器的基本原理,融会贯通了java基础部分的知识