select函数
函数声明
int select(int nfds,
fd_set *readfds,fd_set *writefds,fd_set *exceptfds,
struct timeval *timeout);
返回值:成功返回有数据的文件描述符,失败返回-1
nfds:最大的文件描述符,值为readfds,writefds,exceptfds中最大的文件描述符+1
readfds:可读集合,常用
writefds:可写集合,不常用
exceptfds:异常集合,不常用
timeout:超时时间。NULL永久阻塞,0非阻塞
struct timeval结构体:
struct timeval {
time_t tv_sec; //s
suseconds_t tv_usec; //us
};
fd_set结构体:
fd_set就是一个整型数,每一个bit位代表一个文件描述符fd。
当fd可操作时,对应的位就会自动置1,读取之后需要手动清零。操作函数如下:
//将文件描述符从集合中删除
void FD_CLR(int fd, fd_set *set);
//查看文件描述符是否存在于集合当中
int FD_ISSET(int fd, fd_set *set);
//添加文件描述符,添加后就可以检测该文件描述符
void FD_SET(int fd, fd_set *set);
//初始化集合
void FD_ZERO(fd_set *set);
示例代码
代码功能:
这是一个服务器端的socket,使用select以阻塞的方式去监听多个文件描述符的数据。当监听到数据后,代表数据已经到达,这时不论是调用accept还是read都是不会阻塞的,即:不会有阻塞等待数据到来的过程,会有一小段阻塞拷贝数据的过程。
当监听到的文件描述符是fd时,说明是有客户端需要接入,这时就执行accept相关操作;当监听到不为fd时,这代表是客户端发来数据,需要读取客户端的数据,那么使用循环去轮询各个客户端的套接字文件描述符,从而与客户端进行通信。
代码实现:
server.c代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define BACKLOG 5 //最大接入客户端数量
int socket_init(char** argv);
int main(int argc ,char** argv){
int fd;
//判断参数有效性
if(argc != 3){
printf("param err\n");
printf("%s<ip><port>\n",argv[0]);
return -1;
}
printf("ip = %s\n",argv[1]);
printf("port = %s\n",argv[2]);
//初始化socket:TCP,IPv4,本地回环测试
fd = socket_init(argv);
//接受客户端链接
int newFd;
struct sockaddr_in newAddr;
socklen_t newAddrlen;
char buf[100] = {0};
fd_set readfds,readfdsTmp;
int i,nfds;
//1.清空可读可写集合,并将服务器的fd添加进可读集合
FD_ZERO(&readfds);
FD_ZERO(&readfdsTmp);
FD_SET(fd,&readfds);
nfds = fd + BACKLOG + 1;
while(1){
readfdsTmp = readfds;
//2.以阻塞方式监听全部文件描述符,如果有文件描述符可以写入,则返回
if(select(nfds,&readfdsTmp,NULL,NULL,NULL) == -1){
perror("select");
exit(