Linux编程之select:
select作用是:在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。
1、socket阻塞模式
通常在socket编程中,我们习惯于写connect、accept、recv、recvfrom这样的阻塞程序。如果事件不发生,程序就一直阻塞在那里,无法返回。
2、socket非阻塞模式:select
用select就可以完成非阻塞,进程或线程执行到此函数时,不必非要等待事件的发生,执行到这里之后,会根据select返回结果来反映执行情况。如果事件发生,则根据发生时逻辑执行,如果没发生,会继续执行。它主要时监视我们需要监视的文件描述符的变化情况——读写或是异常。
相关API:
参数一:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的。
参数二:可读文件描述符集合
参数三:可写文件描述符集合
参数四:异常事件文件描述符集合
参数五:用于设置超时时间。NULL表示无限等待,类似于阻塞。
返回值:0:超时;-1:失败;成功返回大于0的整数,这个整数就是就绪描述符的数目。
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfdp,fd_set *readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout);
fd_set可以理解为一个集合,这个集合中存放了需要监控的文件描述符。可以通过FD_ZERO,FD_SET,FD_CLR,FD_ISSET来进行操作。
struct timeval结构体:
struct timeval
{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
};
操作fd_set的几个宏:
#include <sys/select.h>
int FD_ZERO(int fd, fd_set *fdset); //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset); //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set); //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位
使用范例:
fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd, &rset);
然后使用select函数:
select(fd+1, &rset, NULL, NULL, NULL);
select返回后,用FD_ISSET来测试是否置位:
if(FD_ISSET(fd, &rset))
{
...
//do something
}
原理解析:
服务端将需要进行IO操作的socket添加到select中,然后等待select系统调用返回。当数据到达时,socket被激活,select函数返回。服务端进行数据读取。
可以看到,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作。降低了效率。但使用select后,服务端可以在一个线程内同时处理多个socket请求。服务端注册好socket之后,可以通过select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。如果是在同步阻塞线程中,必须通过多线程来实现。
server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#define LINTEN_QUEUE 5
#define PORT 8888
#define MAXLEN 1024
int socket_bind_listen()
{
struct sockaddr_in server_address;
int server_sockfd;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(PORT);
bind(server_sockfd, (struct sockaddr*) & server_address, sizeof(server_address));
listen(server_sockfd, LINTEN_QUEUE);
return server_sockfd;
}
void socket_select(int server_sockfd)
{
fd_set readfds, testfds;
int client_sockfd;
struct sockaddr_in client_address;
int client_len;
FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
struct timeval tv;
while (1)
{
tv.tv_sec = 5;
tv.tv_usec = 0;
int fd;
int nread;
testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量
int result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, NULL); //FD_SETSIZE:系统默认的最大文件描述符
if (result < 0)
{
perror("server selelct error");
exit(1);
}
else if (result == 0)
{
printf("time out\n");
//continue;
}
/*扫描所有的文件描述符*/
for (fd = 0; fd < FD_SETSIZE; fd++)
{
/*找到相关文件描述符*/
if (FD_ISSET(fd, &testfds))
{
/*判断是否为服务器套接字,是则表示为客户请求连接。*/
if (fd == server_sockfd)
{
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr*) & client_address, &client_len );
FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
printf("adding client on fd %d\n", client_sockfd);
}
/*客户端socket中有数据请求时*/
else
{
ioctl(fd, FIONREAD, &nread);//取得数据量交给nread
/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
if (nread == 0)
{
close(fd);
FD_CLR(fd, &readfds); //去掉关闭的fd
printf("removing client on fd %d\n", fd);
}
/*处理客户数据请求*/
else
{
char buf[MAXLEN] = "";
recv(fd, buf, MAXLEN, 0);
printf("buf:%s\n", buf);
printf("serving client on fd %d\n", fd);
}
}
}
}
}
}
int main()
{
int server_sockfd;
server_sockfd = socket_bind_listen();
socket_select(server_sockfd);
return 0;
}
client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string.h>
int main()
{
int client_sockfd;
int len;
struct sockaddr_in address;//服务器端网络地址结构体
int result;
client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(8888);
len = sizeof(address);
result = connect(client_sockfd, (struct sockaddr *)&address, len);
if(result == -1)
{
perror("oops: client2");
exit(1);
}
char buf[1024] = "hello";
send(client_sockfd, buf, strlen(buf), 0);
close(client_sockfd);
return 0;
}
参考:
https://blog.csdn.net/weixin_41010318/article/details/80257177
https://www.cnblogs.com/skyfsm/p/7079458.html
https://blog.csdn.net/piaojun_pj/article/details/5991968