在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 模式。
很多讀寫函數在調用過程中會發(fā)生阻塞。
讀操作中的read、recv、recvfrom
寫操作中的write、send
其他操作:accept、connect
非阻塞I/O
當我們將一個套接字設置為非阻塞模式,我們相當于告訴了系統(tǒng)內核:“當我請求的I/O 操作不能夠馬上完成,你想讓我的進程進行休眠等待的時候,不要這么做,請馬上返回一個錯誤給我?!?br />當一個應用程序使用了非阻塞模式的套接字,它需要使用一個循環(huán)來不停地測試是否一個文件描述符有數據可讀(稱做polling)。
應用程序不停的polling 內核來檢查是否I/O操作已經就緒。這將是一個極浪費CPU 資源的操作。
這種模式使用中不普遍。
非阻塞I/O實現:
fcntl()函數
當你一開始建立一個套接字描述符的時候,系統(tǒng)內核將其設置為阻塞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
所有監(jiān)控的文件描述符中大的那一個加1
read_fds
所有要讀的文件文件描述符的集合
write_fds
所有要的寫文件文件描述符的集合
except_fds
其他要向我們通知的文件描述符 (異常集合)
timeout
超時設置.
Null:一直阻塞,直到有文件描述符就緒或出錯
時間值為0:僅僅檢測文件描述符集的狀態(tài),然后立即返回
時間值不為0:在指定時間內,如果沒有事件發(fā)生,則超時返回
宏的形式:
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去監(jiān)控集合中的狀態(tài),當有數據時,退出select阻塞。
然后,依次判斷那個文件描述符是否有效,然后處理,若是客戶退出,就用FD_CLR清掉FD,并更新maxfd
如果,accept了一個新描述符,就加入到集合中,并更新maxfd。
最后,回到開始,繼續(xù)循環(huán)。
練習:
基于tcp模型的IO多路復用(select)程序,在服務器端采用select來實現客戶端的多路并發(fā)
代碼如下:
客戶端代碼:
#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.創(chuàng)建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); //網絡字節(jié)序端口號
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)){//服務器給發(fā)過來數據
//讀取套接字數據
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創(chuàng)建,并由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;
}
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