I/O类型:
接下来我们将介绍几种常见的I/O模型及其区别
阻塞I/O:blocking I/O(如果没有信息,则阻塞)
非阻塞I/O:nonblocking I/O
多路复用I/O:I/O multiplexing (select and poll)
信号I/O:signal driven I/O (SIGIO)
异步I/O:asynchronous I/O (the POSIX aio_functions)
I/O多路复用:I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个I/O请求。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源。
API:select 、poll、epoll的解决方案
针对如下场合就需要用到IO复用:
当客户处理多个描述符时候
一个同时处理多个套接字的时候
如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字的时候
如果一个服务器既要处理TCP又要处理UDP
如果一个服务器要处理多个服务或者多个协议的时候
总结:如果一个线程中有多路径阻塞I/O时,就可以用多路复用
Select:该函数会等待多个I/O事件(比如读就绪,写)的任何一个发生,并且只要有一个网络事件发生,select线程就会执行。如果没有任何一个事件发生则阻塞。原型如下:
int select ( int nfds , fd_set *readfds , fd_set *writefds , fd_set *exceptfds , struct timeval *timeout ) ;
nfds:最大的监哨的文件描述符
readfs:读的文件描述符
writes 写的文件描述符
exceptfds错误输出文件描述符
timeout:在指定时间内
struct timeout
{
long misec;
long sec;
}
特点:如果监哨的文件描述符没有状态改变(没有读写改变),则会阻塞。否则会唤醒并通知。每监哨一次时,重新设置文件描述符集;返回时必须判断原因 返回值:-1 失败 errno错误的状态
1、创建集合:
fd_set set
2、添加描述符到集合
FD_SET
从文件描述符集中删除描述符
FD_CLR
判断文件描述符中是否发生改变:
FD_ISSET
某文件描述符状态发生改变>0 失败==0
清空描述符集:
FD_ZERO
多路I/O 的 服务器实现 :
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
//UDP--服务器
//4关闭套接字
int main()
{
//1创建套接字
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("socket fail");
return -1;
}
//2绑定套接字
struct sockaddr_in myaddr;
myaddr.sin_family =AF_INET;
myaddr.sin_port =htons(7979);
myaddr.sin_addr.s_addr =INADDR_ANY;
if(bind(sock,(struct sockaddr*)&myaddr,sizeof(myaddr))<0)
{
perror("socket fail");
return -1;
}
//3收发消息
int ilen=0;
char buf[100]="";
struct sockaddr_in caddr;
socklen_t addrlen=sizeof(caddr);
while(1)
{
ilen=recvfrom(sock,buf,99,0,(struct sockaddr*)&caddr,&addrlen);
if(ilen<=0)
break;
buf[ilen]='\0';
//输出
printf("%s\n",buf);
//返回消息
sendto(sock,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
}
//关闭
close(sock);
return 0;
}
主要是在客户端的代码 , 使用select 函数实现 :
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/select.h>
#include<errno.h>
int main()
{
//创建套接字
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0)
{
perror("fail\n");
return -1;
}
//绑定
//发送
//创建文件描述符集
fd_set rset;
FD_ZERO(&rset);
int maxfd=STDIN_FILENO>sock?STDIN_FILENO:sock; //键盘的描述符 0
//地址结构体:服务器
struct sockaddr_in saddr;
bzero(&saddr,sizeof(saddr)); //清空
saddr.sin_family =AF_INET;
saddr.sin_port =htons(7979);
saddr.sin_addr.s_addr =inet_addr("192.168.8.138");
char buf[100]="";
int ilen=0;
while(1)
{
//设置文件描述符集:从键盘上读取 从套接字读取
FD_SET(STDIN_FILENO,&rset);
FD_SET(sock,&rset);
//多路I/O复用
if(-1==select(maxfd+1,&rset,NULL,NULL,NULL)&&errno!=EINVAL)
{
perror("select fail");
break;
}
if(FD_ISSET(STDIN_FILENO,&rset)>0) //从键盘上读取
{
read(STDIN_FILENO,buf,99);
//发送
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&saddr,sizeof(saddr));
}
if(FD_ISSET(sock,&rset)>0) //从套接字读取
{
//读取
ilen=recv(sock,buf,99,0);
if(ilen<=0)
break;
buf[ilen]='\0';
printf("收到:%s\n",buf);
}
}
//关闭
close(sock);
return 0;
}