简单模拟webserver

准备知识

本章节将按照以下内容阐述

一、网络中进程之间是如何通信

二、两种通信协议:TCP、UDP

三、什么是Socket

四、模拟一个webserver

五、请求与响应

六、代码

网络中进程之间是如何通信?

通信嘛,我理解的就是两个进程之间互相收发数据,就像打电话一样。那么进程之间是如何通信的呢,可以参考博客https://www.cnblogs.com/CheeseZH/p/5264465.html

要实现进程通信,首先解决的问题就是如何唯一标识一个进程,否则通信无从谈起,我得知道你的电话号码才能给你打电话吧!那么在本地,可以通过进程PID来标识一个进程(PID熟悉吧,linux下可以使用kill -9 PID 来杀死一个进程),而在网络中如何标识一个进程呢,TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX  BSD的套接字(套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。)来实现网络进程之间的通信。

两种通信协议:TCP、UDP

TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket(理解为电话的一端)之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。

UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。(与本主题无关,了解即可)

什么是Socket?

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,又称"套接字"。其实我理解的套接字,就是电话的一端。

Socket通讯的过程

Server端监听某个端口是否有连接请求,Client端向Server端发出连接请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。

ServerSocket(服务端)编程步骤

1) 创建套接字,将套接字绑定到一个本地地址和端口上

2) 将套接字设置为监听模式,准备接受客户请求

3) 等客户请求到来时,接受连接请求,返回一个新的对应此连接的套接字,启动线程为当前的连接服务。

4) 返回,等另一个客户请求

5) 关闭套接字

6) Socket客户端编程步骤

7) 1.创建套接字

8) 2.向服务器发起请求

9) 和服务器进行通信

10) 关闭套接字

创建Socket

java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。服务端就是一个ServerSocket,这里,我们主要讲ServerSocket

ServerSocket的构造函数有:

a. ServerSocket()throws IOException

b. ServerSocket(int port)throws IOException

c. ServerSocket(int port, int backlog)throws IOException

d. ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException

参数说明:

a. port - 端口号

b. backlog - 指的是最大等待队列长度,超过这个数量的客户端链接过来就不会让它等待而是直接拒绝了

c. bindAddr -用于绑定服务器IP,InetAddress用来描述主机地址

注意:

如果端口被占用或者没有权限使用某些端口会抛出BindException错误。譬如1~1023的端口需要管理员才拥有权限绑定。

如果设置端口为0,则系统会自动为其分配一个端口;

backlog参数将覆盖操作系统限定的队列的最大长度. 值得注意的是, 在以下几种情况中, 仍然会采用操作系统限定的队列的最大长度:

a. backlog 参数的值大于操作系统限定的队列的最大长度,

b. backlog 参数的值小于或等于0,

c. 在ServerSocket 构造方法中没有设置 backlog 参数。

常用方法:

accept()

侦听并接受到此套接字的连接。

getInetAddress()

返回此服务器套接字的本地地址。

模拟一个webserver

