前面看了如何用套接字来实现本地和跨网络的客户/服务器系统连接,一旦建立了连接,套接字的连接行为就类似于打开的底层文件描述符及双向管道。服务器程序在接收来自客户的一个新连接时会创建出一个新的套接字,而原来的监听套接字将被保留以继续监听以后的连接,如果服务器不能立刻接收后来的连接,他们将被放到队列中以等待处理。
对于多客户的处理,可以利用套接字类似文件描述符的行为来解决。如果服务器调用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);
/* 创建一个连接队列,初始化readfds以处理来自server_sockfd的输入 */
- 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);
- }
/* 有活动发生,用FD_ISSET来依次检查每个描述符集,发现活动发生在哪个描述符上 */
- 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);
- }
- }
- }
- }
- }
- }
运行该服务器程序时,可以在客户端运行多个客户程序,他将在一个进程中对多个客户进行依次处理。客户端程序可以与前面的一样。
阅读(2351) | 评论(0) | 转发(1) |