⑶ 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与客户机建立第一个连接,然后轮询所有的读套接字,有数据则读,没有数据则读下一个读套接字。
| 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台客户机写数据。
