介绍
当我们在浏览器中输入网址,然后敲击回车后,浏览器会依据HTTP协议向服务器发送请求协议,服务器接收到请求协议后,获取对应数据,然后返回响应协议及数据。
HTTP协议属于应用层协议,依据计算机网络知识及TCP/IP模型,我们知道,网络中的信息传输都是从发送端的对应层(在本文中即是应用层)将信息从上到下层层封装到最底层(物理层),然后物理层通过光纤等物理传输设备将信息传到接收端的物理层,接收端将信息从物理层向上层层解封传输到对应层。
应用层的下一层是传输层,传输层的协议有TCP协议和UDP协议。本文将使用TCP编程进行数据传输。即在浏览器端只需要输入网址,然后提交即可,除此外不需要对浏览器端进行任何操作,它会自动将信息封装到物理层,然后进行传输,在服务器端,只需要在传输层处理数据即可,因为在服务器端的对应层已将数据进行解封。
来张图片(只是展示了从浏览器端向服务器端传送数据的过程,即请求过程):
目的
编写简单的web服务器响应程序,使其可以接收请求协议,并且能够返回带有数据的响应协议。
逻辑
逻辑讲解
- Browser端http网址访问
(1)一个URL可以唯一定位网络中的一个资源。
URL由三部分组成:资源类型、存放资源的主机域名、资源文件名;也可认为由4部分组成:协议、主机、端口、路径。
URL的一般语法格式为:(带方括号[]的为可选项):
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
协议为https协议时的一个例子:https://blog.csdn.net/qq_37665301
。这是我的csdn博客首页,其中blog.csdn.net
可以定位到csdn的服务器,qq_37665301
定位到csdn为我组织的博客首页。
可以认为网址就是URL。
(2)浏览器请求数据过程。
浏览器依据URL中的hostname[:port]
将请求发送到对应的服务器,服务器依据path
找到对应数据,然后将数据发送给浏览器。
(3)浏览器端请求数据的方式。
一般有两种:get 和 post。它们的一个区别在于数据在url中是否是可见的,这也导致了http网址的不同。
(4)我在实验中所使用的部分测试URL。
http://localhost:8888/o
http://localhost:8888/login
http://localhost:8888/g
http://localhost:8888/reg
- 服务器循环接受浏览器的请求。
- 功能:总控制程序,同时负责与浏览器端进行数据交互。
(1)服务器程序入口。 - 原理:server.java是服务器主程序,同时是服务器的程序入口。它负责接受浏览器的连接请求,建立与浏览器的连接,然后为此次请求建立一个线程,在线程中处理此次请求事件。
- 技术:通过ServerSocket指定服务器端口,并启动服务器;通过accept()阻塞式接受请求连接;通过new Thread(…).start();启动线程处理程序。
(2)多线程处理程序。 - 原理:Dispatcher.java设置在每个线程中进行:请求处理–>服务器中请求数据对应处理类的地址查找–>获取响应数据–>构建报文并向浏览器发送此报文。
- 技术:Dispatcher.java通过继承Runnable接口,重写run()方法来实现多线程;通过Request对象进行请求处理;通过WebAnalysis对服务器配置文件进行分析,查找请求数据对应的处理类的地址;通过反射创建Servlet对象,然后获取响应数据;通过Response对象构建协议报文,并发送给浏览器端。
- 请求处理
- 功能:接受浏览器端的数据请求报文,然后解析此报文,获取URL中的
path
和[;parameters]
。 - 原理:服务器端接收到的是基于http协议下的请求报文,服务器想返回浏览器请求的数据就需要知道报文中的
path
,path
能帮助服务器找到对应数据。 - 技术:通过字符串分割技术提取path,同时注意请求方式(get/post)的不同,会导致请求报文不同,需要分别分析。
- 服务器中请求数据对应处理类的地址查找
(1)服务器配置文件
- 说明:在实验中使用自己写的servlet.xml来模拟服务器配置文件。首先servlet.xml的作用是映射,是我们可以通过 URL 中的
path
就可以找到对应的处理类地址。 - 示例:
下面是servlet.xml中的一段数据
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>dxt.server.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
通过 servlet-mapping 中的 url-pattern 可以找到 servlet-name,然后可以通过 servlet-name 可以在 servlet 中找到对应处理类的地址 servlet-class。当我们有了地址之后,就可以通过 反射技术 来使用此类,来处理此次请求。
假如有一个URL:http://localhost:8888/g
,我们从URL中提取出 g
,然后在xml文件中找url-pattern为/g
的servlet-mapping,然后获得此servlet-mapping中的servlet-name,即login
,然后在找setvlet中servlet-name为login
的,既可以获得值为dxt.server.user.LoginServlet
的servlet-class。
(2)xml数据处理
- 原理:WebAnalysis.java负责解析xml文件,并提供方法是使用者通过URL就可以获取xml文件中的servlet-class。WebHandler.java 、WebContext.java、Entity.java和Mapping.java都服务于WebAnalysis.java。
- 技术:Sax方式解析xml文件;WebHandler.java即为Sax解析中的处理器类。
(3)解释 - 为什么要使用服务器配置文件?有时候服务器中的文件名过长,不适合直接使用在URL中;在此次实验中,我们通过URL去找一个服务器端一个处理类的地址,不应该让使用浏览器的人直接使用此地址,也不方便直接使用。
- 为什么找对应处理类的地址,而不是找服务器中的数据?我们在对应的类中构造浏览器端请求的数据。
- 获取响应数据
(1)Servlet接口
- 解释:Servlet.java 实现了一个接口,声明了一个service()方法;LoginServlet.java 等都是不同实现Servlet接口的数据处理类。
- 作用:我们可以声明Servlet对象,然后使用得到的对应数据处理类的地址(servlet-class),利用反射技术创建对应处理的对象,已处理不同的请求。
(2)LoginServlet.java等 - 功能:就是构建对应的响应信息。比如RegisterServlet.java是处理注册请求的类,在此类中就是构建一个注册页面(html),然后将此页面数据发送到浏览器端,浏览器将数据以注册网页的形式呈现给用户。
- 构建报文并向浏览器发送此报文
- 功能:依据Servlet得到的数据和http响应协议的格式构建响应报文,然后发送此报文给浏览器端。
- 技术:http响应报文格式;字符串的拼接
代码
- Server.java
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器响应程序
* 多线程处理,可以同时处理多个请求
* @author dxt
*
*/
public class Server {
private ServerSocket serverSocket;
private boolean isRunning;
public static void main(String[] args){
Server s = new Server();
s.start();
}
/**
* 启动服务器响应程序,开始接收请求信息
*/
public void start(){
try {
serverSocket = new ServerSocket(8888);
isRunning = true;
receive();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败。");
stop();
}
}
/**
* 接收请求信息,返回响应信息
*/
public void receive(){
while(isRunning){
try {
Socket client = serverSocket.accept();
System.out.println("一个客户端已建立连接");
//多线程处理
new Thread(new Dispatcher(client)).start();
} catch (IOException e) {
System.out.println("客户端错误。");
e.printStackTrace();
}
}
}
/**
* 关闭服务器
* 服务器监听程序应该一直处于运行状态
*/
public void stop(){
isRunning = false;
try {
serverSocket.close();
System.out.println("服务器停止运行。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- Dispatcher.java
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
* 多线程实现
* @author dxt
*
*/
public class Dispatcher implements Runnable{
private Socket client;
private Request request;
private Response response;
public Dispatcher(Socket client){
this.client = client;
try {
this.request = new Request(client);
this.response = new Response(client);
} catch (IOException e) {
e.printStackTrace();
this.release();
}
}
public void run() {
try{
//获取servlet,得到url(请求处理+服务器文件地址查找)
Servlet servlet = WebAnalysis.getServletFromUrl(request.getUrl());
if(null != servlet){
//运行对应的服务(对应处理类获取对应数据)
servlet.service(request, response);
//服务器返回响应信息
response.push(200);
}else{
//错误页面
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("./error.html");
response.print(is.toString());
is.close();
response.push(404);
}
}catch(Exception e){
response.print("");
response.push(500);
}
//处理完即释放资源
this.release();
}
/**
* 释放client资源
*/
private void release(){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- Request.java
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 解析请求协议:
* (1)获取method (get还是post或...)、url以及请求参数
* (2)将请求参数存储到Map结构中
* @author dxt
*
*/
public class Request {
//请求协议信息
private String requestInfo;
//请求方法
private String method;
//请求url
private String url;
//请求参数
private String queryStr;
//存储参数
private Map<String, List<String>> parameterMap;
private final String CRLF = "\r\n"; //换行符
public Request(InputStream is){
parameterMap = new HashMap<String, List<String>>();
byte[] datas = new byte[1024*1024];
int len;
try {
len = is.read(datas);
requestInfo = new String(datas, 0, len);
System.out.println(requestInfo);
} catch (IOException e) {
e.printStackTrace();
return;
}
//分解字符串
parseRequestInfo();
}
public Request(Socket client) throws IOException{
this(client.getInputStream());
}
public String getMethod(){
return this.method;
}
public String getUrl(){
return this.url;
}
public String getQueryStr(){
return this.queryStr;
}
/**
* 分解请求协议,获取对应信息
*/
private void parseRequestInfo(){
//1. 获取请求方法,转成小写。请求方法:开始到第一个"/"
this.method = this.requestInfo.substring(0, this.requestInfo.indexOf("/")).trim().toLowerCase();
System.out.println(this.method);
//2. 获取请求的url。第一个"/" 到 "HTTP/",如果有"?",则"?"前的为url
// 如果是get方法,则url中有请求参数
// 如果是post方法,则url中没有请求参数
//2.1 获取/的位置
int startIdx = this.requestInfo.indexOf("/")+1;
//2.2 获取HTTP/的位置
int endIdx = this.requestInfo.indexOf("HTTP/");
//2.3 分割字符串
this.url = this.requestInfo.substring(startIdx, endIdx);
//2.4 获取?的位置
int queryIdx = this.url.indexOf("?");
if(queryIdx >= 0){ //表示存在?,?前为url,?后为请求参数
String[] urlArray = this.url.split("\\?");
this.url = urlArray[0].trim();
queryStr = urlArray[1].trim(); //请求参数
}
System.out.println(this.url);
//3. 获取请求参数,如果是get,则已获取;如果是post,则参数在请求体中
if(method.equals("post")){
String qStr = this.requestInfo.substring(this.requestInfo.lastIndexOf(CRLF)).trim();
if(null == queryStr){
queryStr = qStr;
}else{
queryStr += "&" + qStr;
}
}
queryStr = null == queryStr?"":queryStr;
System.out.println(this.queryStr);
System.out.println(this.method+"---"+this.url+"---"+this.queryStr);
//转成Map
convertMap();
}
/**
* 将请求参数以map形式存储
*/
private void convertMap(){
//分割字符串
String[] keyValues = this.queryStr.split("&");
for(String queryInfo:keyValues){
String[] kv = queryInfo.split("=");
kv = Arrays.copyOf(kv, 2);
//获取key和value
String key = kv[0];
String value = kv[1]==null?null:decode(kv[1], "utf-8");
//存储到map中
if(!parameterMap.containsKey(key)){ //第一次
parameterMap.put(key, new ArrayList<String>());
}
parameterMap.get(key).add(value); //添加内容
}
}
/**
* 处理中文
* @param value
* @param enc
* @return
*/
private String decode(String value, String enc){
try {
return java.net.URLDecoder.decode(value, enc);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 通过name返回对应的多个值
* @param key
* @return
*/
public String[] getParameterValues(String key){
List<String> values = this.parameterMap.get(key);
if(null==values || values.size()<1){
return null;
}
return values.toArray(new String[0]);
}
/**
* 通过name获取对应的一个值
* @param key
* @return
*/
public String getParameter(String key){
String[] values = getParameterValues(key);
return values==null ? null:values[0];
}
}
- WebAnalysis.java
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* 解析xml数据
* @author dxt
*
*/
public class WebAnalysis {
private static WebContext webContext;
static{
try{
//1. 获取解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2. 从解析工厂获取解析器
SAXParser parse = factory.newSAXParser();
//3. 编写处理器 WebHandler类
//4. 加载文档注册处理器
WebHandler handler = new WebHandler();
//5. 进行解析
parse.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("servlet.xml"), handler);
//获取数据
webContext = new WebContext(handler.getEntitys(), handler.getMappings());
}catch(Exception e){
System.out.println("解析错误");
e.printStackTrace();
}
}
/**
* 通过url获取配置文件对应的servlet
* @param url
* @return
*/
public static Servlet getServletFromUrl(String url){
//反射事件
//2. 依据WebContext 获取我们想要得到的 对应的类名
//2.1 我们输入的是 url
String name = webContext.getClz("/" + url);
//3. 有了类名 ---> 使用反射 获取对应类的信息, 类名就是xml数据中的<servlet-class>
Class clz;
try {
clz = Class.forName(name);
//4. 使用反射进行实例化
Servlet s = (Servlet)clz.getConstructor(null).newInstance(null);
return s;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- WebHandler.java
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
public class WebHandler extends DefaultHandler{
private List<Entity> entitys;
private List<Mapping> mappings;
private Entity entity;
private Mapping mapping;
private String tag; //存储操作标签
private boolean isMapping = false;
public void startDocument(){
System.out.println("------文档解析开始------");
entitys = new ArrayList<Entity>();
mappings = new ArrayList<Mapping>();
}
public void startElement(String uri, String localName, String qName, Attributes attributes){
if(null != qName){ //处理换行与空格
tag = qName;
//处理对应的标签
if(tag.equals("servlet")){
entity = new Entity();
isMapping = false;
}else if(tag.equals("servlet-mapping")){
mapping = new Mapping();
isMapping = true;
}
}
}
public void characters(char[] ch, int start, int length){
String contents = new String(ch, start, length).trim();
if(null != tag){
if(isMapping){ //处理 mapping
if(tag.equals("servlet-name")){
mapping.setName(contents);
}else if(tag.equals("url-pattern")){
mapping.addPattern(contents);
}
}else{ //处理entity
if(tag.equals("servlet-name")){
entity.setName(contents);
}else if(tag.equals("servlet-class")){
entity.setClz(contents);
}
}
}
}
public void endElement(String uri, String localName, String qName){
if(null != qName){
if(qName.equals("servlet")){
entitys.add(entity);
}else if(qName.equals("servlet-mapping")){
mappings.add(mapping);
}
}
tag = null;
}
public void endDocument(){
System.out.println("------文档解析结束------");
}
public List<Entity> getEntitys(){
return this.entitys;
}
public List<Mapping> getMappings(){
return this.mappings;
}
}
- WebContext.java
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 目的: 根据url-pattern 找 servlet-class
* 对于webxml数据
* 可以依据<servlet-mapping> 中的pattern 找到 name
* 在根据 name 在<servlet> 中找到class
* @author dxt
*
*/
public class WebContext {
private List<Entity> entitys;
private List<Mapping> mappings;
//创建两个map数据,帮助解决问题
//key:servlet-name value:servlet-class
private Map<String, String> entityMap = new HashMap<String, String>();
//key:url-pattern value:servlet-name
private Map<String, String> mappingMap = new HashMap<String, String>();
public WebContext(List<Entity> entitys, List<Mapping> mappings){
this.entitys = entitys;
this.mappings = mappings;
//将entity的List转成了map
for(Entity entity : entitys){
entityMap.put(entity.getName(), entity.getClz());
}
//将mapping的List转为map
for(Mapping mapping : mappings){
for(String pattern : mapping.getPatterns()){
mappingMap.put(pattern, mapping.getName());
}
}
}
/**
* 依据url-pattern 找对应的 servlet-class
* 一个url-pattern唯一对应一个class
* 一个class可对应多个url-pattern
* 返回servlet-class
* @param pattern
* @return
*/
public String getClz(String pattern){
String name = mappingMap.get(pattern); //找到对应name
return entityMap.get(name); //找到对应class
}
}
- Entity.java
/**
* 针对xml数据建立的类
* 请求姓名和请求类
* @author dxt
*
*/
public class Entity {
private String name;
private String clz;
public Entity(){}
public Entity(String name, String clz){
super();
this.name = name;
this.clz = clz;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setClz(String clz){
this.clz = clz;
}
public String getClz(){
return this.clz;
}
}
- Mapping.java
import java.util.HashSet;
import java.util.Set;
/**
* 针对<servlet-mapping>
* @author dxt
*
*/
public class Mapping {
private String name;
private Set<String> patterns;
public Mapping(){
patterns = new HashSet<String>();
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public Set<String> getPatterns(){
return this.patterns;
}
public void addPattern(String pattern){
this.patterns.add(pattern);
}
}
- Servlet.java
/**
* 服务器脚本 接口
* @author dxt
*
*/
public interface Servlet {
void service(Request request, Response response);
}
- LoginServlet.java
import dxt.server.core.Request;
import dxt.server.core.Response;
import dxt.server.core.Servlet;
/**
* 登录响应处理程序
* @author dxt
*
*/
public class LoginServlet implements Servlet{
/**
* 构建响应数据
*/
public void service(Request request, Response response) {
response.print("<html>");
response.print("<head>");
response.print("<title>");
response.print("服务器响应信息");
response.print("</title>");
response.print("</head>");
response.print("<body>");
response.print("我就是响应信息"+request.getParameter("uname"));
response.print("</body>");
response.print("</html>");
}
}
- Response.java
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;
/**
* 封装响应协议的类
* 注意响应报文格式:
* 状态行
* 响应头
* 空行
* 响应数据
* @author dxt
*
*/
public class Response {
private BufferedWriter bw;
//正文
private StringBuilder content;
//协议头(状态行+响应头+空行)
private StringBuilder headInfo;
private int len; //正文字节数
private final String BLANK = " "; //空格
private final String CRLF = "\r\n"; //换行符
private Response(){
super();
content = new StringBuilder();
headInfo = new StringBuilder();
len = 0;
}
public Response(Socket client){
this();
try {
bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public Response(OutputStream os){
this();
bw = new BufferedWriter(new OutputStreamWriter(os));
}
/**
* 构建头信息 状态行+响应头+空行
* @param code 状态码
*/
private void createHeadInfo(int code){
//1. 响应行
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("dxt Server/0.0.1;charset=GBK").append(CRLF);
headInfo.append("Content-type:text/html").append(CRLF);
headInfo.append("Content-length:").append(len).append(CRLF);
//3. 空行
headInfo.append(CRLF); //空行
}
/**
* 动态添加内容
* 构建响应信息--content
* @param info
* @return
*/
public Response print(String info){
content.append(info);
len+=info.getBytes().length;
return this;
}
public Response println(String info){
content.append(info).append(CRLF);
len+=(info+CRLF).getBytes().length;
return this;
}
/**
* 服务器返回响应信息
* @param code
*/
public void push(int code){
if(headInfo == null){
code = 505;
}
this.createHeadInfo(code); //封装协议信息
try {
bw.append(headInfo);
bw.append(content);
bw.flush();
} catch (IOException e) {
System.out.println("发送出错");
e.printStackTrace();
}
}
}
- servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>dxt.server.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>dxt.server.user.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>others</servlet-name>
<servlet-class>dxt.server.user.OthersServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>others</servlet-name>
<url-pattern>/o</url-pattern>
</servlet-mapping>
</web-app>
总结
相比于每一部分功能的实现,更需要学习代码的组织结构。