您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 销售管理 > tcpip网络重复型服务器通信软件的设计
TCP/IP网络重复型服务器通信软件的设计本文介绍一种新型的基于消息队列的重复型服务器通信软件的设计方法,不同于并发型服务器和一般的重复型服务器通信软件,这种新的软件具有生成的子进程数少的优点,并且容易对客户机与服务器的连接进行管理,适用于客户机数量较多和随机数据通信的情况,能够有效地提高服务器的运行效率。并发服务器与重复服务器的区别一般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_server2000/tcp。structservent*sp;structsockaddr_inpeeraddr_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(structsockaddr_in));listen(ls,5);qid3=msgget(MSGKEY3,0x1ff);/*获得消息队列的标志号*/qid4=msgget(MSGKEY4,0x1ff);signal(SIGCLD,SIG_IGN);/*避免子进程在退出后变为僵死进程*/addrlen=sizeof(structsockaddr_in);lingerlen=sizeof(structlinger);linger.l_onoff=1;linger.l_linger=0;setpgrp();switch(fork()){/*生成Daemon*/case-1:exit(1);case0:/*Daemon*/for(;;){s=accept(ls,&peeraddr_in,&addrlen);setsockopt(s,SOL_SOCKET,SO_LINGER,&linger,lingerlen);server();close(s);}default:fprintf(stderr,初始进程退出,由守护进程监听客户机的连接请求.\n);}⑵客户机以这样的形式运行通信程序tcp_c:tcp_crhostname,rhostname为客户机所要连接的服务器主机名。客户机上的/etc/services文件中也要登记:tcp_server2000/tcp,公用端口号2000要与服务器一样。intqid1,qid2,s_c1,s_c2,cport1,cport2;structservent*sp;structhostent*hp;memset((char*)&myaddr_in,0,sizeof(structsockaddr_in));memset((char*)&peeraddr_in,0,sizeof(structsockaddr_in));addrlen=sizeof(structsockaddr_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,(structsockaddr*)&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(){intf;charc;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');elsecport1=(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();}elseif(fork()==0){/*子进程tcp_s1*/close(s);Server_Receive();}}linkf=1;}共享内存的结构如下,通信子进程tcp_s1从s_socket1读,tcp_s2往对应的s_socket2写。structs_linkinfo{intid;/*连接的标志号,从1开始顺序编号*/ints_socket1;/*服务器的读套接字*/intlinkf1;/*与客户机的cport1连接标志,0:未建立连接,1:已经连接*/intcport1;/*客户机的第一个端口号*/ints_socket2;/*服务器的写套接字*/intlinkf2;/*与客户机的cport2连接标志*/intcport2;/*客户机的第二个端口号*/u_longclient_addr;/*客户机IP地址*/charflag;/*共享内存占用标志,'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(structlinger));listen(s_c2,5);s_r=accept(s_c2,&peeraddr_in,&addrlen);close(s_c2);setsockopt(s_r,SOL_SOCKET,SO_LINGER,&linger,sizeof(structlinger));⑸进程tcp_s1调用函数Server_Receive在一个循环中不断查询是否又有新的客户机登记在共享内存中,方法是判断共享内存中最后一条记录的linkf1标志是否为0,如果为0就调函数connect_to_client与客户机建立第一个连接,然后轮询所有的读套接字,有数据则读,没有数据则读下一个读套接字。Server_Receive(){ints1,len,i,linkn,linkf1,n;structmsg_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)
本文标题:tcpip网络重复型服务器通信软件的设计
链接地址:https://www.777doc.com/doc-1580460 .html