fcntl与select彻底搞明白(转载+整理)

本文详细介绍fcntl函数和select函数的使用方法。fcntl可用于设置文件描述符的属性,如非阻塞I/O,而select则用于监控多个文件描述符的状态变化,支持高效地处理多路I/O。文中还给出了具体的代码示例。

第一、fcntl函数详细使用      

 

  fcntl有强大的功能,它能够复制一个现有的描述符,获得/设置文件描述符标记,获得/设置文件状态标记,获得/设置异步I/O所有权,获得/设置纪录锁。

 

当多个用户共同使用,操作一个文件的情况,Linux通常采用的方法就是给文件上锁,来避免共享资源产生竞争的状态。

 

fcntl文件有两种型:建制性
         是这样规定的:每个使用上锁文件的进程都要检查是否有锁存在,当然还得尊重已有的锁。内核和系统总体上都坚持不使用建议性锁,它们依靠程序员遵守这个规定。
         制性是由内核执行的。当文件被上锁来进行写入操作时,在锁定该文件的进程释放该锁之前,内核会阻止任何对该文件的读或写访问,每次读或写访问都得检查锁是否存在。

 

        使用fcntl文件锁进行I/O操作必小心:进程在开始任何I/O操作前如何去处理锁,在对文件解锁前如何完成所有的操作,是必须考虑的。如果在设置锁之前打开文件,或者读取该锁之后关闭文件,另一个进程就可能在上锁/解锁操作和打开/关闭操作之间的几分之一秒内访问该文件。当一个进程对文件加锁后,无论它是否释放所加的锁,只要文件关闭,内核都会自动释放加在文件上的建议性锁(这也是建议性锁和强制性锁的最大区别), 所以不要想设置建议性锁来达到永久不让别的进程访问文件的目的(强制性锁才可以)^_^;强制性锁则对所有进程起作用。

 

      可以用fcntl函数改一个已打开的文件的属性,可以重新、写、追加、非阻塞等(志称File StatusFlag),而不必重新open 文件。
     #include <unistd.h>
      #include <
fcntl.h>
      int
fcntl(int fd, int cmd);
      int
fcntl(int fd, int cmd,long arg);
      int
fcntl(int fd, int cmd,struct flock *lock);

这个函数和open 一样,也是用可变参数实现的,可变参数的类型和个数取决于前面的cmd 参数。

文件包括了    和  制性

要求每个上的文件的程都要检查是否有存在,并且尊重已有的,在一般情况下,内核和系都不使用建

制性是由内核行的,当一个文件被上锁进写操作的候,内核将阻止其他任何文件写操作。每次写操作都要检查是否有存在。

在Linux中实现的函数有lock()fcntl()

lock()
用于文件施加建
fcntl()用于文件施加建制性都行。同时还可以文件某一条纪录进行上,也就是记录锁

记录锁  (共享,它能使多个程都能在文件的同一部分建立)  和  写入(排斥,在任何刻只能有一个程在文件的某部分建立写入。)。

fcntl函数原型
#include<sys/types.h>
#include<unistd.h>
#include<
fcntl.h>

int
fcntl(intfd,   //文件描述符
          int cmd , //
不同的命令
          struct flock *lock)  //记录锁的具体状

cmd

F_DUPFD  
复制文件描述符
F_GETFD   得fd的close-on-exec
F_SETFD  
置close-on-exec
F_GETFL  
得open
F_SETFL  
置lock描述的
F_GETLK  
测试该锁是否被另外一把排斥
F_SETLKW 
如果存在其他则调程睡眠,如果捕捉到信号睡眠中断
F_GETOWN 
索收到的SIGIO和SIGURG信号的程号或者
F_SETOWN 
程号或

里的lock构体如下:
struct flock
{
    short l_type;   /*F_RDLCK(
),F_WRLCK(写入),F_UNLCK(解)*/
    off_t l_start;  /*
偏移量(字)*/
    short l_whence; /*SEEK_SET ,SEEK_CUR ,SEEK_END */
    off_t l_len;    /*
区域度*/
    pid_t l_pid;
}

成功:0
:-1
提示:如果加整个文件通常的方法是将l_start0,l_whenceSEEK_SET, l_len0。 


       下面的例子使用F_GETFLF_SETFL两种fcntl命令改STDIN_FILENO的属性上O_NONBLOCK 选项,实现非阻塞读终端的功能。

