初学linux c编程 对select()自己的理解

初学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;  
}  

此时就比较容易理解了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值