select函数详细用法解析 http://blog.chinaunix.net/uid-21411227-id-1826874.html
linux网络编程之socket(九):使用select函数改进客户端/服务器端程序
文件描述符有三种状态 可读、可写、异常
在网络编程中,以下情况socket可读
1、socket内核接收缓冲区的字节数大于其最低标记SO_RCVLOWAT,此时可以无阻塞的读该socket并且返回的字节数大于0;
2、socket通信的对方关闭,此时对该socket的读操作将返回0;
3、监听socket 上有新的连接请求;
4、socket上有未处理的错误,此时可以使用getsockopt来读取和清除该错误
在以下情况下可写
1、socket内核发送缓冲区的字节数大于其最低标记SO_RCVLOWAT,此时可以无阻塞的写该socket并且返回的字节数大于0;
2、socket的写操作被关闭,对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号;
3、socket使用非阻塞connect成功或失败(超时)之后;
4、socket上有未处理的错误,此时可以使用getsockopt来读取和清除该错误
select能处理的异常只有一种:socket上接收到带外数据
socket上接收到 普通数据和带外数据 之后都将使select返回,但socket处于不同的就绪状态,前者处于 可读状态 而后者处于 异常状态。
可以通过判断socket是在select返回的可读描述符还是异常描述符集中来判断是普通数据还是带外数据。部分程序如下
.............
memset(recvbuf,'',sizeof(recvbuf)); /*每次调用select之前都要重新read_fd和exception_fd中设置文件描述符sock_fd,因为事件发生之后,文件描述符会被修改*/ FD_SET(sock_fd,&read_fd); FD_SET(sock_fd,&exception_fd);
nready=select(sock_fd+1,&read_fd,NULL,&exception,NULL);
if(nready==-1)
printf("select error ");
/*对于可读事件,采用recv函数读取数据*/
if(FD_ISSET(sock_fd,&read_fd))
{
ret=recv(sock_fd,recvbuf,sizeof(recvbuf)-1,0);
............
}
/*对于异常事件,采用带MSG_OOB标志recv的函数读取数据*/
else if(FD_ISSET(sock_fd,&exception_fd))
{
ret=recv(sock_fd,recvbuf,sizeof(recvbuf)-1,MSG_OOB);
............
}
..........
使用select函数实现并发回射服务器
服务器端
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/un.h> #include<sys/wait.h> //*进程用的头文件*/ #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> #include<sys/select.h> // #include<sys/time.h> #define MAXLINE 1024 //通信内容的最大长度 #ifndef FD_SETSIZE #define FD_SETSIZE 25 //select最多能处理的文件描述符 #endif ssize_t readn(int fd, void *buf, size_t count) { ssize_t nleft=count; ssize_t nread; char *charbuf=(char*) buf; while(nleft>0) { nread=read(fd,charbuf,nleft); if(nread<0) { if(errno==EINTR) continue; return -1; } else if(nread==0) return count-nleft; charbuf +=nread; nleft=count-nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { ssize_t nleft=count; ssize_t nwrite; char *charbuf=(char*) buf; while(nleft>0) { nwrite=write(fd,charbuf,nleft); if(nwrite<0) { if(errno==EINTR) continue; return -1; } else if(nwrite==0) return count-nleft; charbuf +=nwrite; nleft=count-nwrite; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { int ret; while(1) { ret=recv(sockfd,buf,len,MSG_PEEK); if(ret==-1&& errno==EINTR) continue; return ret; } } ssize_t readline(int sockfd, void *buf, size_t len) { ssize_t nleft=len,nread; int ret; char* bufchar=buf; while(1) { ret=recv_peek(sockfd,bufchar,len); if(ret<0||ret==0) return ret; nread=ret; int i; for(i=0;i<nread;i++) { if(bufchar[i]==' ') { ret=readn(sockfd,bufchar,i+1); if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufchar,nread); if(ret!=nread) exit(EXIT_FAILURE); bufchar+=nread; } return -1; } int main() { int sock_fd,new_fd,fd;//sock_fd用于监听,new_fd用于连接 int maxi,maxfd; int client[FD_SETSIZE];//用于存放客户端描述符 int nready;//检测到的事件数 struct sockaddr_in srv_addr;//服务器的地址信息 struct sockaddr_in client_addr;//客户机的地址信息 int i,size; //地址结构数据的长度 ssize_t n; fd_set rset,allset; char sendbuf[1024],recvbuf[1024]; memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); /*创建套接字*/ sock_fd=socket(AF_INET,SOCK_STREAM,0);//采用IPv4协议 if(sock_fd==-1) { perror("creat socket failed"); exit(1); } /*服务器地址参数*/ srv_addr.sin_family=AF_INET; srv_addr.sin_port=htons(3490); srv_addr.sin_addr.s_addr=htonl(INADDR_ANY); bzero(&srv_addr.sin_zero,sizeof(struct sockaddr_in));//bzero位清零函数,将sin_zero清零,sin_zero为填充字段,必须全部为零 int on=1; //表示开启reuseaddr if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) //打开地址、端口重用 perror("setsockopt"); /*绑定地址和端口*/ if(bind(sock_fd,(struct sockaddr*)&srv_addr,sizeof(struct sockaddr))==-1) { perror("bind failed"); exit(1); } /*设置监听模式,等待客户机的监听*/ if((listen(sock_fd,5))==-1) { perror("listen failed"); exit(1); } maxfd=sock_fd; maxi=-1; for(i=0;i<FD_SETSIZE;i++) client[i]=-1; //描述符为-1表示空闲 FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(sock_fd,&allset); //使用select实现并发服务器 while(1) { rset=allset; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) { if(errno==EINTR) continue; ERR_EXIT("select"); } if(FD_ISSET(sock_fd,&rset)) //监听套接口不在阻塞 { size=sizeof(struct sockaddr_in); new_fd=accept(sock_fd,(struct sockaddr*)&client_addr,&size); /*接受连接,采用非阻塞是的模式调用accep*/ if(new_fd==-1) { perror("accept failed"); //continue;//restart accept when EINTR } printf("server:got connection from IP= %s prot= %d ",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//连接成功,打印客户机IP地址和端口号 /*char *inet_nota(struct sockaddr_in in); 头文件: arpa/inet.h Winsock2.h 参数: 一个网络上的IP地址 返回值: 如果正确,返回一个字符指针,指向一块存储着点分格式IP地址的静态缓冲区(同一线程内共享此内存);错误,返回NULL。 uint31_t ntohs(uint32_t net32bitvalue); 头文件: #include<netinet/in.h> 把net32bitvalue有网络字节序转换为主机字节序。 */ if(send(new_fd,"Hello client,I am 192.168.229.125! ",50,0)==-1) //192.168.229.125为子进程IP,可更改 perror("send failed"); for(i=0;i<FD_SETSIZE;i++) if(client[i]<0) { client[i]=new_fd; //将描述符保存在某一个空闲的位置 break; } if(i==FD_SETSIZE) //没有找到空闲的位置,即描述符个数达到上限 perror("too many client"); FD_SET(new_fd,&allset); if(new_fd>maxfd) //更新最大描述符 maxfd=new_fd; if(i>maxi) maxi=i; if(--nready<=0) //若检测到的套接口已经处理完,则继续用select监听 continue; } for(i=0;i<=maxi;i++) { if((fd=client[i])<0) continue; if(FD_ISSET(fd,&rset)) { if((n=readline(fd,recvbuf,MAXLINE))==0) { printf("client closed "); close(fd); FD_CLR(fd,&allset); //客户端关闭,将其清除 client[i]=-1; } else if(n==-1) perror("readline "); else { writen(fd,recvbuf,n); fputs(stdout,recvbuf,n); } if(--nready<=0) break; } } } }
客户端
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/un.h> #include<sys/wait.h> //*进程用的头文件*/ #include<netinet/in.h> #include<arpa/inet.h> #define MAXBYTEMUN 1024 ssize_t readn(int fd, void *buf, size_t count) { ssize_t nleft=count; ssize_t nread; char *charbuf=(char*) buf; while(nleft>0) { nread=read(fd,charbuf,nleft); if(nread<0) { if(errno==EINTR) continue; return -1; } else if(nread==0) return count-nleft; charbuf +=nread; nleft=count-nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { ssize_t nleft=count; ssize_t nwrite; char *charbuf=(char*) buf; while(nleft>0) { nwrite=write(fd,charbuf,nleft); if(nwrite<0) { if(errno==EINTR) continue; return -1; } else if(nwrite==0) return count-nleft; charbuf +=nwrite; nleft=count-nwrite; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { int ret; while(1) { ret=recv(sockfd,buf,len,MSG_PEEK); if(ret==-1&& errno==EINTR) continue; return ret; } } ssize_t readline(int sockfd, void *buf, size_t len) { ssize_t nleft=len,nread; int ret; char* bufchar=buf; while(1) { ret=recv_peek(sockfd,bufchar,len); if(ret<0||ret==0) return ret; nread=ret; int i; for(i=0;i<nread;i++) { if(bufchar[i]==' ') { ret=readn(sockfd,bufchar,i+1); if(ret!=i+1) exit(EXIT_FAILURE); return ret; } } if(nread>nleft) exit(EXIT_FAILURE); nleft-=nread; ret=readn(sockfd,bufchar,nread); if(ret!=nread) exit(EXIT_FAILURE); bufchar+=nread; } return -1; } int main(int argc,char *argv[]) { int sock_fd,numbytes; char buf[MAXBYTEMUN]; struct hostent *he; struct sockaddr_in client_addr;//客户机的地址信息 ssize_t n,ret; char recvbuf[1024]={'0'},sendbuf[1024]={'0'}; if(argc!=2) { fprintf(stderr,"usage: client IPAddress "); //执行客户端程序时,输入客户端程序名称和其IP地址 exit(1); } /*创建套接字*/ sock_fd=socket(AF_INET,SOCK_STREAM,0);//采用IPv4协议 if(sock_fd==-1) { perror("creat socket failed"); exit(1); } /*服务器地址参数*/ client_addr.sin_family=AF_INET; client_addr.sin_port=htons(3490); client_addr.sin_addr.s_addr=inet_addr(argv[1]); bzero(&client_addr.sin_zero,sizeof(struct sockaddr_in));//bzero位清零函数,将sin_zero清零,sin_zero为填充字段,必须全部为零 /*连接到服务器*/ if(connect(sock_fd,(struct sockaddr*)&client_addr,sizeof(struct sockaddr))==-1) { perror("connect failed"); exit(1); } if((numbytes=recv(sock_fd,buf,MAXBYTEMUN,0))==-1) { perror("receive failed"); exit(1); } buf[numbytes]='';//在字符串末尾加上,否则字符串无法输出 printf("Received: %s ",buf); pid_t pid; pid=fork(); if(!pid)//创建新的子进程,用于接收数据 { while(1) { memset(recvbuf,0,sizeof(recvbuf)); ret=readline(sock_fd,recvbuf,1024); if(ret<0) perror("read from server error"); else if(ret==0) { printf("peer closed "); break; } fputs(recvbuf,stdout); } close(sock_fd); } else //f=父进程用于发送数据 { while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { writen(sock_fd,sendbuf,strlen(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); //清空,以免和下一次混淆 } //exit(EXIT_SUCCESS); close(sock_fd); } /*接受数据 if((numbytes=recv(sock_fd,buf,MAXBTYEMUN,0))==-1) { perror("receive failed"); exit(1); } buf[numbytes]='';//在字符串末尾加上,否则字符串无法输出 printf("Received: %s ",buf); close(sock_fd);*/ return 0; }