对于多客户的处理,可以利用套接字类似文件描述符的行为来解决。如果服务器调用fork为自己创建一份副本,打开的套接字就将被新的子进程所继承。新的子进程可以和连接的客户进行通信,而主服务器程序可以继续接受以后的客户连接。
服务器程序用fork函数处理多个客户,但在数据库应用程序中,这并不是好的解决方法,因为服务器程序会相当大,而且数据库访问方面还存在着需要协调多个服务器副本的问题。因此需要在单个服务器进程在不阻塞、不等待客户请求到达的前提下处理多个客户。采用select调用。
select系统调用允许程序同时在多个底层文件描述符上等待输入的到达或输出的完成。终端仿真程序可以一直阻塞到有事情可做为止,类似下,服务器也可以通过同时在多个打开的套接字上等待请求的到来。
select用于测试文件描述符集合中是否有一个文件描述符已处于可读写或者错误状态,他将阻塞以等待某个文件描述符进入这些状态。主要对数据结构fd_set进行操作。
原型:int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout)
返回值为状态发生变化的描述符总数,失败返回-1.
服务器程序可以利用select调用同时处理多个客户,无需依赖于子进程。服务器让select调用同时检查监听套接字和客户的连接套接字,一旦有活动发生,用FD_ISSET来遍历所有有可能的文件描述符。
一个用于多客户连接的服务器程序例程
/* 头文件及变量定义 */
点击(此处)折叠或打开
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <stdio.h>
- #include <netinet/in.h>
- #include <sys/time.h>
- #include <sys/ioctl.h>
- #include <unistd.h>
- #include <stdlib.h>
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_in server_address;
- struct sockaddr_in client_address;
- int result;
- fd_set readfds, testfds;//文件描述符集,检测可读状态下的客户套接字
点击(此处)折叠或打开
- server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = htonl(INADDR_ANY);
- server_address.sin_port = htons(9734);
- server_len = sizeof(server_address);
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
点击(此处)折叠或打开
- listen(server_sockfd, 5);
- FD_ZERO(&readfds);//将该描述符集初始化为空集
- FD_SET(server_sockfd, &readfds);//设置server_sockfd为可读描述符集中的成员
点击(此处)折叠或打开
- while(1)
- {
- char ch;
- int fd;
- int nread;
- testfds = readfds;
- printf("server waiting\n");
- result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *) 0);
- //timeout设置为空指针,不会发生超时 检测可读状态,不检测可写及错误状态的发生
- if(result < 1)
- {
- perror("server5");
- exit(1);
- }
点击(此处)折叠或打开
- for(fd = 0; fd < FD_SETSIZE; fd++)
- {
- if(FD_ISSET(fd,&testfds))
- {
- if(fd == server_sockfd) //活动发生在套接字server_sockfd上,是一个新的连接请求,把相关client_sockfd添加到描述符集中
- {
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd,
- (struct sockaddr *)&client_address, &client_len);
- FD_SET(client_sockfd, &readfds);//客户套接字添加到读描述符集中
- printf("adding client on fd %d\n", client_sockfd);
- }
- else { //活动发生在客户
- ioctl(fd, FIONREAD, &nread); //读取标准输入上的输入
- if(nread == 0) { //接收到的是close 客户已离去,从描述符集中删除套接字
- close(fd);
- FD_CLR(fd, &readfds);
- printf("removing client on fd %d\n", fd);
- }
- else {//读取客户端输入的一个字符,睡5秒后,将字符值加1 在写回到套接字
- read(fd, &ch, 1);
- sleep(5);
- printf("serving client on fd %d\n", fd);
- ch++;
- write(fd, &ch, 1);
- }
- }
- }
- }
- }
- }