发信人: zjxyz(xyz)
整理人: zjxyz(2002-04-10 19:11:20), 站内信件
|
本文将详细讲述WEB服务器线程调度核心,作为一个WEB服务器,有时要面同时对众多客户端的请求,如何有效率的响应各个请求,一般是服务器编写的难点中难点。“网络编程其实涉及网络的部分一般只占程序的20%。”
下面就详细分析 HttpConnect.java 文件的内容。
HttpConnect.java 文件里面含有定义了几个类:HttpConnect、ListenThread、ConnectThread、Lock,它们在这里的作用是:
HttpConnect 服务实例的初始化类;
ListenThread 侦听线程类;
ConnectThread 处理线程;
Lock 锁对象;
从HttpConnect开始,HttpConnect的实例生成一个侦听线程 ListenThread 的实例,一组处理线程 ConnectThread 的实例,在服务实例开始时,处理线程调用阻塞于Lock对象的 waitQueue() 方法阻塞在该对象上,当侦听线程侦听到一个连接后,调用Lock对象的 Queuenotify() 方法,令到一个阻塞在Lock对象上的处理线程解除在阻塞状态;
被唤醒的处理线程将去检查对连接队列,如果连接队列为空,处理线程则重新阻塞在Lock对象上,如果不为空,处理线程就生成一个 HttpHandler 实例,对该连接进行处理,
处理完毕后,根据 HttpHandler 实例 的返回值决定是否完全销毁 HttpHandler 实例(目前都是完全销毁),最后再次阻塞在Lock对象,完成一个连接处理周期。
这样设计的好处是,预先起了一组服务线程,当有客户端的接入时,服务器的侦听线程可以立即唤醒一个处理线程去处理客户端的请求,同时这样可以很快的进入侦听状态,这样当有很多客户端连接进来是都可以得到很快的响应。
其详细的过程和原理如下:
在 HttpConnect的构造方法HttpConnect(HttpdConf httpdconf)中,根据httpdconf中的“Port”参数,传进来的生成一个ListenSocket 的实例,该侦听线程将侦听值为“Port”的端口;然后根据传进来的“MaxKeepAliveRequests”参数,决定生成处理线程的个数,生成若干个处理线程。
HttpConnect的Listen() 方法,是用于启动上述线程的。
在类HttpConnect中,有一个名为SocketQueue 的Vector类型的成员,这是用于暂时存放客户端连接的,这个SocketQueue 的句柄分别通过构造方法传给侦听线程和处理线程。
侦听线程会侦听到的socket句柄放到队列SocketQueue里面。
在处理线程在第一次启动时会调用自身的checkQueue() 方法去检查这个队列,当发现队列为空时,处理线程会调用Lock 实例的waitQueue() 方法阻塞在Lock上面;等候当调用checkQueue() 方法发现队列不为空时,就会从队头中取出一个连接进行处理,同时把句柄从队列中删除,处理完毕后,处理线程会再去检查队列,如果发现已经为空的话就进入阻塞状态,直到下次被唤醒。毫无疑问,当服务线程初始启动时,所有处理线程都会阻塞在Lock上面,等候处理连接。
当侦听线程侦听到一个连接进来时,主要做了三件事
1、 把侦听到的连接放进队列,
2、 调用Lock 实例的Queuenotify()方法,这样可以令到众多阻塞在Lock上面的的处理线程中的一条退出阻塞状态,到SocketQueue里面取socket句柄
3、 继续进入侦听状态 。
如何令处理线程在没连接进来空闲的时候阻塞起来,在有连接进来时唤醒阻塞的空闲处理线程去处理,在所有没空闲处理线程,把socket句柄暂放在队列中,是这个服务器的线程调度的关键。
上面提到处理线程是阻塞在Lock的实例上的,下面就深入讨论其中的原理。
我们知道Java的对象都是java.lang.Object的父类,Object中,有两个方法:wait()、notify(),这是实现的关键。分析Lock类的代码,其实就两个同步的方法waitQueue()、Queuenotify(),方法里面的内容很简单,就是同步调用分别自身的wait()、notify()方法。当有其他线程调用Lock实例的waitQueue(),就会阻塞在Lock实例上面,直到另一个线程调用Lock实例Queuenotify()方法,才从阻塞状态脱离出来。
这是怎么回事呢,通过阅读JDK文档关于wait()、notify()的说明,我的理解是这样的:
每个对象都有一个自己的monitor(注1),线程要执行(为了解释的形象一点下文称为“通过”)该对象的同步方法(注2),就必须获得该对象的monitor拥有权,而同一时间,只能有一个线程能拥有某个对象的monitor拥有权;线程有了对象的monitor拥有权就能“通过“对象的同步方法,当线程通过了对象的同步方法后,monitor拥有权才会让出来,让给下一个要通过同步方法的线程。这也就保证了对象的同步方法在同一时间内只能被一个线程调用。
当线程在“通过”线程的同步方法时,同步方法中调用了对象自身的wait()方法的话,线程一个就会放弃对象的monitor拥有权,失去了对象的monitor拥有权的线程就会阻塞在该对象上面,直到重新获得monitor拥有权,才能继续执行wait()方法后面的代码。
也就是说,如果某个线程想阻塞在某个锁对象上面,那么该线程只需调用该对象的一同步方法,该同步方法中执行自身的wait()方法,这样线程就会放弃该对象的monitor 拥有权而阻塞在该对象上面;当另一个线程执行锁对象的的同步方法,而同步方法里面调用锁对象自身的notify()方法的话,那么阻塞在锁对象上面的其中一个线程就会获得monitor 拥有权从而走出阻塞状态,继续执行wait()后面的代码。
上述提到的处理线程组,其实就是阻塞在Lock对象上面,当有连接进来时,侦听线程就会通过调用Lock对象Queuenotify()方法,Queuenotify()方法由于调用自身的notify(),所以就会有一个阻塞的线程从Lock对象脱离出来去处理连接。
这个设计的败笔之处:
1、 其实不必要专门设置一个Lock对象,当然有其他需求的话另当别论,我当初是为了便于理解才那样写的。
2、 由于处理线程内容都是一样,启动多个线程没有必要逐个new出来的,其实可以生成一个后,其余的调用对象的clone() 方法克隆出来就可以了。
注1:在下英语四级没过,就不便翻译,知道是干什么用的就可以了。
注2:至于同步方法,上面提到一点概念,详细的我就不解释了,操作系统那门课,我差点被那位连Linux都不会用的老师抓补考呢
---- 网易广州社区Java版
XYZ个人主页,提供一个公开源代码的WEB服务器+聊天室
冗談の言葉は无用だ…俺は最强だ!あんた ゃるじゃないか.だが...,世界じゃ二番目だ.
手机号码归属地查询系统,可查出手机所属省份,所属城市,SIM卡类型,网友做的。

|
|