我们知道用socket进行通信时,发送数据和接收数据所使用的recv/send函数会阻塞进程,只有收到或发送数据后才能返回值,导致是socket通信只能实现服务器和客户端交替收发数据,而使用select可以很好地解决这个问题。
诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
select函数的原型:int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout); 关于select函数的用法和作用,大家看看这个帖子
http://blog.csdn.net/piaojun_pj/article/details/5991968/
这个帖子已经讲的很详细了,大家可以看一看,关于select如何解决socket的异步通信问题,个人的理解:select的作用在于,以轮询的方式 同时对“自己有无发送数据” “对方有无发送数据”同时进行监控。而每次检测的时间限制为 struct timeval * timeout 中自己设置的时间,在检测的这段时间内,进程/线程会阻塞。这样就可以不断地交替对“ 是否发送了数据”和“是否接收到数据”进行不断交替检测,这样,进程就不会被recv或是send阻塞,从而实现了异步通信。
代码如下:
服务器 server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 7000
#define QUEUE 20
int main() {
fd_set rfds;
struct timeval tv;
int retval, maxfd;
int listenfd= socket(AF_INET, SOCK_STREAM, 0);
//设置地址可重复方式
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listenfd, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1) {
perror("bind");
exit(1);
}
if(listen(listenfd, QUEUE) == -1) {
perror("listen");
exit(1);
}
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
int fd = accept(listenfd, (struct sockaddr*)&client_addr, &length);
if( fd < 0 ) {
perror("connect");
exit(1);
}
printf("You got connected from:%s\n",inet_ntoa(client_addr.sin_addr));
while(1) {
/*把可读文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把 标准输入 的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把当前连接的文件描述符加入到集合中*/
FD_SET(fd, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < fd)
maxfd = fd;
/*设置超时时间*/
tv.tv_sec = 5;
tv.tv_usec = 0;
/*等待聊天*/
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval == -1){
printf("select出错,客户端程序退出\n");
break;
}else if(retval == 0){
continue;
}else{
/*客户端发来了消息*/
if(FD_ISSET(fd,&rfds)){
char buffer[1024];
memset(buffer, 0 ,sizeof(buffer));
int len = recv(fd, buffer, sizeof(buffer), 0);
if(strcmp(buffer, "exit\n") == 0) exit(0);
printf("From client:%s", buffer);
}
/*用户输入信息了,开始处理信息并发送*/
if(FD_ISSET(0, &rfds)){
char buf[1024];
fgets(buf, sizeof(buf), stdin);
send(fd, buf, sizeof(buf), 0);
if(strcmp(buf, "exit\n") == 0) exit(0);
}
}
}
close(fd);
close(listenfd);
return 0;
}
客户端 client.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 7000
#define BUFFER_SIZE 1024
int main()
{
int fd;
fd_set rfds;
struct timeval tv;
int retval, maxfd;
fd = socket(AF_INET,SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
while(1){
/*把可读文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把标准输入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把当前连接的文件描述符加入到集合中*/
FD_SET(fd, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < fd)
maxfd = fd;
/*设置超时时间*/
tv.tv_sec = 5;
tv.tv_usec = 0;
/*等待聊天*/
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval == -1){
printf("select出错,客户端程序退出\n");
break;
}else if(retval == 0){
continue;
}else{
/*服务器发来了消息*/
if(FD_ISSET(fd,&rfds)){
char recvbuf[BUFFER_SIZE];
int len;
len = recv(fd, recvbuf, sizeof(recvbuf),0);
printf("From server:%s", recvbuf);
if(strcmp(recvbuf,"exit\n")==0) exit(0);
memset(recvbuf, 0, sizeof(recvbuf));
}
/*用户输入信息了,开始处理信息并发送*/
if(FD_ISSET(0, &rfds)){
char sendbuf[BUFFER_SIZE];
fgets(sendbuf, sizeof(sendbuf), stdin);
send(fd, sendbuf, strlen(sendbuf),0);
if(strcmp(sendbuf,"exit\n")==0) exit(0);
memset(sendbuf, 0, sizeof(sendbuf));
}
}
}
close(fd);
return 0;
}
个人理解:以客户端为例,在上述代码中,若FD_ISSET(fd,&rfds)不为0,则表示接收到了数据,这是因为socket的数据传输是通过读写文件描述符来实现的,所以服务器发送数据时,select可以检测到文件描述符已被读写,于是接收数据。而select中0 1 2分别表示标准输入,标准输出,标准错误。符若FD_ISSET(0,&rfds)不为0,则表示用户已经输入了数据,于是发送数据。