这里涉及网络编程的基本知识以及HTTP协议的基本认识,下面来一步一步实现一下最简单的一个web服务器。
一、用户请求域名到底发送了什么信息来
如果服务端都不知道客户端发来的是什么,那何谈对内容的解析呢?所以我们首先在服务端解析客户端的访问信息,比如我们比较关心的是请求的是什么路径:
此时我们访问地址: localhost:8888 打印出来的结果为:
其实对于我们这种比较简单的实现来说,红框的信息已经足够了。我们只要知道客户端要发来的资源名字是什么,我们根据这个名字取找响应的资源返回给客户端展示即可。由于我其实请求的是根路径,所以是/
。如果我在这里请求 localhost:8888/index.html 那么就会显示 GET /index.html HTTP/1.1
这样的信息。不再演示。
但是上面的写法是存在问题的,仅仅是演示而用,因为它会一直等待输入。
那么,不用while
一直等待,而且只需要第一行即可,那么可以这样写:
好了,不能总之只接收吧,我们服务端要给客户端点什么。
二、服务端如何响应资源给客户端展示
首先我们得有资源才能展示,假设我们要展示index.html
,我们将其暂时放在F:/webrooot
下。
内容暂时为:
只是展示一下欢迎信息而已。服务端就需要读取这个文件,然后以流的形式发送给客户端的浏览器上,浏览器再解析展示。
此时再去访问页面,就会显示欢迎的信息啦!
这里没有关闭流,也没有关闭socket
,不过下面都会关闭掉。这个不重要,重要的是,这玩意都是在主线程中做的,显然不如多线程来的快,并且全写在主线程里,肯定是不够好的。下面就用多线程来实现。
三、普通的多线程实现方式
就是在这个线程中处理数据和返回数据。
其很简单,但是如果我想显示一张图片呢?
比如我的根目录下有一张图片叫做:hh.jpg
很显然现在还是无法展示的,原因是我这里写死了是以text/html
的形式响应,但是图片正确的响应是image/jpeg
这种格式。
所以我们不能无脑写死,要进行适当的判断才行。下面贴出代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| public class ServerThread implements Runnable{ private static final String webroot = "F:\\webroot\\";
private Socket client; InputStream ins; OutputStream out; private static Map<String,String> contentMap = new HashMap<>(); static { contentMap.put("html","text/html;charset=utf-8"); contentMap.put("jpg","image/jpeg"); contentMap.put("png","image/jpeg"); }
public ServerThread(Socket client){ this.client = client; init(); }
private void init() { try { ins = client.getInputStream(); out = client.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } }
@Override public void run() { try { go(); } catch (IOException e) { e.printStackTrace(); } }
private void go() throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(ins)); String line = reader.readLine().split(" ")[1].replace("/","\\"); if(line.equals("\\")){ line += "index.html"; } System.out.println(line); File file = new File(webroot + line); if (file.exists()) { PrintWriter pw = new PrintWriter(out); InputStream i = new FileInputStream(webroot + line);
pw.println("HTTP/1.1 200 OK"); String s = contentMap.get(line.substring(line.lastIndexOf(".")+1,line.length())); System.out.println("返回的类型为:"+ s); pw.println("Content-Type: " + s); pw.println("Content-Length:" + i.available()); pw.println("Server: hello-server"); pw.println("Date:"+ new Date()); pw.println(""); pw.flush(); byte[] buff = new byte[1024]; int len = 0; while ( (len = i.read(buff)) != -1){ out.write(buff,0,len); } pw.flush(); pw.close(); i.close(); reader.close(); client.close(); }else { StringBuffer error = new StringBuffer(); error.append("HTTP /1.1 400 file not found /r/n"); error.append("Content-Type:text/html \r\n"); error.append("Content-Length:20 \r\n").append("\r\n"); error.append("<h1 >File Not Found..</h1>"); try { out.write(error.toString().getBytes()); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
|
ok,你会发现,我还添加了判断资源存在不存在的逻辑,这样显得更加健全一点。
当找得到资源得时候:
当找不到资源得时候:
四、线程池方式
其实很简单,线程池的优势以前也说过,不赘述,直接贴一下代码结束。
至此,我们完成了一个比较简单的web服务器的开发。