函数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 服务器和客户机建立连接的过程 这里必须置套接字的读取标志位O_NDELAY,这样在读数据时如果没有数据可读read函数就不会堵塞住,这是重复型服务器能够实现的关键。因为UNIX系统将套接字与普通文件等同处理,所以就能够使用设置文件标志的函数fcntl来处理套接字。 来源:www.examda.com
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(); }
|