初学linux c编程,在学习过程中遇到select()函数,看了几天总是对这块理解不够深刻。
先把自己的服务器代码贴上,服务器的意思是监听多个客户端,如果有新的客户端连接,输出新连接客户端的ip与端口号,建立连接,如果已链接客户端发来了新的数据,则转为大写再传给客户端。
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 8000
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr; //<span style="font-family: Arial, Helvetica, sans-serif;">sockaddr_in 是一个结构体在 in.h中定义</span><pre name="code" class="cpp">
listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));Listen(listenfd, 20);maxfd = listenfd; /* initialize */maxi = -1; /* index into client[] array */for (i = 0; i < FD_SETSIZE; i++)client[i] = -1; /* -1 indicates available entry */FD_ZERO(&allset);FD_SET(listenfd, &allset); //用于在文件描述符集合中增加一个新的文件描述符for ( ; ; ) {rset = allset; /* structure assignment */nready = select(maxfd+1, &rset, NULL, NULL, NULL);/*fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,每次清空一次rset//我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,//表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,//表示不关心任何文件的读变化。 */if (nready < 0)perr_exit("select error");if (FD_ISSET(listenfd, &rset)) { //使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。/*判断是否为服务器套接字,是则表示为客户请求连接。不是则处理数据请求*/ cliaddr_len = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);//accept()返回一个描述所接受包的SOCKET类型的值。//accept()返回一个描述所接受包的SOCKET类型的值,后续可以使用这个返回值与对方通信,也就是连接客户端的套接字printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));for (i = 0; i < FD_SETSIZE; i++)if (client[i] < 0) {client[i] = connfd; /* save descriptor */break;}if (i == FD_SETSIZE) {fputs("too many clients\n", stderr);exit(1);}FD_SET(connfd, &allset); /* add new descriptor to set */if (connfd > maxfd)maxfd = connfd; /* for select */if (i > maxi)maxi = i; /* max index in client[] array */if (--nready == 0)continue; /* no more readable descriptors */}//不是服务器套接字,表示客服端数据请求for (i = 0; i <= maxi; i++) { /* check all clients for data */if ( (sockfd = client[i]) < 0)continue;if (FD_ISSET(sockfd, &rset)) {//判断是否为所请求的客户端套接字,是则处理数据请求if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {/* connection closed by client */Close(sockfd);FD_CLR(sockfd, &allset);client[i] = -1;} else {int j;for (j = 0; j < n; j++)buf[j] = toupper(buf[j]);Write(sockfd, buf, n);}if (--nready == 0)break; /* no more readable descriptors */}}}}
最开始看代码时对select根本没有概念,只知道select后去判断&rset,如果是服务器描述符准备好了就去迎接新的连接,如果不是则去处理数据,处理数据时需要判断是哪个客户端发来的。单是具体为什么不知道,只知道用的时候就应该这样去写。
查了一些资料后我看到了一张图也验证了我的理解大体是正确的
注:select 原理图,摘自 IBM iSeries 信息中心。
单是让我不理解的是select函数究竟对&rset做了什么,为什么第一个参数要加1,带着疑惑我查看了select()的源码
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
ktime_t expire, *to = NULL;
struct poll_wqueues table;
poll_table *wait;
int retval, i, timed_out = 0;
unsigned long slack = 0;
rcu_read_lock();
retval = max_select_fd(n, fds);
rcu_read_unlock();
if (retval < 0)
return retval;
n = retval;
poll_initwait(&table);
[color=Red]wait = &table.pt;[/color]
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
wait = NULL;
timed_out = 1;
}
if (end_time && !timed_out)
slack = estimate_accuracy(end_time);
retval = 0;
for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
// fdset的输入参数,表明关心哪些fd
inp = fds->in; outp = fds->out; exp = fds->ex;
// fdset的输出参数,表明哪些关心的fd状态变成了可读可写可连接
rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
// 这里看看n,就是外面传进来的max_fd,就很能理解为什么max_fd需要加1了
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
unsigned long in, out, ex, all_bits, bit = 1, mask, j;
unsigned long res_in = 0, res_out = 0, res_ex = 0;
const struct file_operations *f_op = NULL;
struct file *file = NULL;
in = *inp++; out = *outp++; ex = *exp++;
all_bits = in | out | ex;
if (all_bits == 0) {
// 性能优化代码。一次判断4个字节,32个fd,如果都不关心,则可以一次跳过
i += __NFDBITS;
continue;
}
// 32个fd中至少有一个fd是被用户关心的,得瞧瞧
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
int fput_needed;
if (i >= n)
break;
if (!(bit & all_bits))
continue;
file = fget_light(i, &fput_needed);
if (file) {
f_op = file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op && f_op->poll) //调用pool看这个fd是否ready
mask = (*f_op->poll)(file, retval ? NULL : wait);
fput_light(file, fput_needed);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
}
}
}
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
cond_resched();
}
return retval;
}
此时就比较容易理解了。