基于IOCP的北斗广域差分数据播发体制研究
2015-01-01
0 引言
北斗地基增强系统建成以后,数据播发平台将为全国用户提供高精度卫星定位服务,可接入的卫星差分定位终端也将达到数亿量级。随着北斗地基增强系统的不断推广,北斗差分定位用户数的不断增加,北斗数据播发平台未来必将面临同时处理百万级甚至千万级终端用户的并发访问。且根据播发服务的特点,用户与播发平台之间的连接大部分都为长连接。因此,播发系统在建设的过程中必然会面临高并发、长连接的大规模数据访问。
经典的多线程池模型为每一个客户端创造一个独立的线程,随着请求服务的客户端数量的增加,服务器将要创建大量的线程,但是线程数量是有限制的,而且大量的线程之间的切换也浪费了许多的CPU时间,严重影响了系统的效率。为了解决这些问题,微软花了数年时间进行研究和测试的一种非常好的机制来支持这种服务,这种机制被称为IOCP。
1 IOCP模型
IOCP是一种使用线程池处理异步I/O请求的高性能I/O模型,它的基本架构如图1所示:
图1 IOCP基本架构图
主要有三种类型的参与者:
完成端口:任何想利用IOCP方式来处理I/O请求的I/O设备,都必须把该IO设备的句柄关联到完成端口。完成端口维护是一个先进先出的I/O完成队列,操作系统把异步I/O操作完成事件通知放入该队列后,完成端口唤醒等待者线程队列中的一个工作线程对数据进行处理。
等待者线程队列:工作线程通过调用GetQueuedCompleionS tatus API,在完成端口上等待取下一个I/O完成包。
执行者线程组:工作线程已经从完成端口上获得I/O完成包,在占用CPU进行处理。
2 IOCP在北斗广域差分数据播发平台应用中存在的问题及解决方法
IOCP在实际应用于北斗广域差分数据播发平台时暴露出一些问题,下面详细介绍遇到的问题及恰当的解决方案。
2.1 用户端信息的识别和提取
基于IOCP的播发平台在实际应用中将处理大量用户的I/O请求,在完成端口完成用户的I/O请求后,交给工作线程池中的一个空闲线程处理,这个工作线程必须知道这个完成的工作 I/O请求来自哪个用户(该用户的Sockaddr_in结构数据和连接该用户的Socket),以及I/O数据存放位置(存放数据的缓冲)。
在IOCP中,工作线程通过调用GetQueuedCompletionStatus函数从一个指定的完成端口中获取一个I/O完成包。这个函数带有两个具有扩展性的参数,一个是 LPDWORD类型的参数lpCompletionKey(完成健);另一个是OVERLAPPED类型的指针。其中完成健是一个套接字句柄关联完成端口时的一个参数,在这里可以取出这个参数,这是一个指针类型的参数,我们可以通过扩展这个参数使其成为指向带有用户端地址信息的数据结。这样我们在这里就可以通过完成健获得用户端信息,识别用户。
IOCP使用OVERLAPPED结构进行异步、非阻塞I/O,在基于IOCP的输入、输出操作时必须带有一个指向OVERLAPPED结构的指针参数。工作线程可以得到这个参数,但是通过这个参数不能确定I/O的操作类型(READ、WRITE、ACCEPT)。通过上面的分析,我们可以扩展OVERLAPPED结构,使其包含所需的操作类型等用户跟踪信息,这样我们在工作线程中利用CONTAINING_RECORD取出扩展的数据结构,就可以确定操作类型。
2.2 数据包重排序
虽然IOCP是严格按照先进先出(Fist In First Out,FIFO)处理I/O请求,但是,因为工作线程是多线程的,可能导致实际I/O请求的完成顺序不确定。例如三个工作线程A、B、C依次从完成端口取出“数据包1、数据包2、数据包3”,由于三个独立线程工作情况的不确定性,可能处理完的顺序是“数据包3、数据包1、数据包2”等各种情况。针对这个问题,通过对I/O内存增加顺序号,并按内存顺序号依次处理,对内存号不符合当前要处理的顺序号要求时,先将该I/O内存保存。
2.3 系统内存资源管理
在基于IOCP的北斗广域差分数据播发平台中需要给每个合法的客户端连接分配一个内存空间,用于存储连接客户端的地址和SOCKET等信息(统称单句柄数据),当该连接断开时需要及时释放相应的内存资源。在北斗播发平台,大规模、高并发的用户请求将导致频繁的内存分配、释放,这种情况非常占用系统资源。针对这个问题,可以通过内存池的方法来解决,事先创建指定数量的单句柄数据内存块,然后将其全部放入一个空闲队列中,当有新的连接请求时,首先看空闲队列中是否还有可用的单句柄内存块:如果有,则将第一个可用的单句柄数据内存块取出使用;否则向系统申请一个单句柄数据内存块。在断开用户端连接,需要释放单句柄内存块时,首先判断空闲队列是否已满,如果未满,将单句柄内存块清空后存入队列;否则释放该内存块。
在播发平台中,需要高频率地接收、发送数据,每一次接收、发送数据都要申请一块用于存放将要发送或接收的数据、数据长度、操作类型等(统称单I/O数据)内存块。这种情况也涉及到内存块的频繁分配与释放,也将造成系统资源的极大浪费。针对这个问题,我们可以采用单I/O数据内存池管理的方法来解决。
2.4 多线程响应用户的连接
在典型的IOCP模型中,在主线程中调用阻塞的accept()函数接受用户的连接,然后把新建的连接套接字绑定到完成端口。在基于IOCP的播发平台,使用单线程响应用户的连接请求,无法满足大规模、高并发的用户访问需求。这个问题的解决方法是在主线程投递指定数量的异步、非阻塞的AcceptExI/O请求,利用IOCP机制在工作线程中处理用户连接请求,实现多线程并发处理用户连接请求,提高用户连接的响应速度。
3 基于IOCP的北斗播发平台设计
3.1 北斗播发平台的数据流
北斗播发平台的设计需要满足上亿注册用户、千万级同时在线用户的服务,这样大规模的用户量,单台服务器是不可能满足需求的。根据实际情况,这里打算采用服务器集群的方式来实现北斗播发平台,我们通过设计高性能的基于IOCP的单台北斗播发服务器,达到减少所需的服务器数量,降低整个系统的硬件成本目的。图2是北斗广域差分数据播发平台的数据流程图,这里我们专注研究北斗广域差分数据播发服务器的单台服务器性能。
图2 北斗广域差分数据播发平台的数据流程图
3.2 单台服务器的IOCP通信设计
3.2.1 IOCP的主要数据结构设计
(1)CIOCPBUFFER结构
该结构是对OVERLAPPED结构的扩展,其中nSequenceNu mber字段用于数据包重排序;nOperatioon字段用于确定完成I/O包的操作类型。
(2)CIOCPContext
为每一个新建立的用户分配一个CIOCPContext结构的内存块,addrLocal字段保存连接的本地地址;addrRemote字段保存连接的远程地址,该字段用于用户端信息的识别;nReadSequence、nCurrentReadSequence、pOutOfOrderReads三个字段和CIOBUFFER的nSequenceNumber字段共同作用完成数据包重排序。
3.2.2 IOCP的工作流程介绍
程序的工作流程如图3所示。
主线程首先程创建完成端口,然后创建侦听套接字并关联到完成端口,最后启动侦听线程。
侦听线程投递指定数量的接受 I/O,创建指定数量的工作线程,创建事件数组,处理程序运行中的各种事件,工作线程通过getQuenedCompletionStatus获取完成的 I/O,针对不同类型的操作,调用相应的函数处理。
图3 程序工作流程
3 基于IOCP的北斗播发平台测试
3.1 测试环境
6台Intel(R)Core(TM)i5-2520M CPU ·2.5GHz 2.50GHz 4G内存笔记本6台。一台作为服务器,另外5台作为测试用户终端。每个客户端可以循环发起2000个TCP连接模拟用户服务请求。服务器端开启8个工作线程处理IOCP的I/O完成包。
3.2 测试结果
服务器端可以在1秒内完成5个客户端模拟的8000用户连接请求,并可以进行简单的数据通信。
4 结束语
IOCP提供了一种利用有限的资源(工作线程)高效地处理输入/输出请求的方法。本文通过对IOCP参数的扩展、使用内存池等方法解决了基于IOCP北斗播发平台中的一些关键性问题。通过测试,验证了基于IOCP的北斗播发平台是可行的、高效的。