两个关键问题的解决方法
通常一台服务器要连接多台客户机,而每台客户机由于支持多用户方式就会同时运行多个c_process进程。服务器如何准确地将消息送给哪一台客户机? 另外一台客户机上运行的每一个c_process进程如何正确地获取发送给自己的消息? 这是两个关键的问题。 第一个问题在前面已经讲述过,主要是通过消息的sid标志来区别的。第二个问题是这样解决,在第①步时c_process进程先将自身的进程号pidc放在buf->cpid中,该值在以后的传输过程中保持不变,在第⑦步再将cpid赋值给消息类别mtype。这样在第⑧时c_process进程就从消息队列qid2中取走消息类别mtype等于其自身进程号pidc的消息,而不会错将送给同一客户机别的c_process进程的消息拿走。(图3) ┌──────────────┐ ┌────────────┐ │Server ┌───┤ ├───┐ ┌─────┐│ │ │tcp_s │ ┌────┤tcp_c ├┐│c_process2││ │ ┌─────┐ └─┬─┤ │ ├───┤│└─────┘│ │ │s_process │┌───┴┐│ │ ┌─→┤tcp_c1││┌─────┐│ │ │服务程序 ││共享内存││ │ │ L2├─┬─┘││c_process1││ │ └─┬─┬─┘└───┬┘│ │ │ │ ↓⑦ │└───┬┬┘│ │ ⑤↓ ↑④ ┌─┴─┤L1 │ │ │ │ └─┐ │↑⑧│ │┌──┘ │ ┌─┤tcp_s1├←──┘ │ │ │ ②↑ ││ │ ││┌──┬┼┐③│ │ ├←┐L1’ │ │ │┌──┬┼┐①││ │ │││qid3│ ├←┘ ├───┤ │ │ │ ││qid1│ ├←┘│ │ ││├──┼─┤ ┌┤tcp_s2├─┼───┘ │ │├──┼─┤ │ │ │││qid4│ ┼→─┘│ ├┐│┌────┐│ ││qid2│ ┼──┘ │ ││└──┴┬┘⑥ └───┤│└┤ ││ │└──┴┬┘ │ │└────┘ │└→┤Client2 ││ └────┘ Client1 │ └──────────────┘ L2’└────┘└──────────┘
图3 消息在服务器和客户机内传送的过程 消息队列与共享内存
在运行服务器通信软件之前应先创建共享内存和消息队列,创建共享内存的方法见文献[3]。本文共用到四个共享内存操作函数:shm_login(cport1,cport2,client_addr)在共享内存中申请一条记录将三个参数登记其中,并将flag标志设为’i’表示已经占用,同时根据记录的位置赋值给记录编号id。shm_logout(id)将共享内存中第id条记录删除,并将后面的记录前移,重新计算各条记录的编号。shm_info(id,type)根据type查询第id条记录的内容,比如type为GETS1时表示要查询s_socket1的值,当type等于GETLINKN时统计共享内存的记录总数。shm_update(id,s_socket1,s_socket2,linkf1,linkf2)修改第id条记录的内容,如果某个参数为零则不修改这个参数,如shm_update(n,s2,0,1,0)只修改s_socket1和linkf1的值,其余内容不作修改。在业务繁忙的情况下,有必要扩大消息队列的存储容量,下面的例子将消息队列qid3的容量扩大两倍。 来源:www.examda.com
struct msqid_ds sbuf1,*sbuf;int qid3; sbuf=&sbuf1; qid3=msgget(MSGKEY3,02000); msgctl(qid1,IPC_STAT,sbuf); sbuf->msg_qbytes*=2; msgctl(qid3,IPC_SET,sbuf);
其他问题的讨论
由于将服务器与客户机的连接登记在共享内存中,所以可以控制服务器与客户机的连接次数,在服务器接收到客户机的连接请求后可以先查询共享内存,如果与同一台客户机建立的连接次数已达到限定的数量时,服务器的守护进程就可以关闭掉已与客户机建立起来的初始连接,同时不再将客户机的端口号和IP地址登记在共享内存中,这样子进程也将不会再与客户机建立连接了。
另外这种重复型服务器通信软件使用一个只读的套接字和一个只写的套接字,由于一个套接字都有独立的读缓冲区和写缓冲区,长度都是24k。于是只读的套接字就不会用到写缓冲区,只写的套接字就不会用到读缓冲区,为了节省系统资源有必要将套接字设置成只有一个缓冲区,比如将只读套接字的写缓冲区长度设置为0。
int i,bufsize; i=sizeof(int); getsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,&i); fprintf(stderr,"size=%d\n",bufsize); bufsize=0; setsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,i); getsockopt(ls,SOL_SOCKET,SO_SNDBUF,&bufsize,&i); fprintf(stderr,"size=%d\n",bufsize);
在图2所示的仅是应用模式中的一种,本文提到的重复型服务器通信软件还可用于更复杂的情况。比如当客户机要与另一台客户机通信时就可用服务器作为中转站,从而不必在客户机之间建立连接。 比如通信子进程tcp_s1查询出目的客户机登记在共享内存第x条记录中,就将接收到的消息的sid置为x,这样子进程tcp_s2就可将消息送往第x台客户机,当然源客户机在发送的消息中应指明目的客户机的IP地址。这在客户机之间通信并不频繁的情况下很有用,因为这样就可减少所有的客户机都要相互建立连接的系统开销,有利于提高整个网络的运行效率。在某种特定的应用场合服务器在收到客户机的服务请求后,但因某种原因暂不能处理,于是就将消息存放起来,要等到条件成熟时服务器才能处理客户请求并将结果返回给客户机,此时客户机就不能认为这也是一个迟到的消息,应另行处理
|