1、myaddr_in.sin_port=sp-s_port; /* 公用端口号 */ bind(ls,&myaddr_in,sizeof(struct sockaddr_in);listen(ls,5);qid3=msgget(MSGKEY3,0x1ff); /* 获得消息队列的标志号 */ qid4=msgget(MSGKEY4,0x1ff);signal(SIGCLD,SIG_IGN); /* 幸免子进程在退出后变为僵死进程 */ addrlen=sizeof(struct sockaddr_in);lingerlen=sizeof(struct linger);linger.l_onoff
2、=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(stderr,初始进程退出,由守护进程监听客户机的连接要求.n 客户机以如此的形式运行通信程序tcp_c:tcp_c rhostname,rhos
3、tname为客户机所要连接的服务器主机名。客户机上的/etc/services文件中也要登记:tcp_server 2000/tcp,公用端口号2000要与服务器一样。int qid1,qid2,s_c1,s_c2,cport1,cport2;struct hostent *hp;memset(char *)&myaddr_in,0,sizeof(struct sockaddr_in);peeraddr_in,0,sizeof(struct sockaddr_in);hp=gethostbyname(argv1); /* 从/etc/hosts中猎取服务器的IP地址 */ qid1=msgge
4、t(MSGKEY1,0x1ff);qid2=msgget(MSGKEY2,0x1ff);cport1=6000;s=rresvport(&cport1);peeraddr_in.sin_family=hp-h_addrtype;bcopy(hp-h_addr_list0,(caddr_t)&peeraddr_in.sin_addr,hp-h_length);peeraddr_in.sin_port=sp-connect(s,(struct sockaddr *)&peeraddr_in,sizeof(peeraddr_in);cport1-;s_c1=rresvport(&cport2=cpo
5、rt1;s_c2=rresvport(&cport2);sprintf(cportstr,%dx%d,cport1,cport2);write(s,cportstr,strlen(cportstr)+1);先给变量cport1置一个整数后调用rresvport函数,该函数先检查端口号cport1是否已被占用,假如已被占用就减一再试,直到找到一个未用的端口号,然后生成一个套接字,将该套接字与端口号相联形成客户机端的半相关,接下调用connect函数向服务器发出连接要求。客户机在发出连接要求之前,已用函数gethostbyname和getservbyname获得了服务器的IP地址及其公用端口号,如
6、此就形成了一个完整的相关,可建立起与服务器的初始连接。接下来再创建两个套接字s_c1和s_c2,利用初始连接将客户机的两个套接字的端口号以字符串的形式发送给服务器,这时初始连接的任务差不多完成就可将其关闭。以上就完成了与服务器的初始连接,接下来客户机等待服务器的两次连接要求。 tcp_s的监听队列在收到客户机发来的连接要求后,由server函数读出客户机发送来的两个端口号,并在第一次调用时生成两个通信子进程tcp_s1和tcp_s2,以后就不再生成,这是与并发服务器最大的不同。tcp_s进程将客户机的两个端口号和IP 地址以记录的形式登记在共享内存最后一条记录中,子进程通过共享内存获得这两个端
7、口号,然后再分别与客户机建立连接。tcp_s连续处于监听状态,以便响应其他客户机的连接要求。两个子进程都应该关闭从父进程继承来的但又没有使用的套接字s。server() int f;char c;cport1=cport2=f=0;read(s,&c,1);if(c=0) break;if(c=xf=1;continue;if(f) cport2=(cport2*10)+(c-0else cport1=(cport1*10)+(c-/* 在共享内存中登记客户机端口号和IP地址 */ shm_login(cport1,cport2,peeraddr_in.sin_addr.s_addr);if(
8、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写。struct s_linkinfo int id; /* 连接的标志号,从1开始顺序编号 */ int s_socket1; /* 服务器的读套接字 */ int linkf1; /* 与客户机的cport1连接标志,0:未建立连接,1:差
9、不多连接 */ 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)建立第二个监听队列,与服务
10、器建立第二个连接。listen(s_c1,5);s_w=accept(s_c1,&close(s_c1); /*只承诺接收一次连接要求*/ setsockopt(s_w,SOL_SOCKET,SO_LINGER,&linger,sizeof(struct linger);listen(s_c2,5);s_r=accept(s_c2,&close(s_c2);setsockopt(s_r,SOL_SOCKET,SO_LINGER,& 进程tcp_s1调用函数Server_Receive在一个循环中不断查询是否又有新的客户机登记在共享内存中,方法是判定共享内存中最后一条记录的linkf1标志是否为
11、0,假如为0就调函数connect_to_client与客户机建立第一个连接,然后轮询所有的读套接字,有数据则读,没有数据则读下一个读套接字。Server_Receive() int s1,len,i,linkn,linkf1,n;struct msg_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;nmtype=MSGTYPE;s
12、id=n;len=strlen(buf-mdata);mdata=%sn,buf-i=msgsnd(qid3,buf,len+BUFCTLSIZE+1,0);由于已将读套接字的读取标志设为O_NDELAY,因此没有数据可读时read函数就返回-1可不能堵塞住。如此我们才能接收到客户机随机的数据发送同时也才能及时响应新的客户机的连接要求,这是重复服务器得以实现的关键所在。假如read函数返回0则表示客户机通信程序已退出或者别的缘故,比如客户机关机或网络通信故障等,现在就要从共享内存中清除相应客户机的记录。在建立连接时假如显现上述故障也要从共享内存中清除相应客户机的记录。在有数据可读时就将sid标
13、志设置为n,表示数据是从第n台客户机读取的,如此子进程tcp_s2才可依照消息的sid标志往第n台客户机写数据。 进程tcp_s2调用函数Server_Send,在一个循环中不断查询是否又有新的客户机连接登记在共享内存中,方法是判定共享内存中最后一条记录的linkf2标志是否为0,假如为0就调用函数connect_to_client与客户机建立第二个连接,然后再从消息队列中读数据。因为只有一个tcp_s2进程在读消息队列,因此就不必对消息进行区别,有数据则读。再按照消息的sid标志从共享内存中查出写套接字,然后将数据往该套接字写。由于该写套接字是在进程tcp_s2内创建的,因此只要简单地使用套
14、接字的句柄即可访问该套接字。函数msgrcv要设置IPC_NOWAIT标志以免在没有数据时堵塞住,如此才能连续执行下面的程序以便及时地与下一台客户机建立连接,这也是一个关键的地点。tcp_s2调用函数Server_Send用于数据发送,tcp_s1则调用函数Server_Recvice用于数据接收。Server_Send() int s2,linkn,linkf2,i;linkf2=shm_info(linkn,GETLINKF2);if(linkf2=0) if(i=connect_to_client(linkn,2)sid,GETS2);if(write(s2,buf,i+1)!=i+1)
15、 perror(writeclose(s2);函数connect_to_client(n,type)表示服务器与第n台客户机建立第type次连接。该函数由两个子进程同时调用,分别从共享内存中查出客户机的IP地址和端口号后与客户机建立连接,建立的连接分别处于各个子进程自己的数据空间中,彼此并不相通,因此又要用到共享内存,将连接的套接字句柄登记在共享内存中,使得与同一台客户机建立连接的两个套接字形成一一对应的关系。如此tcp_s2才可依照数据读入的套接字去查询出对应的写套接字,才能正确地将处理结果发送给对应的客户机。tcp_s1以type=1调用该函数,使用共享内存中第n条记录的cport1和客户
16、机IP地址与客户机建立第一个连接,同时将这一连接服务器方的套接字(读套接字)登记在共享内存第n条记录的s_socket1中,同时将连接标志linkf1置1。tcp_s2以type=2调用该函数,使用共享内存中第n条记录的cport2和客户机IP地址与客户机建立第二条连接,同样也要将这一连接服务器方的套接字(写套接字)登记在共享内存第n条记录的s_socket2中,将连接标志linkf2置1。因为该函数由两个子进程同时调用,为了保持进程间同步,当type=2时必需等到第n条记录的linkf1为1时才能连续执行,即必须先建立第一个连接才能再建立第二个连接,这是由客户机通信程序决定的,因为客户机通信
17、程序是先监听并建立起第一个连接后再监听并建立第二个连接。子进程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 59995998 1 12 1 5999 13 1 5998168.1.1.21i 168.1.1.22 2 14 1 5995 17 1 5994168.1.1.22i C
18、linet 2 3 0/220/1 59910/230/1 5990168.1.1.71i59955994 :(22,1) :(5990,168.1.1.71) 168.1.1.71 :(23,1) Client 3 13 :(5991,168.1.1.71) 通信 59915990 子进程17 12 tcp_s2 L2 通信 23 子进程14 tcp_s1L1 (读套接字22) (写套接字23) 22 图1 服务器和客户机建立连接的过程 那个地点必须置套接字的读取标志位O_NDELAY,如此在读数据时假如没有数据可读read函数就可不能堵塞住,这是重复型服务器能够实现的关键。因为UNIX系统
19、将套接字与一般文件等同处理,因此就能够使用设置文件标志的函数fcntl来处理套接字。int connect_to_client(n,type) /* type=1,2 */ int s2,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);pee
20、raddr_in.sin_addr.s_addr=client_addr;connect(s2,(struct sockaddr *)&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 终端用户
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1