tcpip网络重复型服务器通信软件的设计Word文档格式.docx
《tcpip网络重复型服务器通信软件的设计Word文档格式.docx》由会员分享,可在线阅读,更多相关《tcpip网络重复型服务器通信软件的设计Word文档格式.docx(14页珍藏版)》请在冰豆网上搜索。
![tcpip网络重复型服务器通信软件的设计Word文档格式.docx](https://file1.bdocx.com/fileroot1/2022-11/27/fcc37cd9-7be1-4204-8dc5-bd8425b3b32c/fcc37cd9-7be1-4204-8dc5-bd8425b3b32c1.gif)
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;
structhostent*hp;
memset((char*)&
myaddr_in,0,sizeof(structsockaddr_in));
peeraddr_in,0,sizeof(structsockaddr_in));
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->
connect(s,(structsockaddr*)&
peeraddr_in,sizeof(peeraddr_in));
cport1--;
s_c1=rresvport(&
cport2=cport1;
s_c2=rresvport(&
cport2);
sprintf(cportstr,"
%dx%d"
cport1,cport2);
write(s,cportstr,strlen(cportstr)+1);
先给变量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;
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-'
/*在共享内存中登记客户机端口号和IP地址*/
shm_login(cport1,cport2,peeraddr_in.sin_addr.s_addr);
if(linkf==0){/*只生成两个子进程*/
if(fork()==0){/*子进程tcp_s2*/
Server_Send();
}else
if(fork()==0){/*子进程tcp_s1*/
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,&
close(s_c1);
/*只承诺接收一次连接要求*/
setsockopt(s_w,SOL_SOCKET,SO_LINGER,&
linger,sizeof(structlinger));
listen(s_c2,5);
s_r=accept(s_c2,&
close(s_c2);
setsockopt(s_r,SOL_SOCKET,SO_LINGER,&
⑸进程tcp_s1调用函数Server_Receive在一个循环中不断查询是否又有新的客户机登记在共享内存中,方法是判定共享内存中最后一条记录的linkf1标志是否为0,假如为0就调函数connect_to_client与客户机建立第一个连接,然后轮询所有的读套接字,有数据则读,没有数据则读下一个读套接字。
Server_Receive(){
ints1,len,i,linkn,linkf1,n;
structmsg_buf*buf,mbuf;
buf=&
mbuf;
linkn=shm_info(0,GETLINKN);
linkf1=shm_info(linkn,GETLINKF1);
if(linkf1==0){
if((i=connect_to_client(linkn,1))<
0){
shm_logout(linkn);
for(n=1;
n<
=linkn;
n++){
s1=shm_info(n,GETS1);
i=read(s1,buf,MSGSIZE);
if(i==0){
Aclientexit!
\n"
shutdown(s1,1);
close(s1);
shm_logout(n);
linkn--;
if(i==-1)continue;
buf->
mtype=MSGTYPE;
sid=n;
len=strlen(buf->
mdata);
mdata=%s\n"
buf->
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(){
ints2,linkn,linkf2,i;
linkf2=shm_info(linkn,GETLINKF2);
if(linkf2==0){
if((i=connect_to_client(linkn,2))<
i=msgrcv(qid4,buf,MSGSIZE,MSGTYPE,0x1ff|IPC_NOWAIT);
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││Client1│
│共享内存└─────┘│├──┬──┤
│ids1linkf1cport1s2linkf2cport2IP_Addressflag││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│││Clinet2│
│├─┼──┼──┼──┼──┼──┼──┼─────┼─┤│├──┬──┤
└→┤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)┌──┴┬─┐└┤Client3│
││└──────┤│13│├──┬──┤
│↓③:
(5991,168.1.1.71)│通信├─┤│5991│5990│
│┌──┴┬─┐│子进程│17│└┬─┴─┬┘
└┤│12││tcp_s2├─┤│L2↑⑦
│通信├─┤││23├───┼───┘
│子进程│14│└───┴─┘│
│tcp_s1├─┤L1(读套接字22)(写套接字23)│
││22├←─────────────────┘
└───┴─┘④
图1服务器和客户机建立连接的过程
那个地点必须置套接字的读取标志位O_NDELAY,如此在读数据时假如没有数据可读read函数就可不能堵塞住,这是重复型服务器能够实现的关键。
因为UNIX系统将套接字与一般文件等同处理,因此就能够使用设置文件标志的函数fcntl来处理套接字。
intconnect_to_client(n,type){
/*type=1,2*/
ints2,cport,sport,i;
if(type==2){
)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,(structsockaddr*)&
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├←→┤终端用户│
└──┘└──┬──┘└───└──┬──┘└─