fcntl改变File Status Flag

 


             #include <unistd.h>
            #include <
fcntl.h>
             #include<errno.h>
            #include <string.h>
            #include <stdlib.h>
            #define     MSG_TRY "try again\n"
             intmain(void)
             {
                    char buf[10];
                    int n;
                    int flags;
                    flags =
fcntl(STDIN_FILENO, F_GETFL);
                    flags |= O_NONBLOCK;
                    if (
fcntl(STDIN_FILENO, F_SETFL, flags) == -1)
           
{
                            perror("
fcntl");
                            exit(1);
                    }
            tryagain:
                    n = read(STDIN_FILENO, buf, 10);
                    if (n < 0)
                   {
                            if (errno == EAGAIN)
                           {
                                    sleep(1);
                                    write(STDOUT_FILENO, MSG_TRY,strlen(MSG_TRY));
                                    goto tryagain;
                            }
                            perror("read stdin");
                            exit(1);
                    }
                    write(STDOUT_FILENO, buf, n);
                    return 0;
             }

 

 

 

 第二、select函数详细使用   

 

     select系统调用是用来让我们的程序监视多个文件句柄(filedescriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

        文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket isreturned.”而man 2 open可以看到“open() and creat()return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:
intsocket(int domain, int type, int protocol);
        当然,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。
比如下面两段代都是从9个字字符:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
        char buf[10] = "";
        read(0, buf, 9); /*
0 入字符 */
        fprintf(stdout, "%s\n",buf); /*
stdout 写字符 */
        return 0;
}
/* **
上面和下面的代都可以用来从户输入的9个字符** */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char ** argv)
{
        char buf[10] = "";
        fread(buf, 9, 1, stdin); /*
stdin 入字符 */
        write(1, buf, strlen(buf));
        return 0;
}

 

        继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set*exceptfds, struct timeval *timeout);
        函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个structtimeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

 

 



struct timeval {
             long    tv_sec;         /* seconds */
             long    tv_usec;        /* microseconds */
         };


第2、3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds,&exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:



