发信人: zjxyz(xyz)
整理人: zjxyz(2002-04-10 19:11:20), 站内信件
|
WEB服务器,我相信大家一定不陌生,至少在读这篇文章时,就在与WEB服务器打交道,我在写WEB服务器主要涉及了三个方面的知识,就是:Httpd协议、 Socket、线程,为了便于展开文章的讨论,我就简单对这三个方面的知识作简单的介绍,详细深入的资料请大家自行在网上找些来看。
Httpd协议:
在写一个Web服务器之前,必须要了解浏览器与Web服务器是怎么通讯的,浏览器跟Web服务器通讯的的规范写在RFC文件里,这里只是简单作一些介绍,首先示范一下浏览器与Web服务器一次简单的应答。
例如一个WEB服务器的根目录上有一个名位zjxyz.html的文件:
zjxyz.html
test
那么,你尝试telnet 该服务器的端口,一般是80端口
telnet loaclhost 80
连接上后输入
GET / zjxyz.txt HTTP/1.0
连续按两个回车
跟着,服务器会返回以下信息:
-------------------------------------------------------------------
HTTP/1.1 200 OK
Date: Thu, 10 Jan 2002 13:02:08 GMT
Server: Apache/1.3.9 (Win32)
Connection: close
Content-Type: text/html
test
-------------------------------------------------------------------
这里简单的说明一下上面的通讯过程,首先,客户端telnet到WEB服务器的端口,
输入请求指令(留意之间的空格)
GET / zjxyz.txt HTTP/1.0,
指令由两个空格分成三部分:
第一部分GET是请求的方法;
第二部分/ zjxyz.txt 是请求的资源;
第三部分HTTP/1.0 是htppd协议的版本号,这就是告诉WEB服务器当前连接请求的浏览所支持的http版本。
输入指令后,再连续输入两个回车,就是告诉服务器,指令已经发送完毕了,
后面是轮到服务器发送信息给浏览器。
常用的请求的方法通常有GET、POST,GET与POST之间的区别,写过CGI的朋是最清楚的了,不过这里我还是要介绍一下:
GET方法就是把请求的内容全部放在第一后面行里面,就是第一个空格后面,第二个空格前面那段文字,在一些动态网页GET方法还负责传送“名值”对给WEB后端的应用,所谓“名值”对,就是大家在浏览一些动态网页时见到的URL”?”后面跟着的一串看起来很乱的一堆字符,例如,http://www.zjxyz.org/cgi-bin/test.cgi?id=zjxyz&name=xyz,名值对之间用&隔开,要是“名值”对含有一些特殊的字符例如空格、等号、回车、换行等字符,浏览器在发送前会把名值对进行URL编码,例如把空格转化为%20,这样就避免了与GET指令冲突,当然,WEB后端的应用也要就有相应的URL解码,才能正确获取“名值”对。GET方法有个限制,就是提交的内容不能超过1024个字节,要是超过1024个字节就要用到POST方法了。
POST方法的格式跟GET方法差不多,只是在两个回车后跟着一段数据,这段数据是不限长度的,当然其中的数据也是经过URL编码的。
Httpd协议还定义了另外几个请求的方法,但是一般的WEB服务是不会去实现那几个方法。
在请求指令与指令结束标志(即两个回车之间),浏览器还可以以行为单位发送一些其他的信息,例如该浏览器支持的mime类型、浏览器的版本、cookies等信息
如果想详细了解这些信息,就请阅读RFC文档了,这里就不详细介绍,这里的介绍只是为后面的讨论作准备。
目前用的比较广泛的是Httpd 1.1,不过我写的WEB服务器目前不支持,但是基本的指令是兼容1.0,所以,建议大家还是阅读Httpd 1.1的规范RFC2068。
上面是浏览器发给服务器的信息,现在简单介绍一下服务器对浏览器的响应,
从上面的例子我们发现,在服务器返回zjxyz.html文件的内容给浏览器之前,还有一些信息,
为了便于描述,我们称返回的zjxyz.html的内容叫正文,称正文之前的内容为响应头,正文与响应头之间,用两个回车符号分隔开,另外,如果返回的信息是文本的话,有时服务不会返回响应头,而是直接返回一段文本,这是题外话。
当服务器接到一个请求,经过处理后,首先回发给浏览器一个服务器状态头,就是发回来的第一行
HTTP/1.1 200 OK
服务器状态头是按数字区分的,有各自的含义,其中,200 的含义就是说服务器正确处理了浏览器的请求,常用的状态头有:
403 禁止访问的资源
404 请求的资源没有找到
405 请求的方法不支持
500 服务器自身出错
在发给浏览器正文之前,还要想浏览器说明返回的数据的mime类型,例如返回的正文是一个html文件,那么就必须返回一行这样的信息:
Content-Type: text/html
否则的话,浏览器无法判断返回正文的类型
以此类推,要是返回的是一个二进制文件,例如一个名为zjxyz.jpg的图片,就应该返回一行这样的信息:
Content-Type: image/jpeg
综述:
httpd协议相对于pop、smtp等协议来说算是一个简单的协议,那是因为httpd协议当年的设计目的是用于交流物理论文用的,但是由于近年来WEB的商业化,为了满足商业需要,httpd协议开始变得复杂起来,但是基本通信过程还是不变的,一些其他本文没有讨论的但程序中涉及的的httpd细节在具体编程时,再另行介绍。
Socket简介
Httpd 协议是基于TCP/IP实现的,所以编程中一定要对Socket有一定的认识,其实Socket的编程在各种编程语言中是大同小异的,因为本文不是主要讨论Socket编程,其实网络编程涉及网络的部分只占代码的20%,引用已故的W.Stevent博士(《UNIX网络编程》的作者)的话,网络编程,功夫在网络之外,这里只会讨论涉及程序中所用到的部分,另外,必须清楚认识到,Java目前支持的网络的API不够C的丰富,例如不支持链路层编程。
为了兼顾Socket不大熟悉的朋友,这里先简单介绍一下什么是Socket,Socket其实是一种网络编程的规范、接口,目前被大量使用的是BSD Socket,其他的Socket一般都与侦听服务器兼容。
用Java常用的Socket类是下面两个:
java.net.ServerSocket
java.net.Socket
类的具体的方法内容请参阅API手册
ServerSocket是用于编写侦听服务器用的一个类,Socket主要是用于连接到一个服务端口上去。
先说一下侦听服务器的是怎么工作的,首先,是把一个TCP端口绑定到一个Scoket上,然后利用这个Scoket句柄侦听该端口,等待连接进来的客户端,当程序运行到ServerSocket的accept()方法时候,程序就阻塞这这个方法上,当有连接进来时,程序从阻塞中出来,利用accept()方法返回的一个Socket句柄与客户端通信;相对而言,客户端的连接就简单一些,就是利用Socket这个对象,指定要连接的服务器的IP地址、端口,连接过去,Socket这个实例与服务器通信。
一般的,侦听服务器是写成一个死循环,其处理过程就是 侦听==》处理连接==》侦听;由于服务器经常会遇到多个客户端同时连接进来要求服务的情况,所以如何提高服务器的运行效率是一个十分重要的问题。
通常,编程者们都想尽办法提高服务器的处理效率,在Java中,我们一般采用多线程并发的机制来解决这些问题。比较传统的做法,就是,当侦听服务器接到一个连接后,该连接不是放在当前线程内处理,而是起一个线程去处理这个连接,侦听服务器则继续进入侦听状态,这样的话,在多个客户端同时连接上来时,后面连接就不用等待侦听服务器处理完第一个连接后才能后面的请求,服务器响应的速度就大大加快。这里我们又涉及了一个关于线程的话题,下面就简单为介绍Java的线程。
Java的线程:
这里就我个人的体会简单的介绍一下什么是线程。
在说到线程之前,我想先说说进程,进程就是在一段正在系统中运行的的程序的实体,一段执行的控制流,其中进程还括程序运行时所用到的数据、打开的文件句柄等系统资源,现代的操作系统一般都是支持多进程,就是说系统在同一时间内可以同时运行若干条进程,每条进程都有自己独立的虚拟的地址空间,还有进程打开的文件句柄。一般的,硬件角度来看,一个CPU在同一时间只会做一件事,而从操作系统则按照一定的算法调度把CPU分配给各个处于激活状态的进程,进程可以根据系统的调度被CPU执行,在用户看来,就好像多个进程在同一时间执行一样。在虚拟模式下,每一个运行着的进程都像在一台虚拟的机器下运行,当然这不是与外界完全隔绝的,进程间的通信也是一个很有讨论意义的话题,不过不是本文的讨论话题。
而所谓线程又是什么概念呢?上面说了,进程是拥有自己独立的虚拟的地址空间,通常,我们生成一个进程,都是通过复制的方式来产生新的进程,每复制出一个进程后,系统必须把父进程的所拥有数据,还有父进程打开的文件句柄都复制一份给子进程,所以在系统中的无论创建还是运行一个进程,系统开销是比较大的。而线程作为一段程序执行的实体存在于一个进程之内,我们可以说一个正在运行的进程必然有一条线程,而进程在支持多个线程的操作系统上可以拥有多个线程,在同一个进程之内若干条线程是共用该进程的资源,又独立于其他线程运行,所以线程的开销远比进程少,所以,有人也叫线程为轻量进程。
Java封装了对线程的操作,一般的,在Java创建线程一般有两个方法:
1、 继承类 java.lang.Thread 来创建线程;
2、 在类中实现java.lang.Runnable 接口。
具体的内容请参阅API手册
Java提供了一套线程同步、阻塞的机制,方便我们对线程的调度,在写多线程服务器的时候,是一定会用到的,在后面的讨论中,我们会着重讨论。另外,Java虚拟机提供的线程,在某些操作系统上,未必就是系统线程,在某些操作系统下,Java虚拟机是利用系统的进程来实现虚拟机的线程,这点是必须留意的,我曾见过一些程序,开了上千个线程,要是放在那些操作系统下,系统的资源很可能会耗尽。
---- 网易广州社区Java版
XYZ个人主页,提供一个公开源代码的WEB服务器+聊天室
冗談の言葉は无用だ…俺は最强だ!あんた ゃるじゃないか.だが...,世界じゃ二番目だ.
手机号码归属地查询系统,可查出手机所属省份,所属城市,SIM卡类型,网友做的。

|
|