目录
1、select简介
在linux网络编程I/O复用使用的函数之一就是select,select函数是一个古老的接口
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select函数主要有5个参数:
nfds:被监听所有文件描述最大加1,因为文件描述是从0开始的。
readfds:监听文件描述的可读事件。
writefds:监听文件描述的可写事件。
exceptfds:监听文件描述的异常事件。
timeout:超时时间。
select的返回值:
大于0:返回准备号的文件描述数量。
等于0:监听超时。
等于-1:监听错误。
struct timeval
{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
};
timeout有三种情况
(1) timeout == NULL
等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号, select函数将返回-1,并将变量 erro设为 EINTR
(2)timeout->tv_sec == 0 &&timeout->tv_usec == 0
这种情况不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态
(3)timeout->tv_sec != 0 || timeout->tv_usec != 0
等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回 0。对于第一种情况,等待也会被信号所中断。
select的操作主要有以下四个宏:
int FD_ZERO(int fd, fd_set *fdset); //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset); //清除某个位时可以使用 i
nt FD_SET(int fd, fd_set *fd_set); //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位
2、select使用
下面介绍一个select的使用例子
tcp_server:
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<sys/unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<string.h>
int main(int argc, char *argv[]){
int server_fd = 0;
int port = 0;
char *ip = NULL;
int ret = 0;
struct sockaddr_in address;
fd_set read_fds;
char buf[512] = {0};
struct timeval timeout;
struct sockaddr_in client_address;
int socket_len = sizeof(client_address);
int client_fd = 0;
int max_fds = 0;
if(argc < 3){
printf("error argv!\n");
return -1;
}
port = atoi(argv[2]);
ip = argv[1];
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_fd < 0){
printf("socket error!\n");
return -1;
}
ret = bind(server_fd, (struct sockaddr*)&address, sizeof(address));
if(ret == -1){
printf("bind error!\n");
return -1;
}
ret = listen(server_fd, 5);
if(ret == -1){
printf("listen error!\n");
return -1;
}
timeout.tv_sec = 5;
timeout.tv_usec = 0;
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
FD_SET(STDIN_FILENO, &read_fds);
if(server_fd > STDIN_FILENO){
max_fds = server_fd +1;
}else{
max_fds = STDIN_FILENO + 1;
}
while(1){
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
FD_SET(STDIN_FILENO, &read_fds);
FD_SET(client_fd, &read_fds);
if(client_fd + 1 > max_fds)
max_fds = client_fd + 1;
ret = select(max_fds, &read_fds, NULL, NULL, NULL);
if(ret < 0){
printf("select error!\n");
break;
}else if(ret == 0){
printf("select timeout!\n");
}
if(FD_ISSET(server_fd, &read_fds)){
client_fd = accept(server_fd, (struct sockaddr*)&client_address, &socket_len);
if(client_fd < 0){
printf("accept error!\n");
}
printf("new client:%d\n", client_fd);
}
if(FD_ISSET(client_fd, &read_fds)){
bzero(buf, 512);
ret = recv(client_fd, buf, sizeof(buf), 0);
printf("server rcv:%s", buf);
}
if(FD_ISSET(STDIN_FILENO, &read_fds)){
bzero(buf, 512);
fgets(buf, 512, stdin);
ret = send(client_fd, buf, sizeof(buf), 0);
}
}
close(server_fd);
close(client_fd);
}
tcp_client:
#include<sys/socket.h>
#include<sys/select.h>
#include<sys/unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main(int argc, char *argv[]){
int client_fd = 0;
int port = 0;
char *ip = NULL;
int ret = 0;
int max_fds = 0;
struct sockaddr_in server_addr;
int sock_len = sizeof(server_addr);
fd_set read_fds;
char buf[512] = {0};
if(argc < 3){
printf("argc error!\n");
return - 1;
}
port = atoi(argv[2]);
ip = argv[1];
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &server_addr.sin_addr);
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_fd < 0){
printf("socket error!\n");
return -1;
}
ret = connect(client_fd, (struct sockaddr*)&server_addr, sock_len);
if(ret == -1){
printf("connect error!\n");
return -1;
}
if(client_fd > STDIN_FILENO){
max_fds = client_fd +1;
}else{
max_fds = STDIN_FILENO + 1;
}
while(1){
FD_ZERO(&read_fds);
FD_SET(client_fd, &read_fds);
FD_SET(STDIN_FILENO, &read_fds);
ret = select(max_fds, &read_fds, NULL, NULL, NULL);
if(ret < 0){
printf("select error!\n");
break;
}
if(FD_ISSET(client_fd, &read_fds)){
bzero(buf, 512);
ret = recv(client_fd, buf, sizeof(buf), 0);
if(ret < 0){
if(errno == EINTR){
continue;
}
printf("read error!\n");
break;
}else if(ret == 0){
printf("server is close!\n");
break;
}
printf("client rcv:%s", buf);
}else if(FD_ISSET(STDIN_FILENO, &read_fds)){
bzero(buf, 512);
fgets(buf, 512, stdin);
ret = send(client_fd, buf, sizeof(buf), 0);
}
}
close(client_fd);
}
3、select缺点
select函数有以下缺点:
1、select会改变传进来的fd_sets所以每次在select之前多要重新调用FD_SET把要监听的文件描述加进来,即使你监听的文件描述没有改变。
2、FD_ISSET检查监听的文件描述是采用轮询的方式,这样比较耗CPU性能,效率不高。
3、监听的文件描述数量最大是1024,除非修改linux源码重新编译可以加大监听的文件描述。
4、当描述符在select
中被监听时其他的线程不能修改它。假设你有一个管理线程检测到sock1
等待输入数据的时间太长需要关闭它,以便重新利用sock1
来服务其他工作线程。但是它还在select
的监听集合中。如果此时这个套接字被关闭会发生什么?select
的man手册中有解释:如果select
正在监听的套接字被其他线程关闭,结果是未定义的。
5、填充文件描述集合fd_sets时要找出值最大的,这样比较麻烦。