fd_set rdfds; /* 先申明一个 fd_set 集合来保存我检测 socket句柄 */
struct timeval tv; /*
申明一个时间变量来保存时间 */
int ret; /*
保存返回 */
FD_ZERO(&rdfds); /*
select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /*
把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /*
select等待的最大时间为1秒加500毫秒 */
ret =
select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测上面置到集合rdfds里的句柄是否有可信息 */
if(ret < 0) perror("
select");/* 这说select函数出 */
else if(ret == 0) printf("
\n"); /* 明在我们设定的时间值1秒加500毫秒的时间内,socket的状没有 */
else { /*
明等待时间还未到1秒加500毫秒,socket的状态发生了 */
    printf("ret=%d\n", ret); /* ret
个返回值记录生状态变化的句柄的数目,由于我监视socket一个句柄,所以里一定ret=1,如果同有多个句柄化返回的就是句柄的和了 */
    /*
里我应该socket个句柄里取数据了,因select函数已们这个句柄里有数据可 */
    if(FD_ISSET(socket, &rdfds)) { /*
先判断一下socket外被监视的句柄是否真的成可的了 */
        /*
socket句柄里的数据 */
        recv(...);
    }
}

 

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:
/************关于本文档********************************************
*filename: Linux网络编程一步一步学-select详解
*purpose: 详细说明select的用法
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-02-03 19:40
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to:Google
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/
int sa, sb, sc;
sa = socket(...); /*
别创3个句柄并接到服器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);

FD_SET(sa, &rdfds);/*
3个句柄加入读监视集合里去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);

在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

 

 



int maxfd = 0;
if(sa > maxfd) maxfd = sa;
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;

 

然后调用select函数:

 

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

 

同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:

 

 



FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret =
select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
if(ret < 0) perror("
select");/* */
else if(ret == 0) printf("
\n"); /* 在我们设定的时间tv内,用没有按键盘 */
else { /*
有按键盘,要取用 */
    scanf("%s", buf);
}

 

 

linuxsocket网络编程:fcntl select(多个客户端连接服务器端情形)

一、引言

    实际情况中,人往往遇到多个客接服器端的情况。由于之前介的函数如connect,recv,send等都是阻塞性函数,若源没有充分准好,则调函数的程将入睡眠状这样就无法I/O多路复用的情况了。

    本文两种I/O多路复用的方法:fcntl(),select()。可以看到,由于Linux中把socket当作一种特殊的文件描述符,这给来很大方便。

二、fcntl

fcntl()函数有如下特性:

1)非阻塞I/O:可将cmd 设为F_SETFL,lock设为O_NONBLOCK

2)信号驱动I/O:可将cmd设为F_SETFL,lock设为O_ASYNC.

例程:

#include <sys/types.h>

#include <sys/socket.h>

#include <sys/wait.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/un.h>

#include <sys/time.h>

#include <sys/ioctl.h>

#include <unistd.h>

#include <netinet/in.h>

#include <fcntl.h>

#include <unistd.h>

 

#define SERVPORT 3333

#define BACKLOG 10

#define MAX_CONNECTED_NO 10

#define MAXDATASIZE 100

 

int main()

{

       structsockaddr_in server_sockaddr,client_sockaddr;

       intsin_size,recvbytes,flags;

       intsockfd,client_fd;

       charbuf[MAXDATASIZE];

/*创建socket*/

       if((sockfd= socket(AF_INET,SOCK_STREAM,0))==-1){

              perror("socket");

              exit(1);

       }

       printf("socketsuccess!,sockfd=%d\n",sockfd);

 

/*设置sockaddr结构*/

       server_sockaddr.sin_family=AF_INET;

       server_sockaddr.sin_port=htons(SERVPORT);

       server_sockaddr.sin_addr.s_addr=INADDR_ANY;

       bzero(&(server_sockaddr.sin_zero),8);

 

/*将本地ip地址绑定端口号*/

       if(bind(sockfd,(structsockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){

              perror("bind");

              exit(1);

       }

       printf("bindsuccess!\n");

 

/*监听*/

       if(listen(sockfd,BACKLOG)==-1){

              perror("listen");

              exit(1);

       }

       printf("listening....\n");

 

/*fcntl()函数,处理多路复用I/O*/

       if((flags=fcntl(sockfd, F_SETFL, 0))<0)

                     perror("fcntlF_SETFL");

              flags|= O_NONBLOCK;

              if(fcntl(sockfd, F_SETFL,flags)<0)

                     perror("fcntl");

       while(1){

              sin_size=sizeof(structsockaddr_in);

              if((client_fd=accept(sockfd,(structsockaddr*)&client_sockaddr,&sin_size))==-1){  //服务器接受客户端的请求,返回一个新的文件描述符

                     perror("accept");

                     exit(1);

              }

              if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){

                     perror("recv");

                     exit(1);

              }

              if(read(client_fd,buf,MAXDATASIZE)<0){

                     perror("read");

                     exit(1);

              }

              printf("receiveda connection :%s",buf);

 

/*关闭连接*/

       close(client_fd);

       exit(1);

       }/*while*/

}

运行该程序:

[root@localhostnet]# ./fcntl

socketsuccess!,sockfd=3

bind success!

listening....

accept: Resourcetemporarily unavailable

可以看到,当accept源不可用,程序会自返回

若将色加粗代换为

if((flags=fcntl(sockfd, F_SETFL, 0))<0)

       perror("fcntl F_SETFL");

flags |= O_ASYNC;

if(fcntl( sockfd,F_SETFL,flags)<0)

       perror("fcntl");

运行果如下:

[root@localhostnet]# ./fcntl1

socketsuccess!,sockfd = 3

bind success!

listening...

可以看到,程一直于等待中,直到另一相关信号驱动止。

三、select

#include<sys/types.h>

#include<sys/socket.h>

#include<sys/wait.h>

#include<stdio.h>

#include<stdlib.h>

#include<errno.h>

#include<string.h>

#include<sys/un.h>

#include<sys/time.h>

#include<sys/ioctl.h>

#include <unistd.h>

#include<netinet/in.h>

#define SERVPORT3333

#define BACKLOG 10

#defineMAX_CONNECTED_NO 10

#defineMAXDATASIZE 100

int main()

{

       struct sockaddr_inserver_sockaddr,client_sockaddr;

       int sin_size,recvbytes;

       fd_set readfd;

       fd_set writefd;

       int sockfd,client_fd;

       char buf[MAXDATASIZE];

/*创建socket*/

       if((sockfd =socket(AF_INET,SOCK_STREAM,0))==-1){

              perror("socket");

              exit(1);

       }

       printf("socketsuccess!,sockfd=%d\n",sockfd);

/*设置sockaddr结构*/

       server_sockaddr.sin_family=AF_INET;

       server_sockaddr.sin_port=htons(SERVPORT);

       server_sockaddr.sin_addr.s_addr=INADDR_ANY;

       bzero(&(server_sockaddr.sin_zero),8);

/*将本地ip地址绑定端口号*/

       if(bind(sockfd,(struct sockaddr*)&server_sockaddr,sizeof(struct sockaddr))==-1){

              perror("bind");

              exit(1);

       }

       printf("bind success!\n");

/*监听*/

       if(listen(sockfd,BACKLOG)==-1){

              perror("listen");

              exit(1);

       }

       printf("listening....\n");

/*select*/

       FD_ZERO(&readfd);              // 将readfd 清空

FD_SET(sockfd,&readfd);         //将sockfd加入到readfd集合中

       while(1){

       sin_size=sizeof(struct sockaddr_in);

       if(select(MAX_CONNECTED_NO,&readfd,NULL,NULL,(structtimeval *)0)>0){  //第一个参数是0和sockfd的最大值加1,第二个参数是读集,第三、四个参数是写集                                                                               //和异常集

              if(FD_ISSET(sockfd,&readfd)>0){         // FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符。从 sockfd 中读入, 输出到标准输出上去.

                     if((client_fd=accept(sockfd,(structsockaddr *)&client_sockaddr,&sin_size))==-1){   //client_sockaddr:客户端地址

                            perror("accept");

                            exit(1);

                     }

                     if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){

                            perror("recv");

                            exit(1);

                     }

                     if(read(client_fd,buf,MAXDATASIZE)<0){

                            perror("read");

                            exit(1);

                     }

                     printf("received aconnection :%s",buf);

              }/*if*/

              close(client_fd);

              }/*select*/

       }/*while*/

}

运行结果如下:

[root@localhostnet]#  gcc select1.c -o select1

[root@localhostnet]# ./select1

socket createsuccess!

bind success!

listening...

 

 

 

 

深入UNIX编程:一个简单聊天室的两种实现 (fcntl 和 select)

在互网相当普及的今天,在互网上聊天很多网虫是家常便了。聊天室程序可以是网上最简单的多点通信程序。聊天室的实现方法有很多,但都是利用所多用信息行交,具有典型的多路I/O的架构。一个简单的聊天室, 从程序点来看就是在多个I/O端点之间实现多的通信。其架构如一所示。这样实现在用的眼里就是聊天室内任何一个人入一段字符之后,其他用都可以得到一句多用的架构在其他多点通信程序中用的非常广泛,其核心就是多路I/O通信。多路I/O通信又被称I/O多路复用(I/O Multiplexing)一般被使用在以下的合:

   
程序需要同时处理交互式的入和同服器之的网络连需要I/O多路复用问题

   
端需要同时对多个网络连接作出反种情况很少);

    TCP
