用户中心

资讯 > SCADA

基于GPRS和Internet的SCADA系统研究与实现(二)

作者:季堃2010.09.02阅读 5258

        第 3 章系统结构和通信服务器的设计实现
        3.1 系统结构
        3.1.1 系统组成
        基于GPRS 的数据采集与监控系统主要由上位机、下位机、DTU 或GPRS Modem、GPRS 无线网络、现场仪表等组成。
        上位机即中心服务器
        下位机可以是工业控制计算机(IPC)或专业数据采集仪。
        DTU 是数据传输单元(Data Transmit Unit),用于通过GPRS 网络与上位机 建立连接。
        现场仪表用于得到各种采集量。
        上位机与 DTU 或GPRS Modem 之间采用无线连接方式。 DUT 或GPRS Modem 与IPC 或数据采集仪之间采用RS232 方式连接。 IPC 或数据采集仪与现场仪表之间采用的连接方式可根据具体使用的仪器 不同采用不同的连接方式,如RS-232、RS-485、或直接接入模拟量。
        按各部分所在位置可分为四层模型:

        上位机层组网结构:

        3.1.2 树状模型
        整个系统具有很好的扩展性。可扩展为树状(金字塔型)省市县多级监控 管理系统(见图3-3),利用internet 为通信媒介实现下级监控中心向上级监控 中心发送数据,这样就可以通过一级一级向上发送,将数据汇总到树的根结点 (即金字塔的顶端)。上级也可以通过下级中心站服务器转发控制命令,实现对 监控点的跨级控制。这种多层管理结构具有很多优点:以地区行政单位为网络 划分依据,简单明了,具有很好的扩展性。易于各级行政部门对各自管辖内的 监控点进行监测管理,权责分明,有利于提高工作效率。多个中心站系统的模 式,分散了中心站系统的工作压力,也分散了故障风险,使整个系统工作更加 稳定。利用已经很完善的internet 网络,免去了网络构建的成本。

        3.2 通信服务器的设计与开发
        通信服务器采用Microsoft Windows 2000 server 为操作系统,完成中心站与 下位机之间的所有通信任务,也包括该中心站与其子站点或父站点的所有通信 任务。数据采集方案采用于主动告警、数据轮巡采集、告警主动回叫等对传输带宽占用较少的采集方式。
        3.2.1 传输层协议的选择
        目前在Internet 上TCP/IP 协议栈已经是大行其道,已成为网络通信的一种 标准。TCP/IP 协议栈分为四层(表3-2):应用层,运输层,网际层,网络接口层 [17]。

        要实现在Internet 和GPRS 网络上进行数据通信要利用运输层的应用程序接 口(Application Program Interface, API)进行程序设计,TCP/IP 的运输层主要有 两个协议TCP(传输控制协议Transmission Control Protocol)和UDP(用户数 据报协议User Datagram Protocol)。下面就对两个协议在GPRS 网络上应用的特 点进行分析。
        UDP是一个简单的面向数据报的运输层协议:进程的每个输出操作都正好产 生一个UDP 数据报,并组装成一份待发送的IP 数据报。UDP 数据报封装成一份IP 数据报的格式如图3-4所示。UDP不提供可靠性连接,它把应用程序传给IP层的数 据发送出去,但是并不保证它们能到达目的地。TCP 和UDP 都使用相同的网络层 ( IP) , TCP 提供了一种可靠的面向连接的字节流运输层服务,如图3-5所示。TCP 向应用层提供与UDP 完全不同的服务,TCP 提供一种面向连接的、可靠的字节流 服务。TCP将用户数据打包构成报文段;它发送数据后启动一个定时器,等待对端 数据确认;另一端对收到的数据进行确认,对失序的数据重新排序,丢弃重复数 据。TCP提供端到端的流量控制,并计算和验证一个强制性的端到端检验和。 TCP 与UDP 的区别(表3-3):

        3.2.2 两种协议的传输效率
        在只考虑UDP/ TCP 分组情况下,发送应用数据时,数据包为IP 头+ UDP/ TCP 头+ 应用数据(图3-3,图3-4)。GPRS 网络计费按照流量计费,数据传送效率就显 得十分重要。由于目前分组数据计费按照网络协议层以上数据计算(即IP 包数 据) ,传输效率的计算公式为:

        dataLen——用户数据字节数
        headLen——协议首部字节数(包括IP首部和TCP/UDP首部)

        通过协议内容分析(表3-4),可以看到,当单包传送的用户数据量比较小 时,UDP 协议传输效率明显高于TCP协议。行业应用数据量比较小,不同的行业应 用在选择协议时,需要仔细分析应用层数据单帧字节数。图3-6所示为两种协议传 输效率的比较。

        以上只是数据分组的传输效率, TCP 协议还需连接、终止连接和ACK 包等额外开销,UDP 与TCP 实际的传送效率差别将远大于表3-4中所列的计算数值。 GPRS 应用于行业,无论是电力抄表、管网监测、气象采集和金融业务等,都 是终端设备与数据服务器之间的通信。不同的应用,对传输可靠性的要求是不同 的,有的可以接受少量数据丢失,有的必须确保任何数据都不丢失,有的不接受超 时效数据。不同的应用中,相同的特点是不依赖传输手段提供的数据保障,终端与 数据中心之间有各自的通信协议,通过误码/超时重传等方法,确保数据的安全准 确。采用UDP协议传送时,UDP包等同于应用数据包,基本没有额外开销。
        TCP协议按照协议窗口进行多包统一确认的方式,可以减少ACK报文的数量, 但是在应用于行业时,应用的特点是数据量小,发送间隔通常从几秒到几小时之 间不等,数据报文之间发送间隔通常超过TCP协议需要的最大确认间隔,导致几乎 每个数据报文都需要在TCP协议中的ACK报文。
        在整个应用系统中,传输保障是由应用协议与网络协议共同完成的,要充分 发挥应用协议与网络协议的优势,达到总的效率最高、效果最好的目的。在应用 协议中,大多具有基本的传输保障功能,通过应用层协议中超时重传等功能完全 可以满足对UDP协议中少量丢包情况的处理,按照UDP丢包的概率,重传概率在1% 左右。如果选用TCP协议,将导致数据量大大增加。
        TCP连接保证数据传输的可靠性,每个具体TCP的实现必须选择一个报文段最 大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络 内的最长时间,而这个时间是有限的。RFC793指出MSL为2min。然而,实现中的常 用值是30s、1min或2min。对于部分实时监控系统,超过时效的数据就没有任何用 途。使用UDP连接,当网络拥塞时,部分数据包被丢弃,但可以改善接收数据严重滞 后的情况。
        无论选用UDP还是TCP协议,都对网络及服务器系统不会产生明显压力[18]。使 用TCP协议时,可靠的传输显得更为便利,但TCP协议却不适合大规模使用。如城市 电力配网自动化以及抄表等应用,一个系统可能有成千上万台终端,如果选用TCP 连接,服务器除了完成大量数据处理功能外,还需要保持对大量GPRS终端的TCP连 接,这对服务器的的承载能力提出了严格的要求。如果终端连接数量大,使用TCP 连接协议可能带来更严重的问题。GPRS终端与服务器建立了TCP连接并发送数据 后,或者服务器向正在请求连接的终端发出SYN+ACK应答报文后,都可能无法收到对端的ACK报文,在这种情况下发送端一般会重试并等待一段时间后终止这个的 连接,一般来说这个时间大约为30 s~2 min。一个终端出现异常导致服务器的一 个线程等待1 min 不是大问题,但如果网络拥塞导致大量这种情况,服务器端将 为了维护一个非常大的半连接列表而消耗非常多的资源,即使是简单的保存并遍 历也会消耗非常多的CPU时间和内存控制工程网版权所有,何况还要不断对这个列表中的IP进行重试。 如果服务器的TCP/IP栈不够强大,最后的结果往往是堆栈溢出崩溃。即使服务器 系统足够强大,也会因忙于处理TCP连接请求以及重传数据而导致系统性能严重 下降。大量重传数据会进一步加剧GPRS 网络的拥塞情况,严重时可以使GPRS 网 络及服务器系统崩溃。
        3.2.3 工业场合的应用的特点
        (1)要求时时传输,但也有一些场合是定时传输,总的来说在整个传输过 程中要求服务器中心端和GPRS 终端设备能相互的、时时的传输数据。 TCP 本身就是可靠链路传输www.cechina.cn,提供一个时时的双向的传输通道,能很好的 满足工业现场传输的要求。但是GPRS 网络对TCP 链路也存在一个限制:此条 链路在长时间(大概20 分钟左右,视具体情况而定)没有数据流量,会自动降 低此链路的优先级直至强制断开此链路。所以在实际使用中也会采用心跳包(一 般是一个字节的数据)来维持此链路。
        UDP 由于自身特点,以及GPRS 网络UDP 端口资源的有限性,在一段时 间没有数据流量后,端口容易改变,产生的影响就是从服务器中心端向GPRS 终端发送数据,GPRS 终端接收不到。具体的原因就是移动网关从中作了中转, 需要隔一定时间给主机发UDP 包来维持这个IP 和端口号,这样主机就能主动给 GPRS 发UDP 包了并且我在测试中发现,这个间隔时间很短,我在1 分钟发一次 UDP 包才能够维持,但是再长可能移动网关就要丢失这个端口了,此时如果主机 想主动发数据给GPRS,那肯定是不行的了,只有GPRS 终端设备再发一个UDP 包过去www.cechina.cn,移动重新给你分配一个中转IP 和端口,才能够进行双向通讯。
        (2)要求数据的丢包率较小。有些工业场合,例如电力、水务抄表,环保 监测等等,不容许传输过程中的数据丢失或者最大限度的要求数据的可靠性。 从这一点来看,很显然在无线数据传输过程中,TCP 比UDP 更能保证数据的完整性、可靠性,存在更小的丢包率。在实际测试中也是如此。
        (3)要求降低费用。在大部分GPRS 设备的应用中除了使用范围外,其考 虑的主要问题就是费用。能降低费用当然都是大家最愿意接受的。和费用直接 相关的就是流量了,流量低,费用就低了。
        在通信过程中数据的大小直接关系到费用的高低。应用层协议设计的是否 简洁高效就显的尤为重要。TCP 本身的包头要比UDP 长,所以一般来说UDP 协议比TCP 协议更加节省费用。但是在实际应用中需要根据行业应用特点和通 信数据的特点来决定。在数据发送频率较低的应用中,往往UDP 需要维护双向 通道,就必须要通过高频率的心跳包数据来维护端口资源,只有这样才能保证 数据的双向通信。在数据发送频率较高的应用中,如不到60 秒就有数据需要发 送,就可以省去心跳包的发送。UDP 则比TCP 有优势。在数据发送频率较低的 应用场合和有大块数据需要连续发送的应用场合,则TCP 比UDP 有优势。 在某些特定的应用场合,例如一些银行的时时交互系统,对响应速度要求 很高,此时数据传输频率较快,不需要大量心跳包维持UDP 端口资源,采用 UDP 就比较有利了。
        (4)要求服务器中心高效。在目前的1:N 的传输模式中,既有多个GPRS 终端设备往一个服务器中心传输数据,此时采用UDP 会比TCP 要好的多,因 为UDP 耗用更少的系统资源。但是在实际应用中,还是采用TCP 的传输方式 可以利用一些方法解决这一问题,如建立二级中心1:A(1:N),即每一个分 中心对应N/A 台设备,独立处理数据,再统一将数据传送到主中心。这样既能 保证了传输过程中采用了TCP 的传输协议,又能很好处理了中心服务器的多链 路的系统耗用的问题。
        3.2.4 TCP 服务器的实现
        这里主要讨论Windows平台下TCP服务器的实现。WIN32平台上的socket编程, Winsock是访问的首选接口[19]。而且在每个Win32平台上,Winsock都以不同的形 式存在着。Winsock是网络编程接口,而不是协议。它从Unix平台的Berkeley(BSD) 套接字方案借鉴了许多东西,后者能访问多种网络协议。在Win32环境中,Winsock 接口最终成为一个真正的“与协议无关”接口,尤其是在Winsock 2发布之后。 利用Winsock开发的TCP通信程序的流程如图3-7。

        3.2.4.1 服务器端的实现方法[19]
        (1)Winsock的初始化
        每个Winsock应用都必须加载Winsock DLL的相应版本。如果调用Winsock之 前,没有加载Winsock库,这个函数就会返回一个SOCKET_ERROR,错误信息是 WSANOTINITIALISED。加载Winsock库是通过调用WSAStartup函数实现的。
        (2)创建套接字
        创建套接字通过调用Socket 或WSASocket 函数实现。
        (3)绑定套接字
        一旦为某种特定协议创建了套接字,就必须将套接字绑定到一个已知地址。 bind函数可将指定的套接字同一个已知地址绑定到一起。
        (4)监听(listen)
        接下来将套接字置入监听模式。bind函数的作用只是将一个套接字和一个指 定的地址关联在一起。指示一个套接字等候进入连接的API函数则是listen。
        (5)接受客户连接
        监听过后就已做好了接受客户连接的准备。接受客户连接通过accept或 WSAAccept函数来完成的。
        (6)数据收发
        在服务器与客户端应用程序建立了TCP socket连接以后就可以在两个进程 之间进行数据通信了。收发数据是网络编程的主题,发送数据可用这两个API函 数:send和WSASend。同样地,在已建立了连接的套接字上接收数据也有两个函 数:recv和WSARecv。
        (7)中断连接
        一旦完成通信任务,就必须关掉连接,释放关联到那个套接字句柄的所有资 源。要真正地释放与一个开着的套接字句柄关联的资源,执行closesocket调用 即可。但closesocket可能会带来负面影响(和如何调用它有关),即可能会导 致数据的丢失。鉴于此,应该在调用closesocket函数之前,利用shutdown函数 从容中断连接。
        3.2.4.2 Winsock 套接字模式和套接字I/O 模型
        Winsock提供了两种套接字模式:锁定和非锁定[19]。在锁定模式下,在I/O 操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不 会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Winsock函数无 论如何都会立即返回。
        对锁定套接字来说,它的一个缺点在于:应用程序很难同时通过多个建好连 接的套接字通信。使用前述的办法,可对应用程序进行修改,令其为连好的每个 套接字都分配一个读线程,以及一个数据处理线程。尽管这仍然会增大一些开销, 但的确是一种可行的方案。唯一的缺点便是扩展性极差,非锁定模式的套接字。 尽管这种套接字在使用上存在着些许难度,但只要排除了这项困难,它在功能上 还是非常强大的。除具备锁定套接字已有的各项优点之外,还进行了少许扩充, 功能更强。
        将一个套接字置为非锁定模式之后,Winsock API调用会立即返回。大多数 情况下,这些调用都会“失败”,并返回一个WSAEWOULDBLOCK错误。这意味着请 求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中, 尚不存在“待决”的数据,那么recv(接收数据)调用就会返回WSAEWOULDBLOCK 错误。通常,需要重复调用同一个函数,直至获得一个成功返回代码。
        由于非锁定调用会频繁返回WSAEWOULDBLOCK错误,所以在任何时候,都应仔细检查所有返回代码,并作好“失败”的准备。许多程序员易犯的一个错误便是 连续不停地调用一个函数,直到它返回成功的消息为止。例如,假定在一个紧凑 的循环中不断地调用recv,以读入200个字节的数据,那么与使用前述的MSG_PEEK 标志来“轮询”一个锁定套接字相比,前一种做法根本没有任何优势可言。为此, Winsock的套接字I/O模型可帮助应用程序判断一个套接字何时可供读写。
        锁定和非锁定套接字模式都存在着优点和缺点。其中,从概念的角度说,锁 定套接字更易使用。但在应付建立连接的多个套接字时,或在数据的收发量不均, 时间不定时,却显得极难管理。而另一方面,假如需要编写更多的代码,以便在 每个Winsock调用中,对收到一个WSAEWOULDBLOCK错误的可能性加以应付,那么 非锁定套接字便显得有些难于操作。在这些情况下,可考虑使用“套接字I/O模 型”,它有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的 通信加以管理。
        Wnsock提供了一些有用的I/O模型,有助于应用程序通过一种“异步”方式, 一次对一个或多个套接字上进行的通信加以管理。这些模型包括select(选择)、 WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、OverlappedI/O (重叠式I/O)以及Completionport(完成端口)
        (1) select模型
        select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“select 模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理!最 初设计该模型时,主要面向的是某些使用Unix操作系统的计算机,它们采用的是 Berkeley套接字方案。select模型已集成到Winsock1.1中,它使那些想避免在套 接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对 多个套接字的管理。由于Winsock1.1向后兼容于Berkeley套接字实施方案,所以 假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对 其进行任何修改,便可正常运行。
        利用select函数,就可以判断套接字上是否存在数据,或者能否向一个套接 字写入数据。之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处 于锁定模式中时,在一次I/O绑定调用(如send或recv)过程中,被迫进入“锁 定”状态;同时防止在套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。 除非满足事先用参数规定的条件,否则select函数会在进行I/O操作时锁定。
        (2)WSAAsyncSelect
        Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个 套接字上,接收以Windows消息为基础的网络事件通知。具体的做法是在建好一 个套接字后,调用WSAAsyncSelect函数。该模型最早出现于Winsock的1.1版本中, 用于帮助应用程序开发者面向一些早期的16位Windows平台(如Windows for Workgroups),适应其“落后”的多任务消息环境。应用程序仍可从这种模型中 得到好处,特别是它们用一个标准的Windows例程(常称为“winproc”),对窗 口消息进行管理的时候。该模型亦得到了Microsoft Foundation Class(微软基 本类,MFC)对象CSocket的采纳。要想使用WSAAsyncSelect模型,在应用程序中, 首先必须用CreateWindow函数创建一个窗口,再为该窗口提供一个窗口例程支持 函数(Winproc)。
        主要的网络事件类型包括:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT 和FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于 应用程序的身份到底是一个客户机,还是一个服务器。
        (3)WSAEventSelect
        Winsock提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的 是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通 知。WSAAsyncSelect模型采用的网络事件,均可原封不动地移植到该模型。在用 该模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别 在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。
        事件通知模型要求应用程序针对打算使用的每一个套接字,首先创建一个事 件对象。创建方法是调用WSACreateEvent函数,它的定义如下: WSAEVENT WSACreateEvent(void);
        WSACreateEvent 函数的返回值很简单,就是一个创建好的事件对象句柄。 事件对象句柄到手后,接下来必须将其与某个套接字关联在一起,同时注册自 己感兴趣的网络事件类型。这些事件类型与WSAAsyncSelectI/O 模型的事件相 同。
        (4)重叠模型
        在Winsock中,相比上面提到的几种I/O模型,重叠I/O(OverlappedI/O)模 型使应用程序能达到更佳的系统性能。重叠模型的基本设计原理便是让应用程序 使用一个重叠的数据结构,一次投递一个或多个WinsockI/O请求。针对那些提交 的请求,在它们完成之后,应用程序可为它们提供服务。该模型适用于除 WindowsCE之外的各种Windows平台。模型的总体设计以Win32重叠I/O机制为基 础。那个机制可通过ReadFile和WriteFile两个函数,针对设备执行I/O操作。
        最开始的时候,Winsock重叠I/O模型只能应用于WindowsNT操作系统上运行 的Winsock1.1应用程序。作为应用程序,它可针对一个套接字句柄,调用ReadFile 以及WriteFile,同时指定一个重叠式结构,从而利用这个模型。自Winsock2发 布开始,重叠I/O便已集成到新的Winsock函数中,比如WSASend和WSARecv。这样 一来,重叠I/O模型便能适用于安装了Winsock2的所有Windows平台。
        要想在一个套接字上使用重叠I/O模型,首先必须使用WSA_FLAG_OVERLAPPED 这个标志,创建一个套接字。如下所示:
        s = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FALG_OVERLAPPED); 创建套接字的时候,假如使用的是socket函数,而非WSASocket函数,那么会默 认设置WSA_FLAG_OVERLAPPED标志。成功建好一个套接字,同时将其与一个本地 接口绑定到一起后,便可开始进行重叠I/O操作。
        (5)完成端口模型
        “完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假如一个应 用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳 的系统性能。该模型只适用于WindowsNT和Windows2000操作系统。因其设计的复 杂性,只有应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着 系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用 “完成端口”模型。要记住的一个基本准则是,假如要为WindowsNT或Windows2000 开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器 便是这方面的典型例子),那么I/O完成端口模型便是最佳选择.
        从本质上说,完成端口模型要求先创建一个Win32完成端口对象,通过指定 数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。 要注意的是,所谓“完成端口”,实际是Win32、WindowsNT以及Windows2000采用的一种I/O构造机制,除套接字句柄之外,实际上还可接受其他东西。然而, 本文只讲述如何关联套接字句柄,来使用完成端口模型提升TCP服务器的性能。 使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套 接字句柄,管理多个I/O请求。要做到这一点,需要调用CreateCompletionPort函 数。该函数定义如下:
        HANDLE CreateIoCompletionPort(
        HANDLE FileHandle,
        HANDLE ExistingCompletionPort,
        DWORD CompletionKey,
        DWORD NumberOfConcurrentThreads
        );
        在说明其中的各个参数之前,首先要注意该函数实际用于两个明显有别的目 的:
        ■用于创建一个完成端口对象。
        ■将一个句柄同完成端口关联到一起。
        最开始创建一个完成端口时,唯一要关注的参数便是Number Of Concurrent Threads ( 并发线程的数量) ; 前面三个参数都会被忽略。
        NumberOfConcurrentThreads参数的特殊之处在于控制工程网版权所有,它定义了在一个完成端口上, 同时允许执行的线程数量。理想情况下,希望每个处理器各自负责一个线程的运 行,为完成端口提供服务,避免过于频繁的线程切换。若将该参数设为0,表明 系统内安装了多少个处理器,便允许同时运行多少个线程。可用下述代码创建一 个I/O完成端口:
        CompletionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0); 该语句的作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对 那个端口进行引用。
        3.2.4.3 I/O 完成端口模型的TCP 服务程序的设计和实现 I/O完成端口模型是WIN32平台性能最好的模型,用这种模型实现的TCP服务 程序可以接受最大量的客户端连接请求,具有很高的数据吞吐量,具有很好扩展 性。是监控系统中TCP服务程序设计很好的方案。
        (1)I/O完成端口
        首先创建套接字,然后利用CreateIoCompletionPort函数创建一个完成端 口,在成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但 在关联套接字之前,首先必须创建一个或多个“工作者线程”,以便在I/O请求 投递给完成端口对象后,为完成端口提供服务。创建多少个工作者线程比较合适, 这实际正是完成端口模型显得颇为“复杂”的一个方面,因为服务I/O请求所需 的数量取决于应用程序的总体设计情况。在此要注意的是, 在调用 CreateIoComletionPort时指定的并发线程数量,与打算创建的工作者线程数量 相比,它们代表的并非同一件事情。理论上说,CreateIoCompletionPort函数应 该为每个处理器都指定一个线程(处理器的数量有多少,便指定多少线程)以避 免由于频繁的线程切换,从而影响系统的整体性能。CreateIoCompletionPort 函数的NumberOfConcurrentThreads参数明确指示系统:在一个完成端口上,一 次只允许n个工作者线程运行。假如在完成端口上创建的工作者线程数量超出n 个,那么在同一时刻,最多只允许n个线程运行。但实际上,在一段较短的时间 内, 系统有可能超过这个值, 但很快便会把它减少至事先在 CreateIoCompletionPort函数中设定的值。实际创建的工作者线程数量有时要比 CreateIoCompletionPort函数设定的多一些。如先前所述,这主要取决于应用程 序的总体设计情况。假定某个工作者线程调用了一个函数,比如Sleep 或 WaitForSingleObject,但却进入了暂停(锁定或挂起)状态,那么允许另一个 线程代替它的位置。换言之,我们希望随时都能执行尽可能多的线程;当然,最 大的线程数量是事先在CreateIoCompletonPort调用里设定好的。这样一来,假 如事先预计到自己的线程有可能暂时处于停顿状态,那么最好能够创建比 CreateIoCompletonPort的NumberOfConcurrentThreads参数的值多的线程,以便 到时候充分发挥系统的潜力。
        一旦在完成端口上拥有足够多的工作者线程来为I/O请求提供服务,便可着 手将套接字句柄同完成端口关联到一起。这要求在一个现有的完成端口上,调用 CreateIoCompletionPort 函数, 同时为前三个参数— FileHandle , ExistingCompletionPort 和CompletionKey — 提供套接字的信息。其中, FileHandle 参数指定一个要同完成端口关联在一起的套接字句柄。
        ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey(完 成键)参数则指定要与某个特定套接字句柄关联在一起的“单句柄数据”;在这 个参数中,应用程序可保存与一个套接字对应的任意类型的信息。之所以把它叫 作“单句柄数据”,是由于它只对应着与那个套接字句柄关联在一起的数据。可 将其作为指向一个数据结构的指针,来保存套接字句柄;在那个结构中,同时包 含了套接字的句柄,以及与那个套接字有关的其他信息。
        使用完成端口模型完成服务器应用程序的设计。基本上按下述步骤行事:
        1)创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一 次只允许执行一个工作者线程。
        2)判断系统内到底安装了多少个处理器。
        3)创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的 I/O请求提供服务。在这个算法中,为每个处理器都只创建一个工作者线程。这 是由于事先已预计到,到时不会有任何线程进入“挂起”状态,造成由于线程数 量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用 CreateThread函数时,必须同时提供一个工作者例程,由线程在创建好执行。本 节稍后还会详细讨论线程的职责。
        4)准备好一个监听套接字,在端口5150上监听进入的连接请求。
        5)使用accept函数,接受进入的连接请求。
        6)创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接 字句柄。
        7)调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关 联到一起。通过完成键(CompletionKey)参数,将单句柄数据结构传递给 CreateIoCompletionPort。
        8)开始在已接受的连接上进行I/O操作。在此,可以通过重叠I/O机制,在新建的 套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一 个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求。
        9)重复步骤5)~8),直至服务器中止。
        (2)完成端口和重叠I/O
        将套接字句柄与一个完成端口关联在一起后,便可以以套接字句柄为基础,投递发送与接收请求,开始对I/O请求的处理。接下来,可以开始依赖完成端口 来接收有关I/O操作完成情况的通知。从本质上说,完成端口模型利用了Win32 重叠I/O机制。在这种机制中,象WSASend和WSARecv这样的Winsock API调用会立 即返回。此时,需要由应用程序负责在以后的某个时间,通过一个OVERLAPPED 结构,来接收调用的结果。在完成端口模型中,要想做到这一点,需要使用 GetQueuedCompletionStatus(获取排队完成状态)函数,让一个或多个工作者 线程在完成端口上等待。该函数的定义如下:
        BOOL GetQueuedCompletionStatus(
        HANDLE CompletionPort,
        LPDWORD lpNumberOfBytesTransferred,
        LPDWORD lpCompletionKey,
        LPOVERLAPPED * lpOverlapped,
        DWORD dwMilliseconds
        );
        其中, CompletionPort 参数对应于要在上面等待的完成端口。 lpNumberOfBytesTransferred参数负责在完成了一次I/O操作后(如WSASend或 WSARecv),接收实际传输的字节数。lpCompletionKey参数为原先传递进入 CreateCompletionPort函数的套接字返回“单句柄数据”。如前面所述,已经将 套接字句柄保存在这个“键”(Key)中。lpOverlapped参数用于接收完成的I/O 操作的重叠结果。这实际是一个相当重要的参数,因为可用它获取每个I/O操作 的数据。而最后一个参数,dwMilliseconds,用于指定调用者希望等待一个完成 数据包在完成端口上出现的时间。假如将其设为INFINITE,调用会无休止地等待 下去。
        (3)单句柄数据和单I/O操作数据
        一个工作者线程从GetQueuedCompletionStatus这个API调用接收到I/O完成 通知后,在lpCompletionKey和lpOverlapped参数中,会包含一些必要的套接字 信息。利用这些信息,可通过完成端口,继续在一个套接字上的I/O处理。通过 这些参数,可获得两方面重要的套接字数据:单句柄数据,以及单I/O操作数据。 其中,lpCompletionKey参数包含了“单句柄数据”,因为在一个套接字首次与完成端口关联到一起的时候,那些数据便与一个特定的套接字句柄对应起来 了。这些数据正是在进行CreateIoCompletionPort API调用的时候,通过 CompletionKey参数传递的。如前所述,应用程序可通过该参数传递任意类型的 数据。通常情况下,应用程序会将与I/O请求有关的套接字句柄保存在这里。 lpOverlapped参数则包含了一个OVERLAPPED结构,在它后面跟随“单I/O操作数 据”。
        当工作者线程处理一个完成数据包时,这些信息是它必须要知道的。单I/O 操作数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。假如一个 函数要求用到一个OVERLAPPED结构,便必须将这样的一个结构传递进去,以满足 它的要求。要想做到这一点,一个简单的方法是定义一个结构,然后将OVERLAPPED 结构作为新结构的第一个元素使用。程序中定义了下述数据结构,实现对单I/O 操作数据的管理:
        typedef struct _PER_IO_DATA{
        OVERLAPPED Overlapped;
        WSABUF databuf;
        Char Buffer[BUFFER_SIZE];
        int BufferLen;
        int OperationType;
        }PER_IO_DATA, * LPPER_IO_DATA;
        在工作线程的后面部分,等GetQueuedCompletionStatus函数返回了一个重 叠结构(和完成键)后,便可通过查看对OperationType成员值,调查到底是哪 个操作投递到了这个句柄之上( 只需将返回的重叠结构转换为自己的 PER_IO_DATA结构)。对单I/O操作数据来说,它最大的一个优点便是允许程序在 同一个句柄上,同时管理多个I/O操作(读/写,多个读,多个写,等等)。这 种模型具有很好的扩展能力。如果运行的机器安装了多个中央处理器,每个处理 器都在运行一个工作者线程,那么在同一个时候,完全可能有几个不同的处理器 在同一个套接字上,进行数据的收发操作。为了完成TCP服务器,还需要设计一 个ServerWorkerThread(服务器工作者线程)函数。
        正确地关闭I/O完成端口。特别是同时运行了多个线程,在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时, 强行释放一个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个 套接字句柄,调用closesocket函数,任何尚未进行的重叠I/O操作都会完成。一 旦所有套接字句柄都已关闭,便需在完成端口上,终止所有工作者线程的运行。 要想做到这一点,需要使用PostQueuedCompletionStatus函数,向每个工作者线 程都发送一个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”。 下面是PostQueuedCompletionStatus函数的定义:
        BOOL PostQueuedCompletionStatus(
        HANDLE CompletionPort,
        DWORD dwNumberOfBytesTransferred,
        DWORD dwCompletionKey,
        LPOVERLAPPED lpOverlapped
        );
        其中,CompletionPort参数指定想向其发送一个完成数据包的完成端口对 象。而就dwNumberOfBytesTransferred、dwCompletionKey和lpOverlapped这三 个参数来说, 每一个都允许用户指定一个值, 直接传递给 GetQueuedCompletionStatus函数中对应的参数。这样一来,一个工作者线程收 到传递过来的三个GetQueuedCompletionStatus函数参数后,便可根据由这三个 参数的某一个设置的特殊值,决定何时应该退出。例如,可用dwCompletionKey 参数传递0值,而一个工作者线程会将其解释成中止指令。一旦所有工作者线程 都已关闭,便可使用CloseHandle函数,关闭完成端口,最终安全退出程序。
        3.2.5 UDP 服务器的实现
        无连接协议(UDP)和面向连接的协议(TCP)比较起来,无连接协议的行 为极为不同,因此,收发数据的方式也会有所不同。其通信过程如图3-8。

        3.2.5.1 接收数据
        对于在一个无连接套接字[7]上接收数据的进程来说,步骤并不复杂。先用 socket或WSASocket建立套接字。再把这个套接字和准备接收数据的接口绑定在 一起。这是通过bind函数来完成的。和面向连接不同的是,程序不必调用listen 和accept。相反,只需等待接收数据。由于它是无连接的,因此始发于网络上任 何一台机器的数据报都可被接收端的套接字接收。最简单的接收函数是 recvfrom。它的定义如下:
        int recvfrom (
        SOCKET s,
        char FAR* buf,
        int len,
        int flags,
        struct sockaddr FAR* from,
        int FAR* fromlen
        );
        前面四个参数和recv是一样的,其中包括标志MSG_OOB和MSG_PEEK。在使用 无连接套接字时,和前面一样。对监听套接字的具体协议来说,from参数是一个 SOCKADDR结构,fromlen是form指向的地址结构的长度。这个API调用返回数据时, SOCKADDR结构from中内便填入发送数据的那个工作站的地址。
        recvfrom函数的Winsock2版本是WSARecvFrom。后者的原型是:
        int WSARecvFrom (
        SOCKET s,
        LPWSABUF lpBuffers,
        DWORD dwBufferCount,
        LPDWORD lpNumberOfBytesRecvd,
        LPDWORD lpFlags,
        struct sockaddr FAR * lpFrom,
        LPINT lpFromlen,
        LPWSAOVERLAPPED lpOverlapped,
        LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
        );
        两者的差别在于接收数据的WSABUF结构的用法上。你可以利用 dwBufferCount为WSARecvFrom提供一个或多个WSABUF缓冲。提供多个缓冲,就可 用发散集合了。读取的字节总数返回在lpNumberOfBytesRecvd中。在调用 WSARecvFrom时,lpFlags参数可以是代表无选项的0、MSG_OOB、MSG_PEEK或 MSG_PARTIAL。这些标志还可以累加起来。如果在调用这个函数时,指定 MSG_PARTIAL,提供者就知道返回数据,即使只收到了部分消息。调用返回之后, 如果只收到部分消息,就会设置MSG_PARTIAL标志。再次返回之后,WSARecvFrom 就会把lpFrom参数(它是一个指向SOCKADDR结构的指针)设为发送端的地址。再 次提醒大家注意,lpFromLen指向SOCKADDR结构的长度,另外,在这个函数中, 它还是一个指针,指向DWORD。最后两个参数,lpOverlapped和 lpCompletionROUTINE,用于重叠I/O。
        3.2.5.2 发送数据
        要在一个无连接的套接字上发送数据,有两种选择。第一种,也是最简单的 一种,便是建立一个套接字,然后调用sendto或WSASendTo。sendto函数,它的 定义是这样的:
        int sendto (
        SOCKET s,
        const char FAR * buf,
        int len,
        int flags,
        const struct sockaddr FAR * to,
        int tolen
        );
        除了buf是发送数据的缓冲,len指明发送多少字节外,其余参数和recvfrom 的参数一样。另外,to参数是一个指向SOCKADDR结构的指针,带有接收数据的那 个工作站的目标地址。另外,也可以用Winsock2函数WSASendTo。它的定义如下:
        int WSASendTo (
        SOCKET s,
        LPWSABUF lpBuffers,
        DWORD dwBufferCount,
        LPDWORD lpNumberOfBytesSent,
        DWORD dwFlags,
        const struct sockaddr FAR * lpTo,
        int iToLen,
        LPWSAOVERLAPPED lpOverlapped,
        LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
        );
        WSASendTo和前一版本中的SendTo函数类似。它把指向带有发给接收端的数据的 指针当作lpBuffers参数,dwBufferCount参数指明现在的结构是多少。可发送多 个WSABUF结构启用发散集合I/O。在函数返回之前,WSASendTo把第四个参数 lpNumberOfBytesSent设为真正发到接收端的字节数。lpTo参数是针对具体协议 的一个SOCKADDR结构,并带有接收端的地址。iToLen参数是SOCKADDR结构的长度。 最后两个参数,lpOverlapped和lpCompletionROUTINE,用于重叠I/O。
        3.2.5.3 关闭套接字
        因为无连接协议没有连接,所以也不会有正式的关闭和从容关闭。在接收端 或发送端结束收发数据时,它只是在套接字句柄上调用closesocket函数。这样, 便释放了为套接字分配的所有相关资源。
        3.2.5.4 可靠性设计
        由于 UDP 是非连接的,它尽最大可能将报文发送到目的主机,并不保证数 据的正确到达。所以需要开发人员在应用层协议的设计中考虑到报文丢失的可 能性,使整个通信过程可靠稳定。
        (1)确认和超时重传
        在使用中,为了确保数据正确到达接收端,协议要求接收端对于收到的每 个报文都要进行确认,即发送确认报文。发送端每发送出一个报文都要等待确 认信息,当等待时间超过一定时间后没有收到接收端的确认信息,发送端则认 为接收端没有收到发出的报文,将报文重发一遍,再等待,若重发两次后还没 收到接收端的确认信息,则放弃发送,向用户报告接收端不可达。这种技术的 实现关键在于如何确定等待的时间,即超时时间。超时值对网络通信的性能具 有很大影响。超时太短将造成不必要的重传,浪费计算机的处理时间,浪费网 络资源;超时太长,又将导致响应时间延长,实时性降低。理想的超时值应该 正好等于一个数据报文穿过网络到达接收端,接收端处理过后,发出的确认报 文到达发送端的时间,这个时间称为往返时间(RTT)。
        在 GPRS 无线网络中RTT 的影响因素较多,无线网络误码率较高,报文到 达目的地后成为无效报文的概率较高,语音业务和数据业务共用信道,可能发 生争抢信道的现象。各地移动公司的网络状况也不尽相同,所以在程序中使用 经过测算出的固定RTT 值是不合适的。
        该系统采用自适应超时值算法,需要计算发送每个报文的重传超时(RTO)。 计算RTO的关键是测量RTT:报文的实际往返时间。每次测得一个RTT后,就更 新两个统计性估计因子:srtt是平滑了的RTT估计因子,rttvar是平滑了的平均偏 差估计因子。后者只是标准偏差的一个很好的近似,而且不需要开方,很容易计 算。有了两个估计因子,可用以下公式计算出RTO。
        delta = RTT-srtt
        srtt = srtt+g×delta
        rttvar = rttvar+h(|delta|-rttvar)
        RTO = srtt+4×rttvar
        delta——测得的RTT和当前平滑了的RTT估计因子(srtt)之间的差。
        g——施加在RTT估计因子上的增益因子,值为1/8。
        h——施加在平均偏差估计因子上的增益因子,值为1/4。
        当重传定时器超时的时候,对下一个RTO要用一个指数退回。例如,如果RTO 是2秒,这段时间内应答没有收到,则下一个RTO是4秒;如果仍没有收到,下一 个RTO是8秒,接着是16秒,依此类推。
        (2) 流量控制
        根据环境数据的报文特点及其对网络环境传输效率的要求,本系统采用比较简 单的等停法,实现规则如下:发送方每发送一条报文后等待接收方的确认信息, 收到上一报文的确认信息后再发送下一条报文。虽然相对于滑动窗口法,这种 方法传输效率低网络吞吐量小,但是该系统不需要大批数据的传送,数据报文 较小,一般在一到两个分组内就可以完成。等停法发送报文单个确认,极大的 减少了无效报文的发送控制工程网版权所有,减轻了网络的负担。
        

版权声明:版权归控制工程网所有,转载请注明出处!

频道推荐

关于我们

控制工程网 & CONTROL ENGINEERING China 全球工业控制、自动化和仪器仪表领域的先锋媒体

CE全球

联系我们

商务及广告合作
任小姐(北京)                 夏小姐(上海)
电话:010-82053688      电话:18616877918
rendongxue@cechina.cn      xiashuxian@cechina.cn
新闻投稿:王小姐

关注我们的微信

关于我们 | 网站地图 | 联系我们
© 2003-2020    经营许可编号:京ICP证120335号
公安机关备案号:110102002318  服务热线:010-82053688