客户机服务器接收和发送数据的方法
数据的传送过程 硬件划分:
├←─── 服务器 ───→┼← 网络 →┼←── 客户机 ──→┤ ┌──┐⑥┌──┐⑦┌──┐ ┌→┤qid4├→┤ L2 ├→┤qid2├─┐ ⑤│ └──┘ └──┘ └──┘ ↓⑧ ┌──┐ ┌──┴──┐ ┌──→ ┌──┴──┐ ┌────┐ │ DB ├←→┤s_process │ │ │c_process ├←→┤终端用户│ └──┘ └──┬──┘ └─── └──┬──┘ └────┘ ④↑ ┌──┐ ┌──┐ ┌──┐ │① └─┤qid3├←┤ L1 ├←┤qid1├←┘ 软件划分: └──┘③└──┘②└──┘ ├←─ s_process ──→┼←tcp_s→┼←tcp_c→┼← c_process →┤
图2 数据在客户机服务器之间传递的全过程 其中s_process和c_process是分别运行在服务器上的服务器业务程序和运行在客户机上的客户业务进程。qid3,qid4和qid1,qid2是分别存在于服务器及客户机上的消息队列。 tcp_s和tcp_c是分别运行在服务器和客户机上的通信软件。在客户机和服务器之间建立的两条连接是L1和L2,其中L1专用于客户机至服务器,L2专用于服务器至客户机。 下面叙述图2中所示的数据传递过程,同时介绍用于数据接收和发送的四个函数。因为业务程序不知何时可以接收或发送消息,所以这四个函数都存在一个循环不断地试图接收或发送数据。表示消息的数据结构是sg_buf,消息由消息类别mtype及正文段mdata组成。 正文段中存放的数据是无结构的,必须定义一种数据结构(struct),用结构中的各变量对mdata进行划分,从而使mdata中的数据可以被理解和使用。还可将mdata前面的一部分区域划出来重新命名用作其他用途。消息在整个数据传递的过程中起类似“载体”的作用。 来源:www.examda.com
#define MSGSIZE 200 struct msg_buf{ long mtype; /* 消息类别 */ long cpid; /* 客户业务进程标识号 */ long sid; /* 共享内存记录编号 */ long msgid; /* 消息编号 */ char mdata[MSGSIZE-16]; /* 数据区 */ }
① 客户业务程序c_process从终端用户接收数据,先存放在一个结构中,然后将该结构的内容依照一定的格式拷入buf->mdata中,然后将buf以消息的形式放入消息队列qid1中。
pidc=getpid();/* c_process的进程号 */ buf->mtype=1; /* 消息类别都为1 */ buf->sid=0; /* sid在客户机没用 */ buf->msgid=++msgid; buf->cpid=pidc; msgsnd(qid1,buf,MSGSIZE,0);
② 进程tcp_c调用函数Client_Send从qid1中取得消息,然后往L1写给服务器。从qid1中取消息时对消息并不予于区别,凡在qid1中的消息都要由进程tcp_c来发送。
for(;;){ /* 取mtype=1的消息 */ msgrcv(qid1,buf,MSGSIZE,1,0); write(s_w,buf,i+1); }
③ 进程tcp_s1调用函数Server_Receive从L1读数据至buf中,将buf作为消息放入qid3中。
for(n=1;n<=linkn;n++){ s1=shm_info(n,GETS1); i=read(s1,buf,MSGSIZE); if(i==-1) continue; if(i==0) ... /* 判断出客户机已退出 */ /* n是s1在共享内存登记项的编号 */ buf->sid=n; msgsnd(qid3,buf,MSGSIZE,0); }
④ 服务器业务程序s_process从消息队列qid3中接收消息到buf,然后将buf->mdata转成结构,根据结构的内容对数据库进行操作。s_process处在一个循环中,一有消息就取走去作消息所要求的操作,对消息并不加以区别。如果没有消息函数msgrcv就处于堵塞状态。 ⑤ s_process根据消息的内容访问数据库后将结果放在一个结构中,然后将该结构的内容拷到buf->mdata中,再将缓冲区buf以消息的形式放于消息队列qid4中,最后s_process又要继续循环再去接收新的消息。
for(;;){ msgrcv(qid3,buf,MSGSIZE,1,0); ... ... /* 解释buf->mdata的内容,对数据库进行操作后再将结果存放在buf->mdata中 */ buf->mtype=1; msgsnd(qid4,buf,MSGSIZE,0); }
⑥ 进程tcp_s2调用Server_Send从qid4中取走mtype=1的第一个消息,往L2写回客户机。
for(;;){ i=msgrcv(qid4,buf,MSGSIZE,1,0); if(i==-1) continue; s2=shm_info(buf->sid,GETS2); write(s2,buf,i+1); }
⑦ 进程tcp_c1调用函数Client_Receive从L2读数据到buf中,将buf作为消息放入qid2中。如果函数read返回0则表示服务器通信程序已经退出,于是就中断循环。这里必须将消息的类别mtype设置为客户业务进程的进程号cpid,便于客户业务程序识别。
for(;;){ i=read(s_r,buf,MSGSIZE); if(i==0){ close(s_r);return(1); } buf->mtype=buf->cpid; msgsnd(qid2,buf,i+1,0); }
⑧ 客户业务程序c_process从消息队列qid2中取走mtype=pidc(自身进程号)的第一个消息放入缓冲区buf中,再将buf->mdata中的数据划分为结构,对该结构作处理后将最终结果显示给用户。 在①中c_process将数据发出后要在什么时候到qid2中去拿结果呢? 方法是一就消息发送出去后客户业务程序马上就到qid2中去拿结果,若没有给自己的消息则堵塞住直到消息到来。这里程序设计成在堵塞20秒后发出时钟警报,调用函数overtime作出超时反应。当时钟警报时如果函数msgrcv正处于堵塞状态也会退出并返回-1。 这里就又存在一个问题,c_process在发送一个新消息后可能先接收到上一个因超时而未能被接收到的消息,解决这一问题最简单的方法就是发送消息之前给每个消息编号,如果接收到的消息的编号与发送的消息的编号不同则将消息从消息队列中删除,或者将消息取出后放在某一地方另行处理,然后继续等待接收正确编号的消息。删除消息的方法很简单,只要从消息队列中将消息取出就可以了。如果进程c_process被杀则迟到的消息由于其mtype表示的c_process已经不在运行,所以将会始终存在于消息队列中,直到客户机关机,因此在必要时也要对这些无主的消息作善后处理。
alarm(20); signal(SIGALRM,overtime); for(;;){ i=msgrcv(qid2,buf,MSGSIZE,pidc,0); if(i==-1) break; if(buf->msgid==msgid) break; } alarm(0); printf("%s\n",buf->mdata); overtime(int sig){ strcpy(buf->mdata,"overtime"); }
|