Linux下的select函数的应用
select函数的声明:
#include <sys/select.h>
#include <sys/time.h>
int select (int __nfds, fd_set *__readfds, fd_set *__writefds, fd_set *__exceptfds, struct timeval * __timeout);
其中 struct timeval 的定义如下:
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
typedef long __kernel_time_t;
typedef long __kernel_suseconds_t;
__readfds: 准备好读的描述字集合。
__writefds: 准备好写的描述字集合。
__exceptfds: 有异常的描述字集合。
这些描述字不仅局限于套接口的描述字,包括文件描述符在内的任何描述都可被select接受。
timeval为时延参数,tv_sec表示秒时,tv_usec表示微秒。
1)如__timeout 设置为空指针表示永远等待直到仅有一个描述字准备好I/O时返回,将该指针设置为空和阻塞I/O模型完全一致。
2 ) 如__timeout 设置为非零值的话,则在tv_sec+tv_usec时间内有准备好的描述字都会马上返回。
3) 如__timeout 为0则表示非阻塞的轮询方式(polling)。
接下来通过一个一个实例来说明 __restrict __timeout 参数的取值对select函数阻塞与非阻塞的影响,以下是根据《Unix网络编程》第一卷的函数改写,为了简化起见,去掉了所有的错误处理。
公共文件:my_comm.h
#ifndef __MY_COMM_H__
#define __MY_COMM_H__
#define MY_PORT 9999
#define LISTENQ 10
#define MAXLINE 1024
#define UDP_PORT 2222
#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
服务器端程序:my_server_select.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h>
#include <errno.h>
#include "my_comm.h"
void str_echo(int fd);
int main(int argc, int argv[])
{
int sockfd, clilen, connfd;
pid_t childpid;
struct sockaddr_in serveraddr, cliaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
__bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(MY_PORT);
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr) ))
{
printf("Bind error!\n");
exit(-1);
}
if(-1 == listen(sockfd, LISTENQ))
{
printf("Listen error!\n");
exit(-1);
}
printf("Begin listen...\n");
while(1)
{
clilen = sizeof(cliaddr);
connfd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen);
//printf("Receive the connection from 0x%x !\n",ntohl(cliaddr.sin_addr.s_addr));
if((childpid = fork()) == 0)
{
close(sockfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
}
void str_echo(int fd)
{
ssize_t n;
char buf[MAXLINE];
AGAIN:
while((n = read(fd, buf, MAXLINE)) > 0)
{
printf("Receive the str = %s.\n",buf);
write(fd,buf,n);
}
if(n < 0 && errno == EINTR)
goto AGAIN;
else if( n < 0)
{
printf("str_echo: read error\n");
exit -1;
}
}
客户端程序:my_client_select.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#include "my_comm.h"
void str_cli(FILE *fp, int sockfd, int mode);
int main(int argc, char* argv[])
{
int sockfd;
struct sockaddr_in serveraddr;
if(argc != 3)
{
printf("usage: my_client <IPaddress> [1:block 2:time=10s 3:polling]\n");//1 表示阻塞模式下 2 表示阻塞于10s 3表示轮询方式,既不阻塞
return 0;
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
__bzero(&serveraddr ,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(MY_PORT);
printf("argv[1] = %s! argv[2] = %s\n",argv[1],argv[2]);
if ( -1 == inet_pton(AF_INET, argv[1], (void *)&serveraddr.sin_addr))
{
printf("Inet_pton error!\n");
exit (0);
}
printf("Begin to connect \n");
if(-1 == connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)))
{
printf("Connect error. Address : sockfd = %d 0x%x \n", sockfd,serveraddr.sin_addr.s_addr);
exit(0);
}
str_cli(stdin,sockfd,atoi(argv[2]));
exit(0);
}
void str_cli(FILE *fp, int sockfd, int mode)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE];
FD_ZERO(&rset);
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
while(1)
{
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd)+1;
struct timeval *p_tmp_timeval;
switch(mode)
{
case 1:
p_tmp_timeval = NULL;
break;
case 2:
timeout.tv_sec = 10;
p_tmp_timeval = &timeout;
break;
case 3:
p_tmp_timeval = &timeout;
break;
default:
printf("Mode value = %d error!\n",mode);
return;
}
if(-1 != select(maxfdp1, &rset, NULL, NULL, p_tmp_timeval))
{
printf("Select return!!\n");
}
if(FD_ISSET(sockfd, &rset))
{
read(sockfd, recvline, MAXLINE);
fputs(recvline, stdout);
}
if(FD_ISSET(fileno(fp), &rset))
{
fgets(sendline, MAXLINE, fp);
write(sockfd, sendline, strlen(sendline));
}
}
}
通过gcc编译后选择相关的模式运行程序
1、表示在阻塞状态下运行TCP链接,
./my_client_select 127.0.0.1 1
argv[1] = 127.0.0.1! argv[2] = 1
Begin to connect
hello world
Select return!!
Select return
可以看到只有输入字符串分别触发两个描述字成为可写状态则成功响应服务器端的回显信息。
2、选择模式2下运行
./my_client_select 127.0.0.1 2
argv[1] = 127.0.0.1! argv[2] = 2
Begin to connect
Select return!!
hello worldSelect return!!
Select return!!
Select return!!
hello world
Select return!!
Select return!!
Select return!!
可以通过红色打印部分了解到无论当前描述字是否可写,间隔十秒都会返回一次。
3、选择模式3,轮询的方式运行。
./my_client_select 127.0.0.1 3
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
Select return!!
非阻塞模式下无论描述字是否可写,select都返回,进入while循环的无限打印中。