器需要同时处听状和多个接状socket

   
器需要理多个网络协议socket;

   
器需要同时处理不同的网协议 

   
聊天室所需要面的情况正是第一和第三两种情况。我将通TCP/IP协议之上建立一个功能简单的聊天室大家更加了解多路I/O以及它的实现方法。讨论的聊天室功能非常简单, 趣的朋友可以将其功能, 展成一个功能比完整的聊天室, 如加上用户认证, 昵称, 秘密信息, semote 等功能. 首先它是一个 client/server 构的程序, 首先启server, 然后用使用client .client/server 构的点是速度快, 缺点是当 server 行更新, client 也必需更新.

初始化 


   
首先是初始化server, 使server 听状: (简洁,以下引用的程序与实际程序略有出入, 下同)

sockfd = socket( AF_INET,SOCK_STREAM, 0);

//
首先建立一个 socket, AF_INET, SOCK_STREAM.

// AF_INET = ARPA Internet protocols
即使用 TCP/IP 协议

// SOCK_STREAM
型提供了序的, 可靠的, 基于字流的全双工.

//
由于该协议族中只有一个协议, 因此第三个参数 0


bind( sockfd, ( struct sockaddr *)&serv_addr, sizeof( serv_addr));

//
再将socket 与某个地址.

// serv_addr
包括sin_family = AF_INET 协议族同socket

