副标题#e#
本文先容一种新型的基于动静行列的反复型处事器通信软件的设计要领,差异于并发型处事器和一般的反复型处事器通信软件,这种新的软件具有生成的子历程数少的利益,而且容易对客户机与处事器的毗连举办打点,合用于客户机数量较多和随机数据通信的环境,可以或许有效地提高处事器的运行效率。
并发处事器与反复处事器的区别
一般TCP/IP处事器通信软件都是并发型的,等于由一个守护历程认真监听客户机的毗连请求,然后再由守护历程生成一个或多个子历程与客户机详细成立毗连以完成通信,其缺点是跟着毗连的客户机数量的增多,生成的通信子历程数量会越来越多,在客户机数量较多的应用场所势必影响处事器的运行效率。一般的反复处事器指的是处事器在吸收客户机的毗连请求后即与之成立毗连,然后要在处理惩罚完与客户机的通信任务后才气再去吸收另一客户机的请求毗连,其利益是不必生成通信子历程,缺点是客户机在每次通信之前都要与处事器成立毗连,开销过大,不能用于随机的数据通信和忙碌的业务处理惩罚。
本文提出的新型的反复型处事器差异于一般的反复处事器,它摒弃了上述两类处事器的缺点综合其利益,该处事器通信软件具有一般反复处事器的特征但又能处理惩罚客户机的随时机见,在客户机数量多且业务忙碌的应用场所将发挥其优势。反复型处事器通信软件只用三个历程就可完成与所有客户机成立毗连,并始终保持这些毗连。
反复型处事器通信软件与客户机成立毗连的要领
根基思路
当第一台客户机向处事器请求毗连时,处事器的守护历程与之成立初始毗连(L0),客户机操作L0向处事器发送两个端标语,守护历程将客户机的IP地点和端标语挂号在共享内存的记录中,然后封锁L0。由守护历程生成的两个通信子历程从共享内存中得到客户机IP地点及端标语后,别离向客户机请求毗连,成立一个从客户机读的毗连(L1)和一个往客户机写的毗连(L2),并将两个毗连的套接字的句柄记录在共享内存中。当另一台客户机请求毗连时,守护历程不再生成通信子历程,只是将客户机IP地点和端标语同样挂号在共享内存中。通信子历程在一个大轮回中先查询共享内存中是否有新的记录,假如有则与这一台客户机成立毗连,然后轮询所有已成立的毗连的读套接字,查察是否有数据可读,有则读取数据,同时标明该数据是从共享内存中的哪笔记录上的读套接字中得到的,再由另一个通信子历程按照这个记录的编号从共享内存中得到对应的写套接字,最后将功效数据往该套接字写往客户机。 2.2 成立毗连
⑴ 处事器通信软件的初始历程首先成立公用端口上的套接字,并在该套接字上成立监听行列,同时生成一个守护历程(Daemon)tcp_s,然后初始历程就退出运行。守护历程在函数accept处堵塞住直到有客户机的毗连请求,一有毗连请求即挪用server函数处理惩罚,然后继承轮回期待另一台客户机的请求。因为TCP/IP在毗连被拆除后为了制止呈现反复毗连的现象,一般是将毗连放在过期毗连表中,毗连在拆除后若要制止处于TIME_WAIT状态(过期毗连),可挪用setsockopt配置套接字的linger延时符号,同时将延时时间配置为0。处事器在/etc/services文件中要挂号一个全局公认的公用端标语:tcp_server 2000/tcp。
suct servent *sp;
suct sockaddr_in peeraddr_in,myaddr_in;
linkf=0;
sp=getservbyname("tcp_server","tcp");
ls=socket(AF_INET,SOCK_STREAM,0); /* 建设监听套接字 */
myaddr_in.sin_addr.s_addr=INADDR_ANY;
myaddr_in.sin_port=sp->s_port; /* 公用端标语 */
bind(ls,&myaddr_in,sizeof(suct sockaddr_in));
listen(ls,5);
qid3=msgget(MSGKEY3,0x1ff); /* 得到动静行列的符号号 */
qid4=msgget(MSGKEY4,0x1ff);
signal(SIGCLD,SIG_IGN); /* 制止子历程在退出后变为僵死历程 */
addrlen=sizeof(suct sockaddr_in);
lingerlen=sizeof(suct linger);
linger.l_onoff=1;
linger.l_linger=0;
setpgrp();
switch(fork()){ /* 生成Daemon */
case -1:exit(1);
case 0: /* Daemon */
for(;;){
s=accept(ls,&peeraddr_in,&addrlen);
setsockopt(s,SOL_SOCKET,SO_LINGER,&linger,lingerlen);
server();
close(s);
}
default:
fprintf(serr,"初始历程退出,由守护历程监听客户机的毗连请求.\n");
}
#p#副标题#e#
⑵ 客户机以这样的形式运行通信措施tcp_c:tcp_c rhostname,rhostname为客户机所要毗连的处事器主机名。客户机上的/etc/services文件中也要挂号:tcp_server 2000/tcp,公用端标语2000要与处事器一样。
#p#分页标题#e#
int qid1,qid2,s_c1,s_c2,cport1,cport2;
struct servent *sp;
struct hostent *hp;
memset((char *)&myaddr_in,0,sizeof(struct sockaddr_in));
memset((char *)&peeraddr_in,0,sizeof(struct sockaddr_in));
addrlen=sizeof(struct sockaddr_in);
sp=getservbyname("tcp_server","tcp");
hp=gethostbyname(argv[1]); /* 从/etc/hosts中获取处事器的IP地点 */
qid1=msgget(MSGKEY1,0x1ff);
qid2=msgget(MSGKEY2,0x1ff);
cport1=6000;
s=rresvport(&cport1);
peeraddr_in.sin_family=hp->h_addrtype;
bcopy(hp->h_addr_list[0],(caddr_t)&peeraddr_in.sin_addr,hp->h_length);
peeraddr_in.sin_port=sp->s_port;
connect(s,(struct sockaddr *)&peeraddr_in,sizeof(peeraddr_in));
cport1--;
s_c1=rresvport(&cport1);
cport2=cport1;
s_c2=rresvport(&cport2);
sprintf(cportstr,"%dx%d",cport1,cport2);
write(s,cportstr,strlen(cportstr)+1);
close(s);
先给变量cport1置一个整数后挪用rresvport函数,该函数先查抄端标语cport1是否已被占用,假如已被占用就减一再试,直到找到一个未用的端标语,然后生成一个套接字,将该套接字与端标语相联形成客户机端的半相关,接下挪用connect函数向处事器发出毗连请求。客户机在发出毗连请求之前,已用函数gethostbyname和getservbyname得到了处事器的IP地点及其公用端标语,这样就形成了一个完整的相关,可成立起与处事器的初始毗连。接下来再建设两个套接字s_c1和s_c2,操作初始毗连将客户机的两个套接字的端标语以字符串的形式发送给处事器,这时初始毗连的任务已经完成绩可将其封锁。以上就完成了与处事器的初始毗连,接下来客户机期待处事器的两次毗连请求。
⑶ tcp_s的监听行列在收到客户机发来的毗连请求后,由server函数读出客户机发送来的两个端标语,并在第一次挪用时生成两个通信子历程tcp_s1和tcp_s2,今后就不再生成,这是与并发处事器最大的差异。tcp_s历程将客户机的两个端标语和IP 地点以记录的形式挂号在共享内存最后一笔记录中,子历程通过共享内存得到这两个端标语,然后再别离与客户机成立毗连。tcp_s继承处于监听状态,以便响应其他客户机的毗连请求。两个子历程都应该封锁从父历程担任来的但又没有利用的套接字s。
server(){
int f;char c;
cport1=cport2=f=0;
for(;;){
read(s,&c,1);
if(c==0) break;
if(c=='x'){
f=1;continue;
}
if(f) cport2=(cport2*10)+(c-'0');
else cport1=(cport1*10)+(c-'0');
}
/* 在共享内存中挂号客户机端标语和IP地点 */
shm_login(cport1,cport2,peeraddr_in.sin_addr.s_addr);
if(linkf==0){ /* 只生成两个子历程 */
if(fork()==0){ /* 子历程tcp_s2 */
close(s);Server_Send();
}else
if(fork()==0){ /* 子历程tcp_s1 */
close(s);Server_Receive();
}
}
linkf=1;
}
共享内存的布局如下,通信子历程tcp_s1从s_socket1读,tcp_s2往对应的s_socket2写。
struct s_linkinfo{
int id; /* 毗连的符号号,从1开始顺序编号 */
int s_socket1; /* 处事器的读套接字 */
int linkf1; /* 与客户机的cport1毗连符号,0:未成立毗连,1:已经毗连 */
int cport1; /* 客户机的第一个端标语 */
int s_socket2; /* 处事器的写套接字 */
int linkf2; /* 与客户机的cport2毗连符号 */
int cport2; /* 客户机的第二个端标语 */
u_long client_addr; /* 客户机IP地点 */
char flag; /* 共享内存占用符号,'i':已占用,'o':未占用 */
};
⑷ tcp_c用listen(s_c1,5)在套接字s_c1上成立客户机的第一个监听行列,期待处事器的毗连请求。在与处事器成立第一个毗连后,再用listen(s_c2,5)成立第二个监听行列,与处事器成立第二个毗连。
listen(s_c1,5);
s_w=accept(s_c1,&peeraddr_in,&addrlen);
close(s_c1); /*只答允吸收一次毗连请求*/
linger.l_onoff=1;linger.l_linger=0;
setsockopt(s_w,SOL_SOCKET,SO_LINGER,&linger,sizeof(struct linger));
listen(s_c2,5);
s_r=accept(s_c2,&peeraddr_in,&addrlen);
close(s_c2);
setsockopt(s_r,SOL_SOCKET,SO_LINGER,&linger,sizeof(struct linger));
⑸ 历程tcp_s1挪用函数Server_Receive在一个轮回中不绝查询是否又有新的客户机挂号在共享内存中,要领是判定共享内存中最后一笔记录的linkf1符号是否为0,假如为0就调函数connect_to_client与客户机成立第一个毗连,然后轮询所有的读套接字,有数据则读,没有数据则读下一个读套接字。
#p#分页标题#e#
Server_Receive(){
int s1,len,i,linkn,linkf1,n;
struct msg_buf *buf,mbuf;
buf=&mbuf;
for(;;){
linkn=shm_info(0,GETLINKN);
linkf1=shm_info(linkn,GETLINKF1);
if(linkf1==0){
if((i=connect_to_client(linkn,1))<0){
shm_logout(linkn);continue;
}
}
for(n=1;n<=linkn;n++){
s1=shm_info(n,GETS1);
i=read(s1,buf,MSGSIZE);
if(i==0){
fprintf(stderr,"A client exit!\n");
shutdown(s1,1);close(s1);
shm_logout(n);
linkn--;continue;
}
if(i==-1) continue;
buf->mtype=MSGTYPE;buf->sid=n;
len=strlen(buf->mdata);
fprintf(stderr,"mdata=%s\n",buf->mdata);
i=msgsnd(qid3,buf,len+BUFCTLSIZE+1,0);
}
}
}
由于已将读套接字的读取符号设为O_NDELAY,所以没有数据可读时read函数就返回-1不会堵塞住。这样我们才气吸收到客户机随机的数据发送同时也才气实时响应新的客户机的毗连请求,这是反复处事器得以实现的要害地址。假如read函数返回0则暗示客户机通信措施已退出可能此外原因,好比客户构造机或网络通信妨碍等,此时就要从共享内存中排除相应客户机的记录。在成立毗连时假如呈现上述妨碍也要从共享内存中排除相应客户机的记录。在有数据可读时就将sid符号配置为n,暗示数据是从第n台客户机读取的,这样子历程tcp_s2才可按照动静的sid符号往第n台客户机写数据。
⑹ 历程tcp_s2挪用函数Server_Send,在一个轮回中不绝查询是否又有新的客户机毗连挂号在共享内存中,要领是判定共享内存中最后一笔记录的linkf2符号是否为0,假如为0就挪用函数connect_to_client与客户机成立第二个毗连,然后再从动静行列中读数据。因为只有一个tcp_s2历程在读动静行列,所以就不必对动静举办区别,有数据则读。再凭据动静的sid符号从共享内存中查出写套接字,然后将数据往该套接字写。由于该写套接字是在历程tcp_s2内建设的,所以只要简朴地利用套接字的句柄即可会见该套接字。函数msgrcv要配置IPC_NOWAIT符号以免在没有数据时堵塞住,这样才气继承执行下面的措施以便实时地与下一台客户机成立毗连,这也是一个要害的处所。tcp_s2挪用函数Server_Send用于数据发送,tcp_s1则挪用函数Server_Recvice用于数据吸收。
Server_Send(){
int s2,linkn,linkf2,i;
struct msg_buf *buf,mbuf;
buf=&mbuf;
for(;;){
linkn=shm_info(0,GETLINKN);
linkf2=shm_info(linkn,GETLINKF2);
if(linkf2==0){
if((i=connect_to_client(linkn,2))<0){
shm_logout(linkn);continue;
}
}
i=msgrcv(qid4,buf,MSGSIZE,MSGTYPE,0x1ff|IPC_NOWAIT);
if(i==-1) continue;
s2=shm_info(buf->sid,GETS2);
if(write(s2,buf,i+1)!=i+1){
perror("write");close(s2);
}
}
}
函数connect_to_client(n,type)暗示处事器与第n台客户机成立第type次毗连。该函数由两个子历程同时挪用,别离从共享内存中查出客户机的IP地点和端标语后与客户机成立毗连,成立的毗连别离处于各个子历程本身的数据空间中,互相并不相通,所以又要用到共享内存,将毗连的套接字句柄挂号在共享内存中,使得与同一台客户机成立毗连的两个套接字形成一一对应的干系。
这样tcp_s2才可按照数据读入的套接字去查询出对应的写套接字,才气正确地将处理惩罚功效发送给对应的客户机。tcp_s1以type=1挪用该函数,利用共享内存中第n笔记录的cport1和客户机IP地点与客户机成立第一个毗连,同时将这一毗连处事器方的套接字(读套接字)挂号在共享内存第n笔记录的s_socket1中,同时将毗连符号linkf1置1。
tcp_s2以type=2挪用该函数,利用共享内存中第n笔记录的cport2和客户机IP地点与客户机成立第二条毗连,同样也要将这一毗连处事器方的套接字(写套接字)挂号在共享内存第n笔记录的s_socket2中,将毗连符号linkf2置1。因为该函数由两个子历程同时挪用,为了保持历程间同步,当type=2时必须比及第n笔记录的linkf1为1时才气继承执行,即必需先成立第一个毗连才气再成立第二个毗连,这是由客户机通信措施抉择的,因为客户机通信措施是先监听并成立起第一个毗连后再监听并成立第二个毗连。子历程tcp_s1和tcp_s2通过共享内存实现历程间通信,在实际应用中老是利用共享内存的最后一笔记录。
②:(5991,5990,168.1.1.71) ┌─────┐①:(5991,5990) 168.1.1.21
┌─────────────┤ 守护历程 ├←─────────┐┌─────┐
│ │ tcp_s │ 初始毗连L0 ││ Client 1 │
│ 共享内存 └─────┘ │├──┬──┤
│ id s1 linkf1 cport1 s2 linkf2 cport2 IP_Address flag ││5999│5998│
│ ┌─┬──┬──┬──┬──┬──┬──┬─────┬─┐│└──┴──┘
│ │1 │ 12 │ 1 │5999│ 13 │ 1 │5998│168.1.1.21│i ││ 168.1.1.22
│ ├─┼──┼──┼──┼──┼──┼──┼─────┼─┤│┌─────┐
│ │2 │ 14 │ 1 │5995│ 17 │ 1 │5994│168.1.1.22│i │││ Clinet 2 │
│ ├─┼──┼──┼──┼──┼──┼──┼─────┼─┤│├──┬──┤
└→┤3 │0/22│0/1 │5991│0/23│0/1 │5990│168.1.1.71│i │││5995│5994│
└─┴──┼──┴┬─┴──┼──┴┬─┴─────┴─┘│└──┴──┘
⑤:(22,1)↑ │ ↑ ↓⑥:(5990,168.1.1.71)│ 168.1.1.71
│ │ │ └─────┐ │┌─────┐
│ │ │⑧:(23,1) ┌──┴┬─┐ └┤ Client 3 │
│ │ └──────┤ │13│ ├──┬──┤
│ ↓③:(5991,168.1.1.71) │通信 ├─┤ │5991│5990│
│┌──┴┬─┐ │子历程│17│ └┬─┴─┬┘
└┤ │12│ │tcp_s2├─┤ │ L2↑⑦
│通信 ├─┤ │ │23├───┼───┘
│子历程│14│ └───┴─┘ │
│tcp_s1├─┤L1 (读套接字22) (写套接字23) │
│ │22├←─────────────────┘
└───┴─┘④
图1 处事器和客户机成立毗连的进程
#p#分页标题#e#
这里必需置套接字的读取符号位O_NDELAY,这样在读数据时假如没有数据可读read函数就不会堵塞住,这是反复型处事器可以或许实现的要害。因为UNIX系统将套接字与普通文件等同处理惩罚,所以就可以或许利用配置文件符号的函数fcntl来处理惩罚套接字。
int connect_to_client(n,type){
u_long client_addr; /* type=1,2 */
int s2,cport,sport,i;
if(type==2){
for(;;) if(shm_info(n,GETLINKF1)==1) break;
}
sport=6000-1;s2=rresvport(&sport);
cport=shm_info(n,GETCPORT1+type-1);
client_addr=shm_info(n,GETCADDR);
peeraddr_in.sin_port=htons((short)cport);
peeraddr_in.sin_addr.s_addr=client_addr;
connect(s2,(struct sockaddr *)&peeraddr_in,sizeof(peeraddr_in));
flags=fcntl(s2,F_GETFL,0);
fcntl(s2,F_SETFL,flags|O_NDELAY);
if(type==1) i=shm_update(n,s2,0,1,0);
if(type==2) i=shm_update(n,0,s2,0,1);
return(i);
}
⑺ tcp_c在吸收随处事器的两个毗连后,生成子历程tcp_c1挪用函数Client_Receive用于吸收数据,tcp_c则挪用函数Client_Send用于发送数据。假如函数Client_Receive从轮回中退出,就说明处事器通信软件已退出,于是子历程在退出之前要先杀掉父历程。
cpid=getpid(); /* 父历程的历程号 */
if(fork()==0){ /* tcp_c1 */
close(s_w);
Client_Receive();
sprintf(cmdline,"kill -9 %d",cpid);
system(cmdline);
}else{
close(s_r);
Client_Send();
}
客户机处事器吸收和发送数据的要领
数据的传送进程
硬件分别:
├←─── 处事器 ───→┼← 网络 →┼←── 客户机 ──→┤
┌──┐⑥┌──┐⑦┌──┐
┌→┤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构成。
#p#分页标题#e#
正文段中存放的数据是无布局的,必需界说一种数据布局(struct),用布局中的各变量对mdata举办分别,从而使mdata中的数据可以被领略和利用。还可将mdata前面的一部门区域划出来从头定名用作其他用途。动静在整个数据通报的进程中起雷同“载体”的浸染。
#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已经不在运行,所以将会始终存在于动静行列中,直到客户构造机,因此在须要时也要对这些无主的动静作善后处理惩罚。
#p#分页标题#e#
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");
}
两个要害问题的办理要领
凡是一台处事器要毗连多台客户机,而每台客户机由于支持多用户方法就会同时运行多个c_process历程。处事器如何精确地将动静送给哪一台客户机? 别的一台客户机上运行的每一个c_process历程如何正确地获取发送给本身的动静? 这是两个要害的问题。 第一个问题在前面已经报告过,主要是通过动静的sid符号来区此外。第二个问题是这样办理,在第①步时c_process历程先将自身的历程号pidc放在buf->cpid中,该值在今后的传输进程中保持稳定,在第⑦步再将cpid赋值给动静种别mtype。这样在第⑧时c_process历程就从动静行列qid2中取走动静种别mtype便是其自身历程号pidc的动静,而不会错将送给同一客户机此外c_process历程的动静拿走。(图3)
┌──────────────┐ ┌────────────┐
│Server ┌───┤ ├───┐ ┌─────┐│
│ │tcp_s │ ┌────┤tcp_c ├┐│c_process2││
│ ┌─────┐ └─┬─┤ │ ├───┤│└─────┘│
│ │s_process │┌───┴┐│ │ ┌─→┤tcp_c1││┌─────┐│
│ │处事措施 ││共享内存││ │ │ L2├─┬─┘││c_process1││
│ └─┬─┬─┘└───┬┘│ │ │ │ ↓⑦ │└───┬┬┘│
│ ⑤↓ ↑④ ┌─┴─┤L1 │ │ │ │ └─┐ │↑⑧│
│┌──┘ │ ┌─┤tcp_s1├←──┘ │ │ │ ②↑ ││ │
││┌──┬┼┐③│ │ ├←┐L1' │ │ │┌──┬┼┐①││ │
│││qid3│ ├←┘ ├───┤ │ │ │ ││qid1│ ├←┘│ │
││├──┼─┤ ┌┤tcp_s2├─┼───┘ │ │├──┼─┤ │ │
│││qid4│ ┼→─┘│ ├┐│┌────┐│ ││qid2│ ┼──┘ │
││└──┴┬┘⑥ └───┤│└┤ ││ │└──┴┬┘ │
│└────┘ │└→┤Client2 ││ └────┘ Client1 │
└──────────────┘ L2'└────┘└────────────┘
图3 动静在处事器和客户机内传送的进程
动静行列与共享内存
在运行处事器通信软件之前应先建设共享内存和动静行列,建设共享内存的要领见文献[3]。本文共用到四个共享内存操纵函数:shm_login(cport1,cport2,client_addr)在共享内存中申请一笔记录将三个参数挂号个中,并将flag符号设为’i’暗示已经占用,同时按照记录的位置赋值给记录编号id。shm_logout(id)将共享内存中第id笔记录删除,并将后头的记录前移,从头计较各笔记录的编号。shm_info(id,type)按照type查询第id笔记录的内容,好比type为GETS1时暗示要查询s_socket1的值,当type便是GETLINKN时统计共享内存的记录总数。shm_update(id,s_socket1,s_socket2,linkf1,linkf2)修改第id笔记录的内容,假如某个参数为零则不修改这个参数,如shm_update(n,s2,0,1,0)只修改s_socket1和linkf1的值,其余内容不作修改。在业务忙碌的环境下,有须要扩大动静行列的存储容量,下面的例子将动静行列qid3的容量扩大两倍。
struct msqid_ds sbuf1,*sbuf;int qid3;
sbuf=&sbuf1;
qid3=msgget(MSGKEY3,02000);
msgctl(qid1,IPC_STAT,sbuf);
sbuf->msg_qbytes*=2;
msgctl(qid3,IPC_SET,sbuf);
其他问题的接头
由于将处事器与客户机的毗连挂号在共享内存中,所以可以节制处事器与客户机的毗连次数,在处事器吸收到客户机的毗连请求后可以先查询共享内存,假如与同一台客户机成立的毗连次数已到达限定的数量时,处事器的守护历程就可以封锁掉已与客户机成立起来的初始毗连,同时不再将客户机的端标语和IP地点挂号在共享内存中,这样子历程也将不会再与客户机成立毗连了。
别的这种反复型处事器通信软件利用一个只读的套接字和一个只写的套接字,由于一个套接字都有独立的读缓冲区和写缓冲区,长度都是24k。于是只读的套接字就不会用到写缓冲区,只写的套接字就不会用到读缓冲区,为了节减系统资源有须要将套接字配置成只有一个缓冲区,好比将只读套接字的写缓冲区长度配置为0。
#p#分页标题#e#
int i,bufsize;
i=sizeof(int);
getsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,&i);
fprintf(stderr,"size=%d\n",bufsize);
bufsize=0;
setsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,i);
getsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,&i);
fprintf(stderr,"size=%d\n",bufsize);
在图2所示的仅是应用模式中的一种,本文提到的反复型处事器通信软件还可用于更巨大的环境。好比当客户秘密与另一台客户机通信时就可用处事器作为中转站,从而不必在客户机之间成立毗连。 好比通信子历程tcp_s1查询出目标客户机挂号在共享内存第x笔记录中,就将吸收到的动静的sid置为x,这样子历程tcp_s2就可将动静送往第x台客户机,虽然源客户机在发送的动静中应指明目标客户机的IP地点。这在客户机之间通信并不频繁的环境下很有用,因为这样就可淘汰所有的客户机都要彼此成立毗连的系统开销,有利于提高整个网络的运行效率。在某种特定的应用场所处事器在收到客户机的处事请求后,但因某种原因暂不能处理惩罚,于是就将动静存放起来,要比及条件成熟时处事器才气处理惩罚客户请求并将功效返回给客户机,此时客户机就不能认为这也是一个迟到的动静,应另行处理惩罚。