关于Web服务器与动态网页的一些思考

首先呢,我们从代码的层面看一下,一个Web服务器是如何运行的。这里的Web服务器实际应该指的是一个运行在机器上的程序,现在各种文章里面“服务器”乱叫,都让人搞不清到底在说啥。从大的层面嘛,服务器就是一台电脑。一台电脑可以做服务器,也可以做客户端,比如我的电脑可以同时做服务器和客户端。而Web服务器就有些含糊,需要根据语境来判断到底是指一台电脑还是在电脑上运行的程序。本文中,Web服务器特指运行在电脑上的程序。


两台电脑通过网络通信,本质上是两支分别运行在各个电脑上的程序(更具体点是进程)在通信,而进程是通过套接字(socket)把数据发送到网络中去的。形象的说,套接字是一支运行中的程序在网络中的代表,因为它包含了两个必要的属性:IP和端口号。IP用来定位主机,端口号用来定位进程。每个需要进行网络通信的进程,其进程ID必然和是某个套接字的端口号绑定的。套接字是操作系统的一种资源,可以由操作系统分配和回收。


常见的Web服务器有Apache和Ngnix等,其核心的思路我大概用java代码模拟一下(本文大部分代码模仿《How Tomcat Works》):
public class WebClient {
	private Socket socket = null;
	
	public WebClient(Socket socket) {
		this.socket = socket;
	}


	...
}

我们需要借助Java语言已经封装好的Socket类,由他负责与底层的操作系统交互。在展示类的构造方法中,要传入一个在main方法(见下面)中构造好的Socket对象,设定了参数IP和端口号,这样Java虚拟机就会向操作系统申请一个套接字,这个套接字就代表了我们运行时的WebClient类在网络中的位置。
然后是接收和发送数据的方法:

	public void send() throws IOException {
		PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
		out.println("GET /index.html HTTP/1.1");
		out.println("Host: localhost:8080");
		out.println();
	}

以上代码明显是输出一个标准的HTTP请求头,这跟浏览器的工作原理是一致的。
	public void accept() throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		StringBuffer sb = new StringBuffer(8096);
		while (true) {
			if (in.ready()) {
				int i=0;
				while (i != -1) {
					i = in.read();
					sb.append((char) i);
				}
			}
			System.out.println(sb.toString());
			socket.close();
		}
	}

这是接收服务器发来的数据,展示然后关闭连接。这里的展示显然只是简单的以字符串的形式打印到控制台,但是浏览器会去解析这些数据,比如常见的HTML代码会被解析成结构化的文档,图片也会被还原。
写个测试:

	public static void main(String[] args) {
		try {
			Socket socket = new Socket("127.0.0.1", 8081);
			WebClient webClient = new WebClient(socket);
			webClient.send();
			webClient.accept();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


以上还只是客户端的代码,服务器会有所不同。为啥呢?因为二者角色不同。服务器是提供服务的,必须二十四小时待命。但是客户端不同,它可以随时随地发起连接,收到回应后就关闭。一句话,客户端不是固定的,然而服务器却必须是固定的,只有这样才能为客户端提供服务。这也是为啥服务器的IP和端口号是已知的、公开的。这就是传统B/S结构的来源。
为了能时刻监听来自客户端的请求,服务器端需要一个ServerSocket类,一旦收到请求,这个类会新建一个Socket类与客户端通信。
它的一个构造方法如下:
public ServerSocket(int port, int backLog, InetAddress bindingAddress);
然后我们的例子是:

public class WebServer {
	private ServerSocket serverSocket = null;
	
	public WebServer(int port, String host) {
		try {
			serverSocket = new ServerSocket(port, 1, InetAddress.getByName(host));
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}


	...
}

到此只是初始化了serverSocket,给它一个绑定的IP地址以及监听端口,重点是下面的:
	public void response() {
		while (true) {
			Socket socket = null;
			OutputStream output = null;
			
			try {
				socket = serverSocket.accept();
				output = socket.getOutputStream();
				byte[] bytes = new byte[1024];
				FileInputStream fis = null;
				try {
					File file = new File("E:\\index.html");
					
					if(file.exists()) {
						fis = new FileInputStream(file);
						int ch = fis.read(bytes, 0, 1024);
						while (ch != -1) {
							output.write(bytes, 0, ch);
							ch = fis.read(bytes, 0, 1024);
						}
					} else {
						String errorMessage = "Sorry, file does not exist!";
						output.write(errorMessage.getBytes());
					}
				} catch(IOException e) {
					e.printStackTrace();
				}
				
				socket.close();
			}
			catch (Exception e) {
				e.printStackTrace ();
				continue;
			}
		}
	}


重点关注标红部分,如果文件存在则发送该文件的字节流;否则直接在代码中生成一个字符串发回去。所以,返回给客户端的数据是可以直接在代码中动态生成的,代码中可以设置条件,根据条件来生成不同的代码。但是对于内容都已经写死的HTML文件这恐怕是非常难办到的,总不能每个条件分支都对应一个HTML文件吧。。而且其实每个条件下,变动非常小,比如整体页面不变,只有上面的名字或电话号码变了,难道值当为此重复写N个HTML文件?
所以这时用代码来搞事情的优势就很明显了,我们在代码里面想让它怎么输出就怎么输出。说白了,反正最终都是发给套接字一堆数据流,至于数据流从静态的HTML文件中来还是由代码生成,不必care。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值