web server原理很简单,在服务器上运行一个ServerSocket程序,这个程序可以监控端口。比如有一个服务器的IP是10.10.40.12,其中的一个程序myserver监控的是8080端口,当浏览器发送一个请求时,这个请求里包含了所需要的页面信息index,(如: http://10.10.40.12:8080/index),请求传到服务器,myserver程序会接受这个请求,并解析请求,(因为请求就是IO流,所以可以用JAVA中的IO操作来解析),解析后,找到请求所需要的页面,页面其实就是一个文件,用JAVA的文件操作,读出这个页面,然后通过Socket写到客户端,就可以了。

简单来说访问网站的过程是这个:用户在浏览器输入一个地址,摁回车之后,就会向服务器发送一个请求,告诉服务器它想要一个页面,服务器找到这个页面,然后给浏览器发送回来。

请求与响应

请求,当客户端发起请求时,我的理解就是客户端向浏览器发送了个字符串,这个字符串包含了一些信息,比如:

  POST /examples/default.jsp HTTP/1.0

  Accept: text/plain;text/html

  Accept-Language: en-gb

  Connection:Keep-Alive

  Host: localhost

  User-Agent: Mozilla/4.0(compatible;MSIE:4.01;Widows 98)

  Content-Length: 33

  Content-Type:application/x-www-form-urlencoded

  Accept-Encodding:gzip,deflate

  lastName=Franks&firstName=Michael

第一行是请求方法 请求资源路径 协议/协议版本,中间那部分是请求头,最后一行是请求体,这里用的POST方法,所以表单的参数会在请求体中,这行和上面的用一个空行隔开,以便解析此请求的时候能知道往下就是请求体了。

响应。服务器接收到用户的请求之后,会找到用户所要的资源(页面,图片等),再发送到客户端。响应的格式如下:

  HTTP/1.1 200 OK

  Server: Microsoft-IIS/4.0

  Date: Mon, 5 Jan 2004 13:13:33 GMT

  Content-Type: text/html

  Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT

  Content-Length: 112

 

  <html>

  <head>

  <title>HTTP Response Example</title>

  </head>

  <body>

  Welcome to Software

  </body>

  </html>

第一行的意思:协议/协议版本  状态码(成功,失败等状态用数字来表示)  状态描述,接下来是告诉客户端的一些信息,再接下来就是客户请求的页面的内容。

 

下面写代码

先看一下流程

从图中看出我们需要创建以下类来完成:

1. HttpServer 用来创建ServerSocket

2. Dispatcher 用来分发请求

3. Reqest 用来获取请求信息

4. Response 用来向客户端发送响应信息

另外,为了方便,我们还需要些一个Util类,用来写一些工具

注意:从Socket获取的inputstream/outputStream 执行关闭操作时,Socket也会关闭,所以我们要在inputStream和outputStream都完成工作时,一起关闭。

HttpServer:

package com.cn.webserver;

 

import java.io.IOException;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.UnknownHostException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

public class HttpServer {

public boolean shutdown = false;

 

public static void main(String[] args)  {

try {

new HttpServer().start();

} catch (IOException e) {

e.printStackTrace();

}

}

 

public void start() throws IOException {

int port = 8081;

int backlog = 1000;

ServerSocket serverSocket = null;

try {

serverSocket = new ServerSocket(port, backlog,InetAddress.getByName("127.0.0.1"));

while (!shutdown) {

Socket socket = serverSocket.accept();

ExecutorService es = Executors.newCachedThreadPool();

es.execute(new Dispatcher(socket));

}

} catch (IOException e) {

shutdown = true;

serverSocket.close();

e.printStackTrace();

}

}

}

Dispatcher:

package com.cn.webserver;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

import com.cn.request.Request;

import com.cn.response.Response;

import com.cn.util.Util;

 

public class Dispatcher implements Runnable{

private Socket socket;

private InputStream is;

private OutputStream os;

private int code = 200;

public Dispatcher(Socket socket){

this.socket = socket;

try {

this.is = socket.getInputStream();

this.os = socket.getOutputStream();

} catch (IOException e) {

code = 500;

e.printStackTrace();

}

}

@Override

public void run() {

try {

Request res = new Request(is);

Response resp = new Response(os);

resp.push(res.getUrl(), code);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}finally{

Util.closeIO(is);

Util.closeIO(os);

}

}

}

 

Request:

package com.cn.request;

 

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.Reader;

import java.util.HashMap;

import java.util.Map;

 

import com.cn.util.Util;

 

public class Request {

private InputStream in;

private Map parameterMap;

private String url;

private String method;

public Request(InputStream in){

this.in = in;

this.parameterMap = new HashMap();

parse();

}

private void parse() {

String requestHead = read();

if(requestHead==null||requestHead.length()<=0){

return ;

}

String firstLine = requestHead.substring(0, requestHead.indexOf("\r\n"));

int a = requestHead.indexOf("/");

this.method = requestHead.substring(0, a).trim();

String urlparam = null;

String parameter = null;

urlparam = firstLine.substring(a,requestHead.indexOf("HTTP/"));

if(method.equalsIgnoreCase("post")){

parameter = requestHead.substring(requestHead.lastIndexOf("\r\n"));

this.url = urlparam;

}else{

if(firstLine.contains("?")){

String []params = urlparam.split("[?]");

this.url = params[0];

if(params.length>0){

String []params1 = params[1].split("&");

if(params1.length>0){

String[] param;

String key = null;

String value = null;

for(int i = 0; i < params1.length;i++){

param = params1[i].split("=");

if(param.length>0){

key = param[0];

if(param.length>1){

value = param[1];

}else{

value = null;

}

this.parameterMap.put(key, value);

}

}

}

}

}else{

this.url=urlparam;

}

}

}

/**

 * 获取请求信息

 * @return

 */

private String read() {

StringBuffer sb = new StringBuffer();

Reader re = null;

try {

re = new InputStreamReader(this.in, "UTF-8");

char []cbuf = new char[20480];

int index = 0;

index = re.read(cbuf);

if(index>0){

sb.append(cbuf,0,index);

}

} catch (Exception e) {

e.printStackTrace();

}

return sb.toString();

}

public String getMethod() {

return method;

}

public void setMethod(String method) {

this.method = method;

}

public String getParameterMap(String key) {

return (String)parameterMap.get(key);

}

public void setParameterMap(Map parameterMap) {

this.parameterMap = parameterMap;

}

public String getUrl() {

return url;

}

public void setUrl(String url) {

this.url = url;

}

}

Response:

package com.cn.response;

 

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.util.Date;

 

import com.cn.util.Util;

 

public class Response {

private OutputStream out;

private int code;

private StringBuffer context ;

public Response(OutputStream out){

this.out = out;

context = new StringBuffer();

}

public void push(String url,int code){

this.read(url);

String head = this.createHead(context.length());

try {

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(this.out,"UTF-8"));

bw.append(head);

bw.append(this.context);

bw.flush();

} catch (IOException e) {

e.printStackTrace();

}

}

private void read(String url){

if(url==null||url.length()<0){

return;

}

url = url.substring(1);

url = System.getProperty("user.dir")+File.separatorChar+"WebRoot"+File.separatorChar+url;

File file = new File(url);

BufferedReader br = null;

if(!file.exists()){

this.code =404;

this.context.append("<h1>大写的<span style='font-size:90px'>404</span></h1>");

}else{

try {

br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

String cacheStr = br.readLine();

while(cacheStr!=null){

context.append(cacheStr);

cacheStr = br.readLine();

}

} catch (FileNotFoundException e) {

this.code =404;

e.printStackTrace();

} catch (IOException e){

e.printStackTrace();

}finally{

Util.closeIO(br);

}

}

}

private String createHead(int len){

StringBuffer headInfo =  new StringBuffer();

headInfo.append("HTTP/1.1").append(" ").append(code).append(" ");

switch (code) {

case 200:

headInfo.append("ok");

break;

case 404:

headInfo.append("NOT FOUND");

break;

case 505:

headInfo.append("SERVER ERROR");

break;

}

headInfo.append("\r\n");

//响应头

headInfo.append("Server: Webserver").append("\r\n");

headInfo.append("Date:").append(new Date()).append("\r\n");

headInfo.append("content-type:text/html;charset=UTF-8").append("\r\n");

//正文长度:字节长度

headInfo.append("Content-Length:").append(len).append("\r\n");

//分隔符

headInfo.append("\r\n");

return headInfo.toString();

}

}

Util:

package com.cn.util;

 

import java.io.Closeable;

import java.io.IOException;

 

public class Util {

public static  <T extends Closeable> void closeIO(T ...ios){

for(Closeable c :ios){

try {

if(c!=null){

c.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

 

运行效果:

 

 

 页面不存在的情况:

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值