Linux下CS模式数据查询的UDP实现.doc_第1页
Linux下CS模式数据查询的UDP实现.doc_第2页
Linux下CS模式数据查询的UDP实现.doc_第3页
Linux下CS模式数据查询的UDP实现.doc_第4页
Linux下CS模式数据查询的UDP实现.doc_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux下C/S模式数据查询的UDP实现摘要伴随着VOD视频点播技术的在KTV等娱乐场所的广泛应用,与之配套的管理系统也被推上了快速发展的轨道,通过这些系统人们可以对这些场所进行统一的管理,在方便顾客的同时也提高其自身的管理水平。目前视频点播系统主要以专用局域网络为主,在这样的网络结构中,要实现若干点播端与服务器之间大量数据的实时交互,C/S模式无疑为一种非常合适的实现模式,本文将详细阐述Linux下C/S模式数据查询的一种UDP实现方式。关键词 Linux C/S模式 数据查询 UDP 三层模型正文一、概述在早期的KTV系统中,我们在机顶盒端仅可以做一些比如点播歌曲、调节歌曲和麦克风音量、切歌、重唱等这些最基本的点歌操作,应该说在功能上比较单一。而随着KTV娱乐场所运营模式的改变和KTV相关技术的进步,在原来机顶盒点播系统的基础上,许多与之配套的管理系统也快速的发展了起来,正是这些配套管理系统的出现,才使得我们今天的机顶盒在功能上变得越来越强大,而酒水管理系统就是在这些配套管理系统中的一个典型代表。在KTV娱乐场所,通过机顶盒我们不仅可以直接对所在消费场所提供的酒水分类、酒水明细、消费账单等进行实时的查询,还可以进行酒水的点单、呼叫服务员等操作,而在场的场所管理人员则可以通过机顶盒直接进行退单、账单打折等操作,这在方便客户的同时也极大提高了场所的管理水平。而所有的这些操作都是通过机顶盒与后台的酒水管理系统的配合工作来实现的,两个系统之间采用了客户端与服务端的实现模式(即C/S模式),其中酒水管理系统为服务端,机顶盒为客户端,所有操作均由客户端发起,由服务端回应,这种工作方式就是我们在本文中要讲述的C/S模式数据查询。二、传输协议选择在KTV系统中应该说歌曲点播功能是其最主要的一项功能,当然音视频流的网络传输优先级也就最高,所以如果要进行音视频流之外的其他数据流的传输,则必须在保证不影响音视频网络传输的前提下进行,才能保证歌曲的点播功能不受影响。而目前在机顶盒端实现的酒水分类、酒水明细、消费账单等信息的实时查询操作由于传输数据量大、查询频繁,如果使用TCP协议进行传输的话,由于TCP协议本身是一种有连接的协议,相对于UDP协议而言系统开销大、传输速度慢,而TCP协议本身也存在有数据粘连等问题,从最大可能的减小查询数据流网络传输对音视频流网络传输的影响和降低软件的开发设计难度两方面考虑,这里我们将选择了系统开销小、传输速度快的UDP协议作为我们C/S模式数据查询的通信协议,并在使用中加入了适当的传输控制。三、通信模型结构图图1 模型结构图在本文中我们将采用如上图所示的模型结构图所描述的模型来实现C/S模式数据查询的UDP实现,该模型共分为三层,从内向外依次为UDP套接字层(CUdpSocket)、UDP客户端层(CUdpClient)、UDP代理层(CUdpProxy),下面将对这三层的功能做一下大致说明:1、UDP套接字层:该层在套接字的基础上实现了对数据发送接口的封装,并创建了独立接受线程负责对数据的实时接受。2、UDP客户端层:该层以UDP套接字层为基础,实现了对单次查询数据发送和接受过程的封装以及对收包数据的分析。3、UDP代理层:该层以UDP客户端层为基础,实现了对整个查询过程即若干单次查询的进一步封装。四、对应类实现结构图图2 对应类实现结构图在该类实现结构图中,分别对三层模型所对应的CUdpSocket、CUdpClient、CUdpProxy类的主要成员变量、接口以及各个类之间的调用关系等进行了说明,在后面的程序具体实现中上述模型结构图及该类实现结构图,对整个模型的工作原理和调用流程做详细说明。五、具体程序实现1、启动服务在模型启动之前首先要启动服务,也就是调用CUdpProxy类的StartService()接口,下面我们来看一下CudpProxy类StartService()接口的具体实现:int CUdpProxy:StartService()/初始化收发包对pthread_cond_t数组if (this-InitPackThreadCondArray() StartService (192.168.0.100, 5600, 8400, CUdpProxy:PackCondCallback)return -3;return 0;在对CCdpProxy的StartService()接口的中我们总共做了3件事情,依次是:1)初始化收发包pthread_cond_t数组:在最内层CUdpSocket的实现中,数据的发送和接受是在不同的线程中完成的,而UDP协议本身是一种无连接和传输控制的协议,为实现对收发包的实时控制,在这里我们采用收发包类型(这需要我们从协议上来区分各种不同的数据包,每种特定功能的收发包都必须有一个唯一的特征值,这里我们称该特征值为包类型)配对绑定同一个pthread_cond_t的方式来进行控制,具体的控制实现方式在后面会讲到,以下为初始化收发包pthread_cond_t数组的具体实现:int CUdpProxy:InitPackThreadCondArray()/为所有收发包对增加pthread_cond_t控制条件this-AddPackCond (0x00, 0x01);/.this-AddPackCond (0x0e, 0x0f);return this-m_PackThreadCondArray.size();/增加收发包对pthread_cond_t控制条件int CUdpProxy:AddPackCond(int nSendPackType,int nRecvPackType)PACK_THREAD_COND PackThreadCond;memset( &PackThreadCond, 0, sizeof(PACK_THREAD_COND) );PackThreadCond.nSendPackType = nSendPackType;PackThreadCond.nRecvPackType = nRecvPackType;pthread_cond_init( &PackThreadCond.ThreadCond, NULL );this-m_PackThreadCondArray.push_back( PackThreadCond );return this-m_PackThreadCondArray.size() - 1;在InitPackThreadCondArray()函数的语句this-AddPackCond (0x00, 0x01)中,0x00为发包类型, 0x01为收包类型,经过AddPackCond()操作后二者就实现了对同一pthread_cond_t的绑定。2)创建CUdpClient实例这里我们通过m_pUdpClient = new CUdpClient()语句创建了一个CUdpClient实例m_pUdpClient。3)调用CUdpClient类的StartService()接口启动客户端层服务:在调用CUdpClient类的StartService()接口时,我们共传入了4个参数,前3个参数分别为:服务器地址(本文为后台酒水管理系统服务器IP)、发送接口和接受端口,最后一个参数是一个PACKCONDCALLBACK类型的回调函数地址,该回调函数类型定义如下:typedef pthread_cond_t * (*PACKCONDCALLBACK)(PACK_DATA_INFO *pPackDataInfo);通过调用该回调函数的可以得到一个PACK_DATA_INFO格式数据包所对应的pthread_cond_t的地址,在下面的执行查询和对收到数据的处理中均会调用到该函数。下面我们来看一下CUdpClient类的StartService()接口的具体实现:int CUdpClient:StartService(char *szServerIP,int nSendPort,int nRecvPort,PACKCONDCALLBACK pPackCondCallback)if (NULL = szServerIP) | (NULL = pPackCondCallback)return -1;m_pUdpSocket = new CUdpSocket ();if (NULL = m_pUdpSocket)return -2;strcpy (this-m_szServerIP, szServerIP);this-m_pPackCondCallback = pPackCondCallback;m_pUdpSocket-SetSendPort( nSendPort );m_pUdpSocket-SetRecvPort( nRecvPort );return m_pUdpSocket- StartRecvService ( ClientRecvCallBack, (void *)this );在CUdpClient类的StartService()接口中我们做了以下几件事情:1)创建了CUdpSocket类实例m_pUdpSocket 2)保存Server IP地址及回调函数地址。3)设置m_pUdpSocket实例发送与接受端口4)调用CUdpSocket对象的StartRecvService()接口启动CUdpSocket层的数据接受服务。这里我们要重点对CdpSocket的StartRecvService()接口进行说明,首先我们看一下该接口的声明:int CUdpSocket:StartRecvService(RECVCALLBACK lpCallbackFunc,void *lpParameter);该接口的共有2个参数情况,第一个参数为lpCallbackFunc,是一个RECVCALLBACK类型的回调函数,具体类型定义为:typedef int (*RECVCALLBACK)(PACK_DATA_INFO *pPackDataInfo,void *lpParameter);第二参数为lpParameter,该参数为调用者传入参数,当CUdpSocket层接受线程收到数据后会调用此函数告知上层调用者对收到的数据(pPackDataInfo所指向)来进行处理,并同时将原先传入的lpParameter参数(CUdpClient的StartService()接口的第二个参数)也传给上层调用者。下面我们来看一下StartRecvThread()接口的具体实现:int CUdpSocket:StartRecvService(RECVCALLBACK lpCallbackFunc,void *lpParameter)struct sockaddr_in LocalAddr;/创建socketm_Socket = socket( AF_INET, SOCK_DGRAM, 0 ); if (m_Socket m_Socket m_Socket,szBuffer,MAX_DATA_LENGTH,0,(struct sockaddr *)&ServerAddress,&fromlen );if (nRecvLen m_lpCallbackFunc) (&PackDataInfo, pParent-m_lpParameter);return 0;在CUdpSocket的StartRecvService()接口的实现过程中主要做了以下三件事情:创建Socket并与本地指定端口进行绑定保存传入的收包解析回调函数地址lpCallbackFunc和用户自定义传入参数lpParameter。创建独立线程进行数据接受。在数据接受线程函数的实现中,我们可以看到,当对数据进行了成功接受和解析后,接口会通过调用在CudpSocket StartRecvThread()接口中传入的收包数据解析回调函数lpCallbackFunc,将收到的数据包交由上层调用者进行处理,处理完成后重新进入下一轮的数据接受工作。至此,CUdpProxy层StartService()接口的调用就算结束了。整个过程概括来讲,所做的主要工作就是将必要的参数和回调函数地址传入所需要的层进行保存,最后由CUdpSocket层创建独立数据接受线程来进行数据的接受工作。CUdpProxy层的服务启动以后,CudpProxy层的各查询接口就可以进行正常工作了。2、调用接口进行查询下面我们以CUdpProxy类的其中一个查询接口GetBillList()为例来对模型的查询过程进行说明,该接口完成的功能就是实现对账单列表的查询,下面我们先来看一下该函数的流程图,如下图所示:图3 GetBillList()查询接口流程图对于该账单列表查询接口GetBillList()需要说明的是:账单是以列表的方式进行传输和保存的,每条记录中又包括详细的消费记录,包括消费物品的名称、单位、数量、单价、折扣等等信息。由于在一次网络数据包的传输中,能够传输的记录条数是有限的,所以在记录量大的情况下就需要对记录进行多次分包传输,同时考虑到UDP协议本身是一种无连接协议,必须为其增加对数据的传输控制。-对于需要多次分包传输数据的情况,在这里我们没有采用“一次发多次收”的工作方式,而是采用了“多次收发配合”的工作方式,即一次发对应一次收,这样进行多个回合的收发往返,在适当增加网络开销的情况下实现了对数据包收发的有效传输控制。在每一次的数据成功查询和分析保存后,都要根据当前已接受到的记录数,确定下一个请求包所对以的记录起始位置,这样逐次累加,直到接收到所有的数据(记录总数会在第一个回包中传过来)完成整个的查询操作。在这个账单查询接口的实现中,最关键的部分就是对m_pUdpClient实例的ExecuteQuery()接口的调用,通过这个接口的调用我们就可以把我们想发的数据发出去,同时返回查询到的数据,中间的所有交互过程都不需要我们去关心。那这个ExecuteQuery()接口是如何来实现上述功能的呢?我们来看看CUDPClient类的这个ExecuteQuery()接口的具体实现:int CUdpClient:ExecuteQuery(const PACK_DATA_INFO *pSendPackData,PACK_DATA_INFO *pRecvPackData)pthread_cond_t *pThreadCond = NULL;if (NULL = pSendPackData) |(NULL = pRecvPackData) |(NULL = this-m_pPackCondCallback)return -1;pThreadCond = this-m_pPackCondCallback (PACK_DATA_INFO *)pSendPackData);if (NULL = pThreadCond)return -2;if (0 != m_pUdpSocket-SendData( (char *)(pSendPackData-szData), pSendPackData-nDataLength,this-m_szServerIP, pThreadCond )return -3;*pRecvPackData = this-m_RecvPackData;return 0;从ExecuteQuery()的具体实现中我们可以看到,该接口共有两个参数,其中pSendPackData为输入参数,传入的是要发送的数据;pSendPackData为输出参数,指向要存放接受数据的缓冲区地址,两者均为PACK_DATA_INFO类型,下面是PACK_DATA_INFO类型的定义:#define MAX_PACK_SIZE 4096 /最大包大小typedef structchar szDataMAX_PACK_SIZE;/包数据缓冲区int nDataLength;/包数据字节数PACK_DATA_INFO,*PPACK_DATA_INFO;在CUdpClient类的ExecuteQuery()接口中我们做了以下三件事情:1)通过回调函数m_pPackCondCallback获取到要发送的数据包对应的pthread_cond_t地址。2)调用m_pUdpSocket的SendData()接口将数据发送给服务器。3)发送成功后将m_RecvPackData中的数据拷贝到上层调用者的接受数据缓冲区中。我们不仅要问,为什么在调用了CUdpSocket类的的SendData()接口后就可以直接从m_RecvPackData中的数据拷贝给上层作为接受的数据呢?问题的关键就处在调用SendData()接口时传入的pThreadCond变量,那具体又是怎么实现的呢?让我们来看看CUdpSocket类的SendData()接口的实现: int CUdpSocket:SendData(char *szData,int nDataLen,char *szDesIP,pthread_cond_t * pThreadCond)struct timespec tm;struct sockaddr_in to;int nTimeoutMillSec = 2000; /2000毫秒超时if (m_Socket GetTimeOut (nTimeoutMillSec, &tm);if (0 = pthread_cond_timedwait (pThreadCond, &m_SendDataMutex, &tm)pthread_mutex_unlock( &m_SendDataMutex );return 0;pthread_mutex_unlock( &m_SendDataMutex );return -3;在SendData()接口的实现中我们可以看到,在数据成功发送到指定的服务器后并接口并没有马上返回,而是又调用了一个pthread_cond_timedwait()函数,而且只是在该函数返回0时候才返回成功,反之其他情况均返回失败。我们知道一旦函数pthread_cond_timedwait被调用,pThreadCond就会被置为无信号状态,该函数只会在两种情况下返回,一种情况是超时,此时返回非0值;还有一种情况就是pThreadCond被置为有信号状态,此时的返回值为,也就是说要使pthread_cond_timedwait()函数的返回值为,就必须要pThreadCond置为有信号状态,那这个工作是谁来完成的呢?在前面讲到CdpSocket类的数据接受线程函数RecvProc()时我们有提到,当数据被正确接受和解析后,会通过调用接受数据处理回调函数让调用层来对收到的数据进行处理,而将pThreadCond就是被这个回调函数置为有信号状态的。我们在CUdpClient类的StartService()接口的实现中曾为m_pUdpSocket实例的StartRecvThread()接口时传入了一个名为ClientRecvCallBack的回调函数,该函数即为接受线程在收到数据后调用的用以处理收到数据的回调函数,实现如下:int ClientRecvCallBack(PACK_DATA_INFO *pRecvPackData,void *lpParameter)pthread_cond_t *pThreadCond = NULL;CUdpClient *pParent = (CUdpClient *)lpParameter;if (NULL = pRecvPackData) |(NULL = pParent) |(NULL = pParent-m_pPackCondCallback)return -1;pThreadCond = pParent-m_pPackCondCallback (pRecvPackData);if (NULL = pThreadCond)return -2;pParent-m_RecvPackData = * pRecvPackData;pthread_cond_signal( pThreadCond );return 0;该接口被调用时,首先会获取与接受到数据包对应的pthread_cond_t地址,在成功获取到包对应的pthread_cond_t地址后,会先将收到的数据进行保存,然后通过调用pthread_cond_signal()函数将收到数据包对应的pthread_cond_t置为有信号状态。而我们知道,在调用CUdpProxy的StartService()接口启动服务时,我们就已经将所有的收发包类型进行了配对,也就是说所有的配对收发包都

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论