// sin_addr.s_addr = htonl( INADDR_ANY) server
所接受的所有其他

//
地址求建立的.

// sin_port = htons( SERV_TCP_PORT) server
听的端口

//
在本程序中, server IP听的端口都存放在config 文件中.


listen( sockfd, MAX_CLIENT);

//
地址定之后,server 听状.

// MAX_CLIENT
是可以同建立接的 client.

server
listen , 等待 client 建立接。


Client
端要建立接首先也需要初始化接:

sockfd = socket( AF_INET,SOCK_STREAM,0));

//
,client 也先建立一个 socket, 其参数与 server 相同.


connect( sockfd, ( struct sockaddr *)&serv_addr, sizeof( serv_addr));

// client
使用 connect建立一个.

// serv_addr
中的量分别设:

// sin_family = AF_INET
协议族同socket

// sin_addr.s_addr = inet_addr( SERV_HOST_ADDR)
地址server

//
所在的算机的地址.

// sin_port = htons( SERV_TCP_PORT)
端口 server 听的端口.


client 建立新接的求被送到Server,server 使用 accept 来接受该连:

accept( sockfd, (struct sockaddr*)&cli_addr, &cli_len);

//
在函数返回,cli_addr 中保留的是该连方的信息

//
包括方的 IP 地址和方使用的端口.

// accept
返回一个新的文件描述符


   
server listen 之后, 由于已有多个用线,所以程序需要同时对这些用户进行操作,并在它间实现信息交实现上称I/O多路复用技。多路复用一般有以下几种方法: 

   
非阻塞通信方法:将文件管道通fcntl()设为非阻塞通信方式,每隔一端时间对们实行一次轮询,以判断是否可以写操作。种方式的缺点是用太高,大部分源浪轮询上。 

   
程方法:用多个子程,每一个一个工阻塞方式通信。所有子程通IPC和父行通信。父程掌管所有信息。种方式的缺点是实现,而且由于IPC在各个操作系平台上并不完全一致,会致可移植性降低。 

   
信号驱动SIGIO)的异步I/O方法:首先,异步I/O是基于信号机制的,并不可靠。其次一的信号不足以提供更多的信息来源。是需要助以其他的手段,实现上有很高的度。 

   
select ()方法:在BSD中提供了一种可以多路I/O行阻塞式查询的方法——select()。它提供同时对多个I/O描述符行阻塞式查询的方法,利用它,我可以很方便的实现多路复用。根据UNIX范的协议,POSIX也采用了种方法,因此,我可以在大多数操作系中使用select方法。 

   
使用专门I/O多路复用器:在“UNIX? SYSTEM V Programmer's Guide:STREAMS”详细明了构造和使用多路复用器的方法。里就不再述了。


下面分别讨论多路I/O的两种实现:

1.
非阻塞通信方法 

   
一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞。所阻塞方式的意思是指, 试图对该文件描述符, 如果当没有西可,或者暂时不可写, 程序就入等待状, 直到有西可或者可写止。而于非阻塞状, 如果没有西可, 或者不可写, 写函数上返回, 而不会等待。缺省情况下, 文件描述符于阻塞状。在实现聊天室, server 需要查询与各client 建立的 socket, 一旦可就将 socket 中的字符出来并向所有其他client 送。并且, server 要随时查看是否有新的 client 试图建立,这样, 如果 server 在任何一个地方阻塞了, 其他 client 送的内容就会受到影响,得不到服器的及。新 client 试图建立接也会受到影响。所以我里不能使用缺省的阻塞的文件工作方式,而需要将文件的工作方式成非阻塞方式。在UNIX下,函数fcntl()可以用来改文件I/O操作的工作方式,函数描述如下:

fcntl( sockfd, F_SETFL, O_NONBLOCK);

// sockfd
是要改的文件描述符.

//F_SETFL 表明要改文件描述符的状

// O_NONBLOCK
表示将文件描述符变为非阻塞的.


省篇幅我使用自然言描述聊天室server :

while ( 1)

{

    if
有新 then 建立并记录该;

    for (
所有的有效)

        begin

            if
该连接中有字符可 then

                begin

                   
入字符串;

                for (
所有其他的有效)

                    begin

                      
字符串给该连;

                    end;

                end;

        end;

    end. 

   
由于判断是否有新, 是否可都是非阻塞的, 因此每次判断,不管有是没, 都会上返回. 这样,任何一个 client server 送字符或者试图建立新, 都不会其他 client 的活造成影响。

client 而言, 建立接之后, 只需要理两个文件描述符, 一个是建立了接的 socket 描述符, 另一个是. server , 如果使用阻塞方式的, 很容易因其中一个暂时没有入而影响另外一个的.. 因此将它成非阻塞的, 然后client 行如下:

while (
不想退出)

    begin

    if (
server 接有字符可)

        begin

       
该连, 出到出上去.

        End;

    if (
入可)

        Begin

       
, 出到与server 接中去.

        End;

    End.

上面的写分别调这样两个函数:

read( userfd, line, MAX_LINE);

// userfd
是指第 i client 接的文件描述符.

// line
是指出的字符存放的位置.

// MAX_LINE 是一次最多出的字符数.

//
返回实际读出的字符数.


write( userfd[j], line, strlen( line));

// userfd[j]
是第 j client 的文件描述符.

// line 是要送的字符串.

// strlen( line)
是要送的字符串.


分析上面的程序可以知道, 不管是 server client, 都不停的查询各个文件描述符, 一旦可入并. 这样的程序, 不停的在, 只要有CPU , 就不会放因此统资源的消耗非常大。server或者 client , CPU 源的 98% 左右都被其占用。极大的消耗了系统资源。

2select 方法 

   
因此,然我不希望在某一个用没有反应时阻塞其他的用,但我应该在没有任何用有反的情况之下停止程序的运行,占的系统资源,入阻塞状。有没有种方法呢?在的UNIX中都提供了select方法,具体实现方式如下:

    select
方法中, 所有文件描述符都是阻塞的. 使用 select 判断一文件描述符中是否有一个可(), 如果没有就阻塞, 直到有一个的候就被. 先看比较简单 client 实现:

由于 client 只需要理两个文件描述符, 因此, 需要判断是否有可写的文件描述符只需要加入两:

FD_ZERO( sockset);

//
sockset 清空

FD_SET( sockfd, sockset);

//
sockfd 加入到 sockset 集合中

FD_SET( 0, sockset);

//
0 () 加入到 sockset 集合中


然后 client 理如下:

while (
不想退出)

{

    select( sockfd 1, &sockset, NULL, NULL, NULL);

    //
时该函数将阻塞直到入或者 sockfd 中有一个可读为

    //
第一个参数是 0 sockfd 中的最大加一

    //
第二个参数是, 也就是 sockset

    //
第三, 四个参数是写集和异常集, 在本程序中都

    //
第五个参数是超时时间, 即在指定时间内仍没有可,

    //
并返回. 个参数NULL , 时时间无限.

   
// select 返回, sockset 中包含的只是可

    //
那些文件描述符.



    if ( FD_ISSET( sockfd, &sockset))

    {

        // FD_ISSET
个宏判断sockfd 是否属于可的文件描述符

       
sockfd , 出到出上去.

    }

    if ( FD_ISSET( 0, &sockset))

    {

        // FD_ISSET
个宏判断sockfd 是否属于可的文件描述符

       
, 出到 sockfd 中去.

    }

   
重新 sockset. (即将 sockset 清空, 并将 sockfd 0 加入)

}


下面看 server 的情况:


sockset 如下:

FD_ZERO( sockset);

FD_SET( sockfd, sockset);

for (
所有有效)

FD_SET( userfd, sockset);

}

maxfd =
最大的文件描述符号  1;


server
理如下:

while ( 1)

{

    select( maxfd, &sockset, NULL, NULL, NULL);

    if ( FD_ISSET( sockfd, &sockset))

    {

        //
有新

       
建立新, 并将该连接描述符加入到sockset 中去了.

    }

    for (
所有有效)

    {

        if ( FD_ISSET ( userfd, &sockset))

        {

            //
该连接中有字符可

           
该连接中入字符, 送到其他有效接中去.

        }

    }

   
重新 sockset;

}

性能比 

   
由于采用select 机制, 因此当没有字符可读时, 程序于阻塞状,最小程度的占用CPU , 在同一台机器上行一个server 和若干个client, 统负载只有 0.1 左右, 而采用原来的非阻塞通信方法, 只运行一个 server, 统负载就可以达到 1.5 左右. 因此我推荐使用select.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值