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

24 篇文章 0 订阅

一、引言

    在实际情况中,人们往往遇到多个客户端连接服务器端的情况。由于之前介绍的函数如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.


用以下方法将socket设置为非阻塞方式 


int flags = fcntl(socket, F_GETFL, 0); 
fcntl(socket, F_SETFL, flags | O_NONBLOCK);

将非阻塞的设置回阻塞可以用


int flags = fcntl(socket, F_GETFL, 0); 
fcntl(socket, F_SETFL, flags & ~O_NONBLOCK);


例程:

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <sys/wait.h>  
  4. #include <stdio.h>  
  5. #include <stdlib.h>  
  6. #include <errno.h>  
  7. #include <string.h>  
  8. #include <sys/un.h>  
  9. #include <sys/time.h>  
  10. #include <sys/ioctl.h>  
  11. #include <unistd.h>  
  12. #include <netinet/in.h>  
  13. #include <fcntl.h>  
  14. #include <unistd.h>  
  15.   
  16. #define SERVPORT 3333  
  17. #define BACKLOG 10  
  18. #define MAX_CONNECTED_NO 10  
  19. #define MAXDATASIZE 100  
  20.   
  21. int main()  
  22. {  
  23.     struct sockaddr_in server_sockaddr,client_sockaddr;  
  24.     int sin_size,recvbytes,flags;  
  25.     int sockfd,client_fd;  
  26.     char buf[MAXDATASIZE];  
  27. /*创建socket*/  
  28.     if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){  
  29.         perror("socket");  
  30.         exit(1);  
  31.     }  
  32.     printf("socket success!,sockfd=%d\n",sockfd);  
  33.   
  34. /*设置sockaddr结构*/  
  35.     server_sockaddr.sin_family=AF_INET;  
  36.     server_sockaddr.sin_port=htons(SERVPORT);  
  37.     server_sockaddr.sin_addr.s_addr=INADDR_ANY;  
  38.     bzero(&(server_sockaddr.sin_zero),8);  
  39.   
  40. /*将本地ip地址绑定端口号*/  
  41.     if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){  
  42.         perror("bind");  
  43.         exit(1);  
  44.     }  
  45.     printf("bind success!\n");  
  46.   
  47. /*监听*/  
  48.     if(listen(sockfd,BACKLOG)==-1){  
  49.         perror("listen");  
  50.         exit(1);  
  51.     }  
  52.     printf("listening....\n");  
  53.   
  54. /*fcntl()函数,处理多路复用I/O*/  
  55.     if((flags=fcntl( sockfd, F_GETFL, 0))<0)  
  56.             perror("fcntl F_GETFL");  
  57.         flags |= O_NONBLOCK;  
  58.         if(fcntl( sockfd, F_SETFL,flags)<0)  
  59.             perror("fcntl");  
  60.     while(1){  
  61.         sin_size=sizeof(struct sockaddr_in);  
  62.         if((client_fd=accept(sockfd,(struct sockaddr*)&client_sockaddr,&sin_size))==-1){  //服务器接受客户端的请求,返回一个新的文件描述符  
  63.             perror("accept");  
  64.             exit(1);  
  65.         }  
  66.         if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){  
  67.             perror("recv");  
  68.             exit(1);  
  69.         }  
  70.         if(read(client_fd,buf,MAXDATASIZE)<0){  
  71.             perror("read");  
  72.             exit(1);  
  73.         }  
  74.         printf("received a connection :%s",buf);  
  75.   
  76. /*关闭连接*/  
  77.     close(client_fd);  
  78.     exit(1);  
  79.     }/*while*/  
  80. }  


运行该程序:

[cpp]  view plain copy
  1. [root@localhost net]# ./fcntl  
  2. socket success!,sockfd=3  
  3. bind success!  
  4. listening....  
  5. accept: Resource temporarily unavailable  
可以看到,当accept的资源不可用时,程序会自动返回。

若将红色加粗代码替换为:

[cpp]  view plain copy
  1. <span style="color:#FF6666;"if((flags=fcntl( sockfd, F_SETFL, 0))<0)  
  2.             perror("fcntl F_SETFL");  
  3.         <strong>flags |= O_ASYNC;</strong>  
  4.         if(fcntl( sockfd, F_SETFL,flags)<0)  
  5.             perror("fcntl");</span>  
运行结果如下:

[cpp]  view plain copy
  1. [root@localhost net]# ./fcntl1  
  2. socket success!,sockfd = 3  
  3. bind success!  
  4. listening...  
可以看到,进程一直处于等待中,直到另一相关信号驱动它为止。

三、select

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <sys/wait.h>  
  4. #include <stdio.h>  
  5. #include <stdlib.h>  
  6. #include <errno.h>  
  7. #include <string.h>  
  8. #include <sys/un.h>  
  9. #include <sys/time.h>  
  10. #include <sys/ioctl.h>  
  11. #include <unistd.h>  
  12. #include <netinet/in.h>  
  13. #define SERVPORT 3333  
  14. #define BACKLOG 10  
  15. #define MAX_CONNECTED_NO 10  
  16. #define MAXDATASIZE 100  
  17. int main()  
  18. {  
  19.     struct sockaddr_in server_sockaddr,client_sockaddr;  
  20.     int sin_size,recvbytes;  
  21.     fd_set readfd;  
  22.     fd_set writefd;  
  23.     int sockfd,client_fd;  
  24.     char buf[MAXDATASIZE];  
  25. /*创建socket*/  
  26.     if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){  
  27.         perror("socket");  
  28.         exit(1);  
  29.     }  
  30.     printf("socket success!,sockfd=%d\n",sockfd);  
  31. /*设置sockaddr结构*/  
  32.     server_sockaddr.sin_family=AF_INET;  
  33.     server_sockaddr.sin_port=htons(SERVPORT);  
  34.     server_sockaddr.sin_addr.s_addr=INADDR_ANY;  
  35.     bzero(&(server_sockaddr.sin_zero),8);  
  36. /*将本地ip地址绑定端口号*/  
  37.     if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){  
  38.         perror("bind");  
  39.         exit(1);  
  40.     }  
  41.     printf("bind success!\n");  
  42. /*监听*/  
  43.     if(listen(sockfd,BACKLOG)==-1){  
  44.         perror("listen");  
  45.         exit(1);  
  46.     }  
  47.     printf("listening....\n");  
  48. /*select*/  
  49.     FD_ZERO(&readfd);              // 将readfd 清空   
  50. FD_SET(sockfd,&readfd);         //将sockfd加入到readfd集合中  
  51.     while(1){  
  52.     sin_size=sizeof(struct sockaddr_in);  
  53.     if(select(MAX_CONNECTED_NO,&readfd,NULL,NULL,(struct timeval *)0)>0){  //第一个参数是0和sockfd的最大值加1,第二个参数是读集,第三、四个参数是写集                                                                                //和异常集  
  54.         if(FD_ISSET(sockfd,&readfd)>0){         // FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符。从 sockfd 中读入, 输出到标准输出上去.  
  55.             if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){   //client_sockaddr:客户端地址  
  56.                 perror("accept");  
  57.                 exit(1);  
  58.             }  
  59.             if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){  
  60.                 perror("recv");  
  61.                 exit(1);  
  62.             }  
  63.             if(read(client_fd,buf,MAXDATASIZE)<0){  
  64.                 perror("read");  
  65.                 exit(1);  
  66.             }  
  67.             printf("received a connection :%s",buf);  
  68.         }/*if*/  
  69.         close(client_fd);  
  70.         }/*select*/  
  71.     }/*while*/  
  72. }  
  73. 运行结果如下:  
  74. [root@localhost net]#  gcc select1.c -o select1  
  75. [root@localhost net]# ./select1  
  76. socket create success!  
  77. bind success!  
  78. listening...  

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

功能描述:根据文件描述词来操作文件的特性。

#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);


参数: 
fd:文件描述词。 
cmd:操作命令。 
arg:供命令使用的参数。 
lock:同上。

有以下操作命令可供使用

一. F_DUPFD :复制文件描述词 。


二. FD_CLOEXEC :设置close-on-exec标志。如果FD_CLOEXEC位是0,执行execve的过程中,文件保持打开。反之则关闭。


三. F_GETFD :读取文件描述词标志。


四. F_SETFD :设置文件描述词标志。


五. F_GETFL :读取文件状态标志。


六. F_SETFL :设置文件状态标志。 
其中O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOCTTY 和 O_TRUNC不受影响, 
可以更改的标志有 O_APPEND,O_ASYNC, O_DIRECT, O_NOATIME 和 O_NONBLOCK。


七. F_GETLK, F_SETLK 和 F_SETLKW :获取,释放或测试记录锁,使用到的参数是以下结构体指针: 
F_SETLK:在指定的字节范围获取锁(F_RDLCK, F_WRLCK)或者释放锁(F_UNLCK)。如果与另一个进程的锁操作发生冲突,返回 -1并将errno设置为EACCES或EAGAIN。


F_SETLKW:行为如同F_SETLK,除了不能获取锁时会睡眠等待外。如果在等待的过程中接收到信号,会立即返回并将errno置为EINTR。


F_GETLK:获取文件锁信息。


F_UNLCK:释放文件锁。


为了设置读锁,文件必须以读的方式打开。为了设置写锁,文件必须以写的方式打开。为了设置读写锁,文件必须以读写的方式打开。


八. 信号管理 
F_GETOWN, F_SETOWN, F_GETSIG 和 F_SETSIG 被用于IO可获取的信号。


F_GETOWN:获取当前在文件描述词 fd上接收到SIGIO 或 SIGURG事件信号的进程或进程组标识 。


F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。


F_GETSIG:获取标识输入输出可进行的信号。


F_SETSIG:设置标识输入输出可进行的信号。


使用以上命令,大部分时间程序无须使用select()或poll()即可实现完整的异步I/O。


九. 租约( Leases) 
F_SETLEASE 和 F_GETLEASE 被用于当前进程在文件上的租约。文件租约提供当一个进程试图打开或折断文件内容时,拥有文件租约的进程将会被通告的机制。


F_SETLEASE:根据以下符号值设置或者删除文件租约


1.F_RDLCK设置读租约,当文件由另一个进程以写的方式打开或折断内容时,拥有租约的当前进程会被通告。 
2.F_WRLCK设置写租约,当文件由另一个进程以读或以写的方式打开或折断内容时,拥有租约的当前进程会被通告。 
3.F_UNLCK删除文件租约。


F_GETLEASE:获取租约类型。


十.文件或目录改变通告 
(linux 2.4以上)当fd索引的目录或目录中所包含的某一文件发生变化时,将会向进程发出通告。arg参数指定的通告事件有以下,两个或多个值可以通过或运算组合。 
1.DN_ACCESS 文件被访问 (read, pread, readv) 
2.DN_MODIFY 文件被修改(write, pwrite,writev, truncate, ftruncate) 
3.DN_CREATE 文件被建立(open, creat, mknod, mkdir, link, symlink, rename) 
4.DN_DELETE 文件被删除(unlink, rmdir) 
5.DN_RENAME 文件被重命名(rename) 
6.DN_ATTRIB 文件属性被改变(chown, chmod, utime[s])


返回说明: 
成功执行时,对于不同的操作,有不同的返回值 
F_DUPFD: 新文件描述词 
F_GETFD: 标志值 
F_GETFL: 标志值 
F_GETOWN: 文件描述词属主 
F_GETSIG: 读写变得可行时将要发送的通告信号,或者0对于传统的SIGIO行为


对于其它命令返回0。


失败返回-1,errno被设为以下的某个值 
EACCES/EAGAIN: 操作不被允许,尚未可行 
EBADF: 文件描述词无效 
EDEADLK: 探测到可能会发生死锁 
EFAULT: 锁操作发生在可访问的地址空间外 
EINTR: 操作被信号中断 
EINVAL: 参数无效 
EMFILE: 进程已超出文件的最大可使用范围 
ENOLCK: 锁已被用尽 
EPERM:权能不允许 
struct flock { 
short l_type; /* 锁类型: F_RDLCK, F_WRLCK, F_UNLCK */ 
short l_whence; /* l_start字段参照点: SEEK_SET(文件头), SEEK_CUR(文件当前位置), SEEK_END(文件尾) */ 
off_t l_start; /* 相对于l_whence字段的偏移量 */ 
off_t l_len; /* 需要锁定的长度 */ 
pid_t l_pid; /* 当前获得文件锁的进程标识(F_GETLK) */ 
};








SelectSocket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下! Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明): int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 先说明两个结构体: 第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。 第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。 具体解释select的参数: int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 返回值: 负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值