具体实施方式
下面根据附图和实施例对本发明作进一步详细说明:
我们开发了一个Web医学影像服务器,用于解决高并发资源的有效利用。通过该服务器Web浏览器就可以访问到集群存储服务器上的影像数据,Web服务器具体的任务处理操作环节:
首先Web浏览器通过标准HTTP协议向Web服务器发出取图请求。
Web服务器接收到求请后,通过TCP/IP协议向影像存储服务器发出连接请求,收到影像存储服务器的连接确认后读取影像数据,数据读取成功断开连接。
图像获取成功后,Web服务器应该根据浏览器传过来的参数进行相应的处理,如调整图像大小,数据压缩,文件格式传换等。
图像处理完毕后将结果返回给Web浏览器。如图1所示.
由于Web服务器所有功能都在一个进程的多个线程中实现,当一个任务处理发生错误的时候有可能会导致整个进程停止工作,这样将导致整个Web服务器当机。
I/O完成端口可以较好的解决高并发的访问效率,但并没有解决其系统的稳定性问题,但对于一台服务器来说,能够长期稳定的工作是非常重要的。除开任务处理环节,影响系统的稳定性主要是网络通信,由于用户连接、数据传输存在很多不确定因素,有硬件方面的也有软件方面的。为了解决系统的稳定性问题,我们将Web服务器的网络通信环节独立出来,同时设定一个进程生存期,也就是说由一个进程专门负责网络通信过程,当进程生存期到将关闭该进程,同时由另一个进程接替网络通信任务。如图2所示.
由于进程启动需要花费较多的时间,考虑到效率问题所以服务器运行的时候系统将预先启动三个网络通信进程,其中一个负责执行网络通信任务,其它两个进程挂起等待执行。当执行进程的生存期到,执行进程将关闭监听任务,同时等待该进程未处理完成的请求,待所有的通信任务都处理完成后关闭进程。当执行进程关闭监听任务的时候将由其中一个挂起进程接替监听网络处理任务,进入执行状态。由于已经关闭了一个进程,同时一个挂起进程已经切换到执行状态,所以挂起进程减少了一个,运行环境发生了变化,这个时候系统将自动补充一个新的挂起进程来等待下一步的操作。使用这种方法可以有效的避免进程长时候运行后产生的随机错误。
Web服务器除了网络通信内容外还有就是实现如何处理用户的任务,为了保证Web影像服务器能够持续稳定工作,我们把所有的处理任务量化成数据的输入、处理、输出三个环节。任务请求数据的输入和处理结果数据的输出只涉及到数据内容的拷贝,环节简单不可能出现什么问题。问题主要出现在任务处理过程,由于影像处理的请求任务比普通的文字处理请求要复杂多变,处理过程可能出现的问题很难全部控制,所以将该环节独立到服务程序之外来考虑,这样就不会影响服务程序的稳定性。
如何能将处理环节独立到服务程序之外呢?我们使用了一种方法将每一个处理任务独立成一个个进程,将并发处理的每一个线程任务对应一个进程,由每一个进程来处理该线程需要完成的任务,当发生一个处理任务时线程将输入数据提交给该进程,由进程处理任务,线程等待进程处理,进程处理结束将结果传给线程,线程返回请求结果。任务线程与处理进程通道的搭建由一个管理进程来负责,管理进程同时实现处理进程分配策略。当处理进程发生错误无法完成处理任务,线程结束等待返回错误信息,管理进程将该处理进程关闭,重新启动一个新的进程代替原进程。使用这种方法可以将处理环节独立于服务程序之外,避免系统处理错误导致崩溃的可能性。如图3所示。
根据上述说明,我们可以知道该方法的实现主要是分成三部分,分别是Web服务进程(Web Server Process)、数据交换区(Shared Memory)、处理任务进程(Task Process)。但是这三部分如何联系起来以及如何避免线程相互抢夺资源呢?为了将这三部分有效的组织起来,还需要再引入一个管理进程(Manage Process),由管理进程来对资源进行有效的分配,如图4所示
因为系统在运行的时候会为每个进程分配不同的内存区域,对不同进程来说,它们具有独立的数据空间,要进行数据传递需要分配一个共享内存区(Shared Memory)。首先在启动主管理进程的时候可以通过系统函数CreateFileMapping创建一个共享内存区,通过MapViewOfFile将内存区影射到进程地址中。在系统运行过程中,管理进程需要管理两种类型的进程,一种为网络通信进程,别一种为数据处理任务进程。因为两种进程的调度策略完全不同,所以在实际操作中,应该分别进行考虑。
首先我们先描述网络通信进程调度策略:为了实现主管理进程对其进行合理调度,应该定义两个数据结构,分别记录管理主进程的属性信息、进程运行状态信息,同时定义进程的状态,将两个结构影射到共享内存中
进程状态定义表:
网络通信子进程属性信息
struct LIOCPChildInfo
{
LIOCPProcState State;//进程当前状态
HANDLE ProcessHandle;//进程句柄
int ProcessId;//进程编号
HWND Handle;//进程接收消息对象句柄
DATE OpenTime;//进程打开时间
DATE ExecuteTime;//进程监听时间
DATE SuspendTime;//进程最后一次挂起时间
};
网络通信管理主进程属性信息结构:
struct LParentProcInfo
{
HWND Handle;//父进程对象句柄
LPipeProcInfo Channel[100];//任务进程选择
};
将二个结构影射到共享内存区中,如图5所示.
实现了对进程状态的记录后还需要定义一组用于进程间通信的消息命令。
进程消息定义表:
对消息定义完成后就可以实现进程的调度工作:管理主进程启动后将进程消息句柄记录在属性信息中。为了避免启动任务进程影响系统的运行效率,在启动管理主进程后将预先启动三个网络通信进程,使用CreateProcess启动进程,函数调用结束后返回进程ID号,找出子进程信息列表中未记录进程信息的位置,将内容写入该位置,同时将进程设置为psInit状态表示该任务进程正在启动中,还不能进行网络通信。
通信进程启动后通过系统函数OpenFileMapping打开管理进程创建的共享内存区,通过MapViewOfFile将内存区影射到该进程地址中,这样通信进程就可以访问进程信息列表中的信息。使用GetCurrendProcessId获取该进程ID找出列表中记录该进程的位置,将进程状态修改为psOpen,说明进程已经启动成功,可以执行任务。同时使用PostMessage向管理主进程发出WM_PROCESS_OPEN消息,通知管理主进程可以为该进程分配任务,进程进入等待状态。
管理进程接收到WM_PROCESS_OPEN消息后选择其中一个进程作为执行进程,向该进程发送WM_PROCESS_EXECUTE消息。
通信进程接收到WM_PROCESS_EXECUTE消息后启动完成端口网络监听模式,接收客户端的处理请求。当通信进程进入执行状态后生存期将开始计时,进程运行一段时间,到达生存期后当前执行的通信进程将向管理进程发出WM_PROCESS_SUSPEND消息,同时关闭完成端口网络监听,进程状态修改成psSupsend状态。网络监听虽然关闭,但该进程有可能还存在关闭监听端口前用户未处理完成的连接,所以进程不马上关闭,而是等待所有用户处理完成后再关闭进程。
管理进程接收到通信进程的WM_PROCESS_SUSPEND消息后需要马上选择一个挂起的通信进程发送WM_PROCESS_EXECUTE消息启动执行状态,接替网络通信任务。
管理进程定时检测通信进程数量,如果进程数量少于三个,将自动启动一个新进程用与等待执行通信任务。如图6和7所示。
以上为通信进程的调度策略,使用该方法可以保正网络通信的持续稳定工作,不会因运行时间长导致运行问题。
描述了通信进程的调度策略后,我们再描述处理进程的调度策略:
同样我们为处理进程也定义三个数据结构,分别记录管理主进程属性信息、进程选择属性信息及处理子进程的数据交换信息,同时定义进程的状态,将三个结构影射到共享内存中,这样进程间就可以知道各自的状态,以便如何进行下一步的操作。
进程状态定义表:
处理子进程属性信息结构:
struct LChildProcInfo
{
LProcState State;//进程当前状态
HANDLE ProcessHandle;//进程句柄
DWORD ProcessId;//进程编号
HWND Handle;//进程接收消息对象句柄
DATE OpenTime;//进程打开时间
DATE SuspendTime;//进程最后一次挂起时间
DWORD ChannelId;//通道进程编号
DWORD ThreadId;//通道线程编号
int Command;//处理命令编号
int DataState;//数据读写状态0:输入,1:输出,-1:错误
bool IsLastFragment;//是否为结尾数据,True为数据传输结束
int Size;//数据大小
char Data[0x100000];//任务处理数据交换区
};
进程选择属性信息:
struct LPipeProcInfo
{
DWORD ProcessId;//进程编号
int Request;//向父进程请求通道句柄,0:无处理任务,1:有处理任务
int Result;//返回值,0~N:返回处理子进程索引
DWORD ChannelId;//请求通道进程ID
DWORD ThreadId;//请求通道线程ID
};
管理主进程属性信息结构:
struct LParentProcInfo
{
HWND Handle;//父进程对象句柄
int MaxProcCount;//子进程总数
LPipeProc Info Channel[100];//任务进程选择
};
将三个结构影射到共享内存区中,如图8所示.
管理主进程启动后将进程消息句柄记录在属性信息中。为了避免启动任务进程影响系统的运行效率,在启动管理主进程后应该预先启动几个处理子进程,使用CreateProcess启动任务进程,函数调用结束后返回进程ID号,找出子进程信息列表中未记录进程信息的位置,将内容写入该位置,同时将进程设置为psInit状态表示该任务进程正在启动中,还不能接收任务处理。
处理子进程启动后通过系统函数OpenFileMapping打开管理进程创建的共享内存区,通过MapViewOfFile将内存区影射到该进程地址中,这样处理子进程就可以访问进程信息列表中的信息。使用GetCurrendProcessId获取该进程ID找出列表中记录该进程的位置,将进程状态修改为psSuspend,说明进程已经启动成功,可以执行任务。同时使用PostMessage向管理主进程发出WM_PROCESS_OPEN消息,通知管理主进程可以为该进程分配任务。
管理主进程进入等待任务求请状态,实时检测管道进程属性信息表变量Request属性值,如果值为1表示需要分配一个资源给服务线程来处理任务。
当Web服务接收到一个任务请求时,首先判断处理子进程属性信息结构,该线程是否已经分配了处理资源,如果已经分配了资源,直接向处理进程发出处理请求,如果未分配资源,将管道进程属性信息变量Request值设为1,向管理主进程发出请求,管理进程为该线程分配资源,将Result设置为0~N的值,指定处理子进程属性信息具体的位置,同时向分配进程发出WM_PROCESS_EXECUTE消息,通知进程进入执行状态。如果所有的进程都处于处理状态,管理进程直接启动一个进程来执行任务。的到资源后线程向该结构分别填写Command:处理命令信息,DataState:0输入状态,Size:输入数据大小,Data:数据内容,State:进程状态设为psExecute,然后等待进程处理结束返回结果。如果Data定义的最大空间不能放下输入的数据,可以采用多次填入数据的方法来实现大数据的输入,采用IsLastFragment来识别数据是否传输完成。
处理进程接收到处理任务,从处理子进程属性信息表中读取数据,进行处理,处理结束后向任务线程返回处理结果,分别填写DataState:1输出状态,Size:输出数据大小,Data:数据内容。如果处理错误DataState值-1处理错误。如果Data定义的最大空间不能放下输出的数据,可以采用多次填入数据的方法来实现大数据的输出,采用IsLastFragment来识别数据是否传输完成。
任务线程判断DataState状态如果值为1,说明任务处理完成读取Data数据,将内容返回给Web浏览器,结束任务。如果值为-1,说明处理错误,将进程状态设置为psError,结束任务。管理进程定期检测各进程状态,发现有psError状态进程,将其关闭。
为了提高线程与进程交互效率,避免任务线程每次处理都要向管理进程申请资源,所以当进程任务处理完后,并没有直接退出执行状态,仍然记录对应线程信息,延迟一段时间,等待该线程的下一次处理任务。
延迟一段时间,如果未接收到对应线程任务,处理子进程重新进入psSuspend状态将挂起时候记录在信息列表结构变量SuspendTime中,同时发管理进程发出WM_PROCESS_SUSPEND消息,通知管理进程该处理进程可以接收下一个线程任务。我们为每个任务进程设置了一个生存期,然后通过与SuspendTime时间进行判断来确认是否已经超过了进程的生存期。如果进程已经超过生存期结束进程,向管理进程发出WM_PROCESS_CLOSE消息。
由于每个进程都有一定的生存期,也就是说过一段时间后所有的旧进程都会被关闭,如果没有了任务进程,就无法接收任务请求,所以我们设置了一个初始默认启动进程数,如果进程小于指定数量,管理进程会启动新的任务进程来补充处理任务。使用定期更换进程的方法可以进一步提高系统的稳定性。
当系统关闭的时候管理进程会向各处理子进程发出WM_PROCESS_STOP消息,要求关闭所有处理。
使用该方法,Web服务程序内的线程任务只负责将任务输入数据拷入到数据交换区,然后等待处理结果,处理结束后再从数据交换区拷出输出数据返回结Web浏览器,服务进程内部环节简单,稳定性高。如图9、10、11所示.
在医学影像应用中,为了实现跨internet访问远端的存储服务器影像数据,使用本方法,我们开发了一个基于Web的医学影像显示服务层,通过该服务层Web浏览器就可以访问到PACS集群存储服务器上的影像数据,将其传送到Web浏览器上,如图16所示。
我们在Web服务器上开发了三个应用程序,分别是WebServerChild.exe该程序实现了基与完成端口网络通信模式的Web服务,用于接收浏览器的访问请求,WebServer.exe和WebExecChild.exe,前一个程序负责管理进程,后者负责处理WebServerChild接收的处理任务。图11所示是应用程序实例后在任务管器的表现方式。
WebServer、WebServerChild和WebExecChild实例化后的具体运行流程:
WebServer.exe主进程启动后将进程消息句柄记录在属性信息中。为了避免启动任务进程影响系统的运行效率,在启动管理主进程后应该预先启动三个WebServerChild网络通信进程和几个WebexecChild执行进程,使用CreateProcess启动任务进程,函数调用结束后返回进程ID号,找出子进程信息列表中未记录进程信息的位置,将内容写入该位置,同时将进程设置为psInit状态表示该进程正在启动中,还不能接收任务处理。
WebServerChild.exe和WebExecChild.exe子进程启动后打开管理进程创建的共享内存区,将其影射到该进程地址中,这样处理子进程就可以访问进程信息列表中的信息。将进程状态修改为psSuspend,说明进程已经启动成功,可以执行任务。同时使用PostMessage向管理主进程发出WM_PROCESS_OPEN消息,通知管理主进程可以为该进程分配任务。
WebServer.exe主进程将启动一个WebServerChild.exe进程用来接收网络请求,然后进入等待任务求请状态,实时检测管道进程属性信息表变量Request属性值,如果值为1表示需要分配一个资源给服务线程来处理任务。
当Web服务接收到一个任务请求时,WebServerChild.exe将激活一个线程任务,首先判断处理子进程属性信息结构,该线程是否已经分配了处理资源,如果已经分配了资源,直接向WebExecChild.exe处理进程发出处理请求,如果未分配资源,将管道进程属性信息变量Request值设为1,向WebServer.exe主进程发出请求,管理进程为该线程分配资源,将Result设置为0~N的值,指定处理子进程属性信息具体的位置,同时向分配进程发出WM_PROCESS_EXECUTE消息,通知WebExecChild.exe进程进入执行状态。如果所有的进程都处于处理状态,管理进程直接启动一个进程来执行任务。的到资源后线程向该结构分别填写Command:处理命令信息,DataState:0输入状态,Size:输入数据大小,Data:数据内容,State:进程状态设为psExecute,然后等待进程处理结束返回结果。如果Data定义的最大空间不能放下输入的数据,可以采用多次填入数据的方法来实现大数据的输入,采用IsLastFragment来识别数据是否传输完成。
例如:客户端需要将PACS服务器上的DICOM图像文件专换成一个JPG格式文件,返回到Web浏览器上进行显示,可以在Web浏览器输入如下命令http://www.testwado.com/WebPACS?requestType=WADO&studyUID=1.2.826.0.1.3680043.2.461.3803757.1278770276&seriesUID=1.3.46.670589.26.702387.2.20100201.102436.66744&objectUID=1.3.46.670589.26.702387.4.20100201.102525.66744.0&contentType=image%2Fjpeg&columns=800&rows=800&windowCenter=1500&windowWidth=2000&imageQuality=90
当服务器接收到这条命令后,我们将内容写入处理子进程属性信息结构传送给WebExecChild.exe进行处理。
WebExecChild.exe进程接收到处理任务,从处理子进程属性信息表中读取数据,首先到PACS存储服务器读取对应的DICOM图像文件,然后根据输入参数将宽高设置成800X800,窗宽窗位设置成WW2000WL1500,以90的压缩率转换成JPG图像文件,处理结束后向任务线程返回处理结果,分别填写DataState:1输出状态,Size:输出数据大小,Data:数据内容。如果处理错误DataState值-1处理错误。如果Data定义的最大空间不能放下输出的数据,可以采用多次填入数据的方法来实现大数据的输出,采用IsLastFragment来识别数据是否传输完成。
WebServerChild.exe任务线程判断DataState状态如果值为1,说明任务处理完成读取Data数据,将内容返回给Web浏览器,结束任务。如果值为-1,说明处理错误,将进程状态设置为psError,结束任务。管理进程定期检测各进程状态,发现有psError状态进程,将其关闭。
为了提高WebServerChild.exe线程与WebExecChild.exe进程交互效率,避免任务线程每次处理都要向管理进程申请资源,所以当进程任务处理完后,并没有直接退出执行状态,仍然记录对应线程信息,延迟一段时间,等待该线程的下一次处理任务。
延迟一段时间,如果未接收到对应线程任务,WebExecChild.exe进程重新进入psSuspend状态将挂起时候记录在信息列表结构变量SuspendTime中,同时发管理进程发出WM_PROCESS_SUSPEND消息,通知管理进程该处理进程可以接收下一个线程任务。我们为每个WebExecChild.exe进程设置了一个生存期,然后通过与SuspendTime时间进行判断来确认是否已经超过了进程的生存期。如果进程已经超过生存期结束进程,向管理进程发出WM_PROCESS_CLOSE消息。
由于每个WebExecChild.exe进程都有一定的生存期,也就是说过一段时间后所有的旧进程都会被关闭,如果没有了WebExecChild.exe进程,就无法接收任务请求,所以我们设置了一个初始默认启动进程数,如果进程小于指定数量,管理进程会启动新的任务进程来补充处理任务。使用定期更换进程的方法可以进一步提高系统的稳定性。
当系统关闭的时候WebServer.exe进程会向各WebExecChild.exe子进程发出WM_PROCESS_STOP消息,要求关闭所有处理。
使用该方法,Web服务程序内的线程任务只负责将任务输入数据拷贝到数据交换区,然后等待处理结果,处理结束后再从数据交换区拷贝输出数据返回结Web浏览器,服务进程内部环节非常简单,稳定性高。当WebExecChild.exe处理当前一次任务的时候发生未知的异常错误,中止了程序,WebServerChild.exe任务线程等待处理过程延迟一段时间后任为系统处理错误,结束等待回返浏览器错误信息,这样并不会影响到系统的稳定性。
本领域技术人员不脱离本发明的实质和精神,可以有多种变形方案实现本发明,以上所述仅为本发明较佳可行的实施例而已,并非因此局限本发明的权利范围,凡运用本发明说明书及附图内容所作的等效结构变化,均包含于本发明的权利范围之内。