扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:最常用
非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:允许同时对多个I/O进行控制
信号驱动I/O:一种异步通信模型
阻塞I/O
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。
很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect
非阻塞I/O
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
这种模式使用中不普遍。
非阻塞I/O实现:
fcntl()函数
当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式。
可以使用函数fcntl()设置一个套接字的标志为O_NONBLOCK 来实现非阻塞。
代码实现;
1.fcntl( )函数int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);
2.ioctl() 函数
int b_on =1;
ioctl(sock_fd, FIONBIO, &b_on);
多路复用I/O
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用。其基本思想是:
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
select()参数
maxfd
所有监控的文件描述符中大的那一个加1
read_fds
所有要读的文件文件描述符的集合
write_fds
所有要的写文件文件描述符的集合
except_fds
其他要向我们通知的文件描述符 (异常集合)
timeout
超时设置.
Null:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回
宏的形式:
void FD_ZERO(fd_set *fdset) //集合清零
void FD_SET(int fd,fd_set *fdset) //把fd加入集合
void FD_CLR(int fd,fd_set *fdset) //把fd删除
int FD_ISSET(int fd,fd_set *fdset) //判断fd是否在集合中。
select使用步骤:
首先,建立相关读集合、写集合或异常集合,集合清零。
其次,把关心的集合加入到相关集合中,并更新maxfd
接着,调用select去监控集合中的状态,当有数据时,退出select阻塞。
然后,依次判断那个文件描述符是否有效,然后处理,若是客户退出,就用FD_CLR清掉FD,并更新maxfd
如果,accept了一个新描述符,就加入到集合中,并更新maxfd。
最后,回到开始,继续循环。
练习:
基于tcp模型的IO多路复用(select)程序,在服务器端采用select来实现客户端的多路并发
代码如下:
客户端代码:
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include#include#include#include#include#include#include#include#include#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.203.130"
#define BACKLOG 5
#define QUIT_STR "quit"
#include#define SERV_RESP_STR "Server:"
#include#endif
void usage(char *s){printf("\n%s serv_ip serv_port\n",s);
printf("\n\t serv_ip:server ip address");
printf("\n\t serv_port:server port(>5000)\n\n");
}
int main(int argc, char *argv[])
{int fd = -1;
int port;
if(argc != 3){usage(argv[0]);
exit(1);
}
struct sockaddr_in sin;
//1.创建socket fd
if((fd = socket(AF_INET,SOCK_STREAM,0))< 0){perror("socket");
exit(1);
}
port = atoi(argv[2]);
if(port< 5000){usage(argv[0]);
exit(1);
}
//2.连接服务器
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port); //网络字节序端口号
if(inet_pton(AF_INET,argv[1],(void *)&sin.sin_addr)!=1){perror("inet_pton");
exit(1);
}
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))< 0){perror("connect");
exit(1);
}
fd_set rset;
int maxfd = -1;
int ret = -1;
struct timeval tout;
char buf[BUFSIZ];
while(1){FD_ZERO(&rset);
FD_SET(0,&rset);
FD_SET(fd,&rset);
maxfd = fd;
tout.tv_sec = 5;
tout.tv_usec = 0;
select(maxfd+1,&rset,NULL,NULL,&tout);
if(FD_ISSET(0,&rset)){//标准键盘上有输入
//读取键盘输入
bzero(buf,BUFSIZ);
do{ ret = read(0,(void *)buf,BUFSIZ-1);
}while((ret< 0) && (EINTR == errno));
if(ret< 0){ perror("read");
continue;
}
if(ret == 0){ continue;
}
if(write(fd,buf,strlen(buf))< 0){ perror("write () to socket");
continue;
}
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){ printf("client is exiting!\n");
break;
}
}
if(FD_ISSET(fd,&rset)){//服务器给发过来数据
//读取套接字数据
bzero(buf,BUFSIZ);
do{ ret = read(fd,(void *)buf,BUFSIZ-1);
}while((ret< 0) && (EINTR == errno));
if(ret< 0){ perror("read from socket");
continue;
}
if(ret == 0){//服务器关闭
break;
}
printf("server said:%s\n",buf);
//there is a bug FIXME
if((strlen(buf) >strlen(SERV_RESP_STR))
&& (!strncasecmp(buf+strlen(SERV_RESP_STR),QUIT_STR,strlen(QUIT_STR)))){ printf("server client is exiting!\n");
break;
}
}
}
close(fd);
return 0;
}
服务器代码:
#ifndef __MAKEU_NET_H__
#define __MAKEU_NET_H__
#include#include#include#include#include#include#include#include#include#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.203.130"
#define BACKLOG 5
#define QUIT_STR "quit"
#include#define SERV_RESP_STR "Server:"
#include#endif
#include "net.h"
void * cli_data_handle(void * arg);
int creatFd();
int main(int argc, char *argv[])
{fd_set rset,clientfds;
int maxfd = -1;
int i;
int fd,newfd;
char buf[BUFSIZ];
struct timeval tout;
int ret = 0;
fd = creatFd();
maxfd = fd+1;
FD_ZERO(&clientfds);
while(1){FD_ZERO(&rset);
FD_SET(fd,&rset);
i = fd;
while(++i< maxfd){ if(FD_ISSET(i,&clientfds)){ FD_SET(i,&rset);
}
}
tout.tv_sec = 5;
tout.tv_usec = 0;
ret = select(maxfd,&rset,NULL,NULL,&tout);
i = fd;
while(++i< maxfd){ if(FD_ISSET(i,&rset)){ bzero(buf,BUFSIZ);
read(i,buf,BUFSIZ);
printf("buf=%s\n",buf);
if(!strncasecmp(buf,"quit",4)){close(i);
FD_CLR(i,&clientfds);
printf("\tclientfd %d exit\n",i);
}
}
}
if(FD_ISSET(fd,&rset)){ struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen))< 0){perror("accept");exit(1);}
FD_SET(newfd,&clientfds);
maxfd = (maxfd >newfd) ? maxfd : newfd+1;
}
}
return 0;
}
int creatFd(){//封装,fd(由socket创建,并由bind绑定,listen倾听的fd)
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd< 0){perror("socket");exit(1);}
struct sockaddr_in sin;
socklen_t addrlen = sizeof(sin);
memset(&sin,0,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(fd,(struct sockaddr *)&sin,addrlen)< 0){perror("bind");exit(1);}
if(listen(fd,BACKLOG)< 0){perror("listen");exit(1);}
return fd;
}
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流