第12次文章:网络编程——httpserver服务器的搭建
- 2019 年 10 月 8 日
- 筆記
这周的任务太多了,服务器只学习了一半,先更新出来吧!下周继续!fighting!
一、HTML语法简介:
下面是一个网页的源码:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>第一个表单</title> </head> <body> <pre> method:请求方式 get/post get:默认方式,数据量小,安全性不高 post:量大,安全性相对高 action:请求的服务器路径 id:编号,前端区分唯一性,js中使用 name:名称,后端(服务器)区分唯一性,获取值 只要提交数据给后台,必须存在name </pre> <form method="get" action="http://localhost:8888/index.html"> 用户名:<input type = "text" name="uname" id="uname"/> 密码:<input type="password" name="pwd" id="pwd"/> 兴趣:<input type="checkbox" name="fav" value="0">篮球 <input type="checkbox" name="fav" value="1">足球 <input type="checkbox" name="fav" value="2">乒乓球 <input type="submit" value="登录"/> </form> </body> </html>
使用浏览器打开之后,显示的内容如下所示:

下面是两种不同的请求方式,服务器返回的响应信息:
1、post方式:
POST /index.html HTTP/1.1 Host: localhost:8888 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 38 Connection: keep-alive Upgrade-Insecure-Requests: 1
2、get方式:
GET /index.html?uname=dasdsai&pwd=dsadfa&fav=0&fav=1&fav=2 HTTP/1.1 Host: localhost:8888 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1
二、服务器的封装
http服务器的构建。在网络中的通信过程中,就是客户端和服务器端进行相互请求和应答方式的交互。客户端在自己这边输入服务器地址,请求获取服务器的资源信息。当服务器接收到请求信息的时候,根据已有的资源,进行响应的答复。所以在服务器的搭建过程中,主要就是将请求信息进行封装,并且进行分析,然后根据分析结果,把应答信息发送出去即可。
1、对响应信息进行封装
在对响应信息进行封装的过程中,主要的思想就是利用输出流,按照html语法格式,对应答信息进行包装,然后输出包装之后的信息块。
package com.peng.server.demo01; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.Date; import com.peng.server.util.CloseUtil; /** * 封装响应信息 * */ public class Response { //两个常量 private static String CRLF = "rn";//回车换行 private static String BLANK = " ";//空格 //流 BufferedWriter bw ; //正文 private StringBuilder content; //存储头信息 private StringBuilder headInfo; //存储正文长度,正文长度是字节长度 private int len = 0; //构造器 public Response() { headInfo = new StringBuilder(); content = new StringBuilder(); len = 0; } public Response(Socket client) { this(); try { bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); } catch (IOException e) { headInfo = null; } } public Response(OutputStream os) { this(); bw = new BufferedWriter(new OutputStreamWriter(os)); } /** * 构建正文 */ 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; } /** * 构建响应头 * 私有方法,仅供该类内部使用 */ private void createHeadInfo(int code) { //1) HTTP协议版本、代码状态、描述 headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK); switch(code) { case 200: headInfo.append("OK"); break; case 404: headInfo.append("NOT FOUND"); break; case 500: headInfo.append("SERVER ERROR"); break; }; headInfo.append(CRLF); //2) 响应头(Response Head) headInfo.append("Server:bjsxt Server/0.0.1").append(CRLF); headInfo.append("Date:").append(new Date()).append(CRLF); headInfo.append("Content-type:text/html;charset=GBK:").append(CRLF); //长度:字节长度 headInfo.append("Content-Length:").append(len).append(CRLF); headInfo.append(CRLF);//分隔符 } //推送到客户端 void pushToClient(int code) throws IOException { if(null == headInfo) { code = 500; } createHeadInfo(code); //头信息+分隔符 bw.append(headInfo.toString()); //正文 bw.append(content.toString()); bw.flush(); } public void close() { CloseUtil.closeAll(bw); } }
tips:
(1)、在html信息中,经常使用到换行和空格,所以我们预定义两个静态常量,使用的时候,直接进行调用。
(2)、在描述代码状态的时候,一般采用200代表正常状态,404代表服务器没有寻找到相关资源,500代表服务器报错。这些状态码属于一种常规操作,在编写服务器的时候,尽量使用常规定义,这样的话,在后续检查代码以及修正代码的时候,便于我们自己快速定位错误地方。
2、封装请求信息
package com.peng.server.demo01; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; /** * 封装请求信息 * */ public class Request { //请求方式 private String method; //请求资源 private String url; //请求参数 private Map<String,List<String>> parameterMapValues; //内部 public static final String CRLF = "rn"; private InputStream is ; private String requestInfo;//请求信息 //构造器 public Request() { method = ""; url = ""; parameterMapValues = new HashMap<String,List<String>>(); } public Request(InputStream is) { this(); this.is = is; try { byte[] data = new byte[20480]; int len; len = is.read(data); requestInfo = new String(data,0,len); } catch (IOException e) { return; } //分析请求信息 parseRequestInfo(); } /** * 分析请求信息 */ public void parseRequestInfo() { if(null==requestInfo || ( requestInfo.trim().equals(" "))) { return; } /** * =================================== * 从信息的首行分解出:请求方式 请求路径 请求参数(get可能存在) * 如:GBT/index.html?name123&pwd=5467 HTTP/1.1 * * 如果为post方式,请求参数可能在最后正文中 * * =================================== */ //接收请求参数 String paramString =""; //1、获取请求方式 String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF)); int idx =requestInfo.indexOf("/");// /的位置 this.method = firstLine.substring(0, idx).trim();//获取请求的方式 String urlStr = firstLine.substring(idx,firstLine.indexOf("HTTP/")).trim(); if(this.method.equalsIgnoreCase("post")) { this.url = urlStr; paramString = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim(); }else if(this.method.equalsIgnoreCase("get")) { if(urlStr.contains("?")) {//是否存在参数 String[] urlArray = urlStr.split("\?"); this.url = urlArray[0]; paramString = urlArray[1];//接收请求参数 }else { this.url = urlStr; } } //不存在请求参数 if(paramString.equals("")) { return; } //2、将请求参数封装到MAP中 parseParams(paramString); } /** * 将请求封装到MAP中 * @param paramString */ private void parseParams(String paramString) { //分割 将字符串转成数组 StringTokenizer token = new StringTokenizer(paramString,"&"); while(token.hasMoreTokens()) { String keyValue = token.nextToken(); String[] keyValues = keyValue.split("="); if(keyValues.length==1) { keyValues = Arrays.copyOf(keyValues, 2); keyValues[1] = null; } String key = keyValues[0].trim(); String value = null==keyValues[1]?null:decode(keyValues[1].trim(),"gbk"); //转换成Map 分拣 if(!parameterMapValues.containsKey(key)) { parameterMapValues.put(key, new ArrayList<String>()); } List<String> values = parameterMapValues.get(key); values.add(value); } } /** * 解决中文问题 * @param value * @param code * @return */ private String decode(String value,String code) { try { return java.net.URLDecoder.decode(value, code); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 根据页面的name获取对应的多个值 * @param name * @return */ public String[] getParameterValues(String name) { List<String> values = null; if((values=parameterMapValues.get(name))==null) { return null; }else { return values.toArray(new String[0]); } } /** * 根据页面的name获取对应的单个值 * @param name * @return */ public String getParameter(String name) { String[] values = getParameterValues(name); if(null==values) { return null; } return values[0]; } //获取Url的方法 public String getUrl() { return url; } }
tips:
(1)在进行获取相关值的时候,我们将其显示在网页上,将会涉及到不同编码集和解码集的转换。所以,为了避免解码集和编码集不同而导致的乱码问题,我们自定义一个指定相同解码集与编码集的方法。使用此方法,避免我们在显示时出现的乱码问题。
(2)在管理用户名和参数的问题上时,由于一个用户名可以对应有多个参数,比如密码,喜好等等,所以我们使用了MAP进行存储管理用户信息。键值对,键只有一个,我们使用用户名作为键,然后多个参数,我们使用链表进行存储,这样就可以很好的解决一个键对应多个值的问题。
3、服务器构建
package com.peng.server.demo01; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * 创建服务器并启动 * 请求并响应 * @author 赵晓鹏 * */ public class Server4 { private ServerSocket server; private static String CRLF = "rn";//回车换行 private static String BLANK = " ";//空格 public static void main(String[] args) { new Server4().start(); } /** * 启动方法 */ public void start() { try { server = new ServerSocket(8888); this.receive(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 接收客户端 */ private void receive() { try { Socket client = server.accept(); //请求 Request req = new Request(client.getInputStream()); //响应 Response rep = new Response(client.getOutputStream()); //响应信息的构建 rep.println("<html><head><title>HTTP响应示例</title>"); rep.println("</head><body>"); rep.println("欢迎").println(req.getParameter("uname")).println("回来"); rep.println("</body></html>"); rep.pushToClient(200); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
tips:经过相应的封装后,我们在建立服务器的时候,只需要进行建立响应和请求类别就可以了,大大简化服务器建立时的代码。