IO复用与select函数
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,'\0',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\n");
/*对于可读事件,采用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>)
{
nread=read(fd,charbuf,nleft);
if(nread<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nread==)
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>)
{
nwrite=write(fd,charbuf,nleft);
if(nwrite<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nwrite==)
return count-nleft;
charbuf +=nwrite;
nleft=count-nwrite; }
return count;
} ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
int ret;
while()
{
ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-&& 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()
{
ret=recv_peek(sockfd,bufchar,len);
if(ret<||ret==)
return ret;
nread=ret;
int i;
for(i=;i<nread;i++)
{
if(bufchar[i]=='\n')
{
ret=readn(sockfd,bufchar,i+);
if(ret!=i+)
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 -; } 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[],recvbuf[];
memset(sendbuf,,sizeof(sendbuf));
memset(recvbuf,,sizeof(recvbuf)); /*创建套接字*/
sock_fd=socket(AF_INET,SOCK_STREAM,);//采用IPv4协议
if(sock_fd==-)
{
perror("creat socket failed");
exit();
} /*服务器地址参数*/
srv_addr.sin_family=AF_INET;
srv_addr.sin_port=htons();
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=; //表示开启reuseaddr
if(setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<) //打开地址、端口重用
perror("setsockopt"); /*绑定地址和端口*/
if(bind(sock_fd,(struct sockaddr*)&srv_addr,sizeof(struct sockaddr))==-)
{
perror("bind failed");
exit();
} /*设置监听模式,等待客户机的监听*/
if((listen(sock_fd,))==-)
{
perror("listen failed");
exit();
} maxfd=sock_fd;
maxi=-;
for(i=;i<FD_SETSIZE;i++)
client[i]=-; //描述符为-1表示空闲 FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(sock_fd,&allset); //使用select实现并发服务器
while()
{
rset=allset;
nready=select(maxfd+,&rset,NULL,NULL,NULL);
if(nready==-)
{
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==-)
{
perror("accept failed");
//continue;//restart accept when EINTR
} printf("server:got connection from IP= %s prot= %d \n",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!\n",,)==-) //192.168.229.125为子进程IP,可更改
perror("send failed");
for(i=;i<FD_SETSIZE;i++)
if(client[i]<)
{
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<=) //若检测到的套接口已经处理完,则继续用select监听
continue;
} for(i=;i<=maxi;i++)
{
if((fd=client[i])<)
continue;
if(FD_ISSET(fd,&rset))
{
if((n=readline(fd,recvbuf,MAXLINE))==)
{
printf("client closed\n");
close(fd);
FD_CLR(fd,&allset); //客户端关闭,将其清除
client[i]=-;
}
else if(n==-)
perror("readline\n");
else
{
writen(fd,recvbuf,n);
fputs(stdout,recvbuf,n);
} if(--nready<=)
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>)
{
nread=read(fd,charbuf,nleft);
if(nread<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nread==)
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>)
{
nwrite=write(fd,charbuf,nleft);
if(nwrite<)
{
if(errno==EINTR)
continue;
return -;
}
else if(nwrite==)
return count-nleft; charbuf +=nwrite;
nleft=count-nwrite;
}
return count;
} ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
int ret;
while()
{
ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-&& 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()
{
ret=recv_peek(sockfd,bufchar,len);
if(ret<||ret==)
return ret;
nread=ret;
int i;
for(i=;i<nread;i++)
{
if(bufchar[i]=='\n')
{
ret=readn(sockfd,bufchar,i+);
if(ret!=i+)
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 -; } 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[]={''},sendbuf[]={''}; if(argc!=)
{
fprintf(stderr,"usage: client IPAddress\n"); //执行客户端程序时,输入客户端程序名称和其IP地址
exit();
} /*创建套接字*/
sock_fd=socket(AF_INET,SOCK_STREAM,);//采用IPv4协议
if(sock_fd==-)
{
perror("creat socket failed");
exit();
} /*服务器地址参数*/
client_addr.sin_family=AF_INET;
client_addr.sin_port=htons();
client_addr.sin_addr.s_addr=inet_addr(argv[]);
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))==-)
{
perror("connect failed");
exit();
}
if((numbytes=recv(sock_fd,buf,MAXBYTEMUN,))==-)
{
perror("receive failed");
exit();
}
buf[numbytes]='\0';//在字符串末尾加上\0,否则字符串无法输出
printf("Received: %s\n",buf); pid_t pid;
pid=fork();
if(!pid)//创建新的子进程,用于接收数据
{
while()
{
memset(recvbuf,,sizeof(recvbuf));
ret=readline(sock_fd,recvbuf,);
if(ret<)
perror("read from server error");
else if(ret==)
{
printf("peer closed\n");
break;
}
fputs(recvbuf,stdout);
}
close(sock_fd);
}
else //f=父进程用于发送数据
{
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
writen(sock_fd,sendbuf,strlen(sendbuf));
memset(sendbuf,,sizeof(sendbuf)); //清空,以免和下一次混淆
}
//exit(EXIT_SUCCESS);
close(sock_fd);
}
/*接受数据
if((numbytes=recv(sock_fd,buf,MAXBTYEMUN,0))==-1)
{
perror("receive failed");
exit(1);
} buf[numbytes]='\0';//在字符串末尾加上\0,否则字符串无法输出
printf("Received: %s\n",buf);
close(sock_fd);*/
return ;
}
IO复用与select函数的更多相关文章
- 并发服务器--02(基于I/O复用——运用Select函数)
I/O模型 Unix/Linux下有5中可用的I/O模型: 阻塞式I/O 非阻塞式I/O I/O复用(select.poll.epoll和pselect) 信号驱动式I/O(SIGIO) 异步I/O( ...
- 【Unix网络编程】chapter6 IO复用:select和poll函数
chapter6 6.1 概述 I/O复用典型使用在下列网络应用场合. (1):当客户处理多个描述符时,必须使用IO复用 (2):一个客户同时处理多个套接字是可能的,不过不叫少见. (3):如果一个T ...
- 第十七篇:IO复用之select实现
前言 在看过前文:初探IO复用后,想必你已对IO复用这个概念有了初步但清晰的认识. 接下来,我要在一个具体的并发客户端中实现它(基于select函数),使得一旦服务器中的客户进程被终止的时候,客户端这 ...
- IO复用之select实现
前言 在看过前文:初探IO复用后,想必你已对IO复用这个概念有了初步但清晰的认识.接下来,我要在一个具体的并发客户端中实现它( 基于select函数 ),使得一旦服务器中的客户进程被终止的时候,客户端 ...
- IO复用: select 和poll 到epoll
linux 提供了select.poll和epoll三种接口来实现多路IO复用.下面总结下这三种接口. select 该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经 ...
- linux的IO复用,select机制理解--ongoing
一:首先需要搞清楚IO复用.阻塞的概念: Ref: https://blog.csdn.net/u010366748/article/details/50944516 二:select机制 作为IO ...
- 多路IO复用模型--select, poll, epoll
select 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 2.解决1024以下客户端时使用se ...
- 网络编程之IO复用:select or epoll
对于服务器的并发处理能力,我们需要的是:每一毫秒服务器都能及时处理这一毫秒内收到的数百个不同TCP连接上的报文,与此同时,可能服务器上还有数以十万计的最近几秒没有收发任何报文的相对不活跃连接.同时处理 ...
- 6. IO复用:select 和 poll
select #include <sys/select.h> #include <sys/time.h> int select(int maxfdp1, fd_set *rea ...
随机推荐
- 表达式拼接Expression<Func<IEntityMapper, bool>> predicate
/// <summary> /// 重写以筛选出当前上下文的实体映射信息 /// </summary> protected override IEnumerable<IE ...
- JS 根据特定URL获取ID数组
工作中遇到的问题 咱是菜鸟 有更好的方法 求大神看到的指点 事情经过主要是后台返回商品ID 和 商品数量 然后做个卖光的遮罩 这样的效果 结果 后台返回的ID 数组不是后台输入的时候的排序 也就是 ...
- JS获取当前对象大小以及屏幕分辨率等...
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <meta nam ...
- JSF的CommandXxx组件的update用法总结
Update all forms 1 update="@(form)" Update first form 1 update="@(form:first)" U ...
- jquery+css3实现3d滚动旋转
最近有个漂亮妹子一直在向我推销潭州的视频,BUT我以前就看了一次觉得挺水的,就对那个妹子表示吾孤高.你们那玩意没意义的很弱.然后那个漂亮妹子锲而不舍的对我发链接,这个效果会吗,这个幻灯会写吗...我也 ...
- EasyUI datagrid优化
easyui datagrid 在IE上加载速度慢, 150行数据就无法忍受了. firefox加载速度还可以. jquery easyui datagrid使用参考 http://www.cnblo ...
- 2015年12月01日 GitHub入门学习(二)手把手教你Git安装
序:Mac与Linux中,Mac都预装了Git,各版本的Linux也都提供了Git的软件包.下面手把手教你Windows下的安装. 一.Git Windows GUI 下载地址 msysgit htt ...
- windows2003最详细的安装操作步骤.(最详细)
以下为windows2003的安装操作步骤,由于安装操作步骤较多,安装可能需要一定的实际安装经验.安装时请参照此文档一步步完成安装. 一.首先准备好Windows2003安装光盘CD1,将CD1光盘放 ...
- HDOJ 4751 Divide Groups
染色判断二分图+补图 比赛的时候题意居然是反的,看了半天样例都看不懂 .... Divide Groups Time Limit: 2000/1000 MS (Java/Others) Memo ...
- MySQL源码分析以及目录结构
原文地址:MySQL源码分析以及目录结构作者:jacky民工 主要模块及数据流经过多年的发展,mysql的主要模块已经稳定,基本不会有大的修改.本文将对MySQL的整体架构及重要目录进行讲述. 源码结 ...