【1】阻塞IO
-
最常用的,最简单、效率最低的;
-
套接字创建后就处于阻塞IO模式
-
read write recv send recvfrom sendto
【2】非阻塞IO
-
防止进程阻塞在IO上,需要轮询
-
当一个程序使用了非阻塞IO模式的时候,那么他需要使用一个循环来不停的判断该文件描述符是否有读取数据,这个步骤称之为polling;
-
程序不停的polling内核,检测IO事件是否发生,cpu消耗率高
1.获取文件描述符属性; 2.在该属性上设置上非阻塞属性; 3.将修改后的属性重新设置回到文件描述符上; fcntl:功能:获取/设置文件描述符的属性; #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ ); int cmd: F_GETFL (void) 获取文件描述符状态标识 F_SETFL (int) 设置文件描述符状态标识; #include <stdio.h> #include <unistd.h> #include <string.h> #include <fcntl.h> int main(int argc, const char *argv[]) { //1.获取文件描述符属性 int flag = fcntl(0, F_GETFL); //2.在该属性上设置上非阻塞属性; //非阻塞属性只是flag上的一个标志位 flag |= O_NONBLOCK; //3.将设置后的文件描述符属性重新设置回到文件描述符中 fcntl(0, F_SETFL, flag); char buf[128] = ""; ssize_t res = 0; while(1) { res = read(0, buf, sizeof(buf)); if(res > 0) { printf("res = %ld %s\n", res, buf); } bzero(buf, sizeof(buf)); sleep(1); } return 0; }
【3】信号驱动IO
-
异步通信方式,采用SIGIO信号
-
信号驱动IO是指预选告知内核,使得某个文件描述符上发送IO事件的时候,内核会通知相关的进程
-
对于TCP而言,信号驱动IO的信号产生过于频繁,而且不能区分是哪个文件描述符发生的。
【4】IO多路复用
1. 概念
-
允许同时多个IO进行操作,内核一旦发现进程执行一个或多个IO事件,就会通知该进程。
-
应用程序中同时需要处理多路输入输出流
2. select
功能:阻塞等待文件描述符准备就绪,如果有文件描述符准备就绪,则当前函数立即解除阻塞; 头文件: /* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> 原型: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 参数: int nfds:三个集合中最大文件描述符+1; fd_set *readfds, fd_set *writefds,fd_set *exceptfds:指定让内核去检测(读集合,写集合,其他)所有文件描述符集合; 如果不想用某个集合,则参数填NULL; struct timeval *timeout:设置超时时间;如果不想设置超时时间,可以写NULL,则该函数会一直阻塞,直到有文件描述符准备就绪; 返回值: >0,成功触发事件的文件描述符个数; =0,超时; =-1,函数调用失败; 操控集合: void FD_CLR(int fd, fd_set *set); //将fd从集合中删除 int FD_ISSET(int fd, fd_set *set); //判断fd是否在集合中。如果存在返回1,如果不存在返回0; void FD_SET(int fd, fd_set *set); //将文件描述符加入到集合中 void FD_ZERO(fd_set *set); //清空集合 注意:select会让内核监控集合,当集合中又文件描述符产生事件,则该集合中只会保留触发事件的文件描述符; 例如:集合中有文件描述符0,3,4,5.当3号文件描述符产生事件,则集合中只剩下3号
3. select服务器
sfd = socket(); bind(); listen(); //创建集合 fd_set readfds,tempfds; FD_ZERO(); //将相关的文件描述符添加到集合中 FD_SET(0, &readfds); FD_SET(sfd, &readfds); maxfd = sfd; while(1) { select(); for(i=0; i<=maxfd; i++) { //判断是什么事件被触发了 //1.终端输入事件 //2.客户端连接事件 { accept(); 添加newfd到readfds集合中 更新maxfd; } //3.客户端交互事件 { recv(); 对方关闭,需要将文件描述符从集合中剔除 更新maxfd; } } }
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #include <stdlib.h> #define ERR_MSG(msg) do{\ printf("line = %d\n", __LINE__);\ perror(msg); \ }while(0) #define PORT 6666 #define IP "192.168.1.12" //ifconfig查找到本机IP int updateMaxfd(int maxfd, fd_set readfds); int main(int argc, const char *argv[]) { //1.创建字节流式套接字 int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd < 0) { ERR_MSG("socket"); return -1; } //允许端口快速重用 int reuse = 1; if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0) { ERR_MSG("setsockopt"); return -1; } //绑定IP和端口 struct sockaddr_in sin; sin.sin_family = AF_INET; //地址族 sin.sin_port = htons(PORT); //端口号的网络字节序 sin.sin_addr.s_addr = inet_addr(IP); //IP地址的网络字节序 if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0) { ERR_MSG("bind"); return -1; } printf("绑定成功\n"); //listen 设置被动监听状态 if(listen(sfd, 10) < 0) { ERR_MSG("listen"); return -1; } printf("listen success\n"); //设置读集合, fd_set readfds, tempfds; //将readfds清空 FD_ZERO(&readfds); FD_ZERO(&tempfds); //将0 sfd添加到读集合中 FD_SET(0, &readfds); FD_SET(sfd, &readfds); int maxfd = sfd; struct sockaddr_in cin; socklen_t addrlen = sizeof(cin); int ret = 0; char buf[128] = ""; int newfd; ssize_t res = 0; int i = 0; int sendfd = 0; //由于文件描述符总量是1024,其中0,1,2,3不会与客户端进行交互 //所以只要定义1024-4个容量即可; struct sockaddr_in *pcin = (struct sockaddr_in*) malloc(sizeof(struct sockaddr_in)*(1024-4)); while(1) { tempfds = readfds; ret = select(maxfd+1, &tempfds, NULL, NULL, NULL); if(ret < 0) { ERR_MSG("select"); return -1; } else if(0 == ret) { printf("超时了....\n"); return -1; } //当集合中有文件描述符准备就绪后, //集合中就会只剩下准备就绪的文件描述符 //所以select解除阻塞后,只需要判断集合中剩下哪个文件描述符即可 for(i=0; i<=maxfd; i++) { if(FD_ISSET(i, &tempfds) == 0) { continue; } //如果0==i,则说明0号文件描述符在tempfds中 //即有终端输入事件 if(0 == i) { printf("触发终端输入事件\n"); bzero(buf, sizeof(buf)); ret = scanf("%d %s", &sendfd, buf); while(getchar() != 10); if(ret != 2) { fprintf(stderr, "输入错误:请按照以下格式输入 fd string\n"); continue; } //判断输入的sendfd是否在集合中,即判断是否有这个客户端存在 if(!FD_ISSET(sendfd, &readfds)) { fprintf(stderr, "%d 不在readfds集合中\n", sendfd); continue; } if(send(sendfd, buf, sizeof(buf), 0) < 0) { ERR_MSG("send"); continue; } printf("发送成功\n"); } //如果sfd==i,则说明sfd文件描述符在tempfds中 //即有客户端连接事件 else if(sfd == i) { printf("触发客户端连接事件\n"); //accept newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen); if(newfd < 0) { ERR_MSG("accept"); return -1; } printf("[%s:%d] newfd = %d\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port) ,newfd); //当newfd==4,则将cin存储在pcin[0] //当newfd==5,则将cin存储在pcin[1] //当newfd==n,则将cin存储在pcin[n-4] pcin[newfd-4] = cin; //需要将newfd添加到读集合中 FD_SET(newfd, &readfds); //更新maxfd maxfd = maxfd>newfd?maxfd:newfd; } //剩下的文件描述符都是客户端交互使用的文件描述符(newfd) else { printf("触发客户端交互事件\n"); bzero(buf, sizeof(buf)); res = recv(i, buf, sizeof(buf), 0); if(res < 0) { ERR_MSG("recv"); return -1; } else if(0 == res) { printf("fd=%d 对方关闭\n", i); //将文件描述符从readfds集合中剔除 FD_CLR(i, &readfds); //更新maxfd maxfd = updateMaxfd(maxfd, readfds); if(maxfd < 0) { printf("集合为空\n"); return -1; } //关闭文件描述符 close(i); } else { //printf("fd=%d : %s\n", i, buf); printf("[%s:%d] newfd = %d: %s\n", \ inet_ntoa(pcin[i-4].sin_addr),ntohs(pcin[i-4].sin_port) ,i, buf); } } } } free(pcin); pcin = NULL; close(sfd); return 0; } int updateMaxfd(int maxfd, fd_set readfds) { int i = maxfd; //从最大的文件描述符开始判断,是否在集合中 //如果在,则该文件描述就是最大的 for(;i>=0; i--) { if(FD_ISSET(i, &readfds)) { return i; } } return -1; }
4. select客户端
5. poll
头文件: #include <poll.h> 原型: int poll(struct pollfd *fds, nfds_t nfds, int timeout); 参数: struct pollfd *fds:指定要监控的文件描述符集合; struct pollfd { int fd; /* file descriptor */ 要监控的文件描述符 short events; /* requested events */ 要监控的事件 short revents; /* returned events */ 实际产生的事件 }; 事件: POLLIN 是否有数据可读 POLLOUT 是否有数据输出; POLLERR 错误事件; nfds_t nfds:指定要监控的文件描述符集合个数; int timeout:超时时间; >0, 设置超时时间,ms为单位; =0, 不阻塞,立即返回; <0, 一直阻塞; 返回值: 成功,返回触发事件的文件描述符个数; =0;代表超时; -1; 函数调用失败,更新errno;
6. poll客户端
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <unistd.h> #include <poll.h> #define ERR_MSG(msg) do{\ printf("line = %d\n", __LINE__);\ perror(msg); \ }while(0) #define PORT 6666 #define IP "192.168.1.12" //ifconfig查找到本机IP int main(int argc, const char *argv[]) { //1.创建字节流式套接字 int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd < 0) { ERR_MSG("socket"); return -1; } //绑定客户端的IP和端口 非必须绑定 //填充服务器的地址信息结构体 struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(PORT); //服务器的PORT sin.sin_addr.s_addr = inet_addr(IP); //服务器的IP //连接服务器 if(connect(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0) { ERR_MSG("connect"); return -1; } //创建文件描述符集合; struct pollfd fds[2]; //监听0号文件描述符的读事件 fds[0].fd = 0; fds[0].events = POLLIN; //监听sfd文件描述符的读事件 fds[1].fd = sfd; fds[1].events = POLLIN; char buf[128] = ""; ssize_t res = 0; int ret = 0; while(1) { ret = poll(fds, 2, -1); if(ret < 0) { ERR_MSG("poll"); return -1; } else if(0 == ret) { printf("超时了....\n"); continue; } //通过按位与的方式判断revents中是否右POLLIN事件 if( 0 != (fds[0].revents & POLLIN)) { printf("有终端输入事件产生\n"); bzero(buf, sizeof(buf)); fgets(buf, sizeof(buf), stdin); buf[strlen(buf)-1] = 0; if(send(sfd, buf, sizeof(buf), 0) < 0) { ERR_MSG("send"); return -1; } } //通过按位与的方式判断revents中是否右POLLIN事件 if(fds[1].revents & POLLIN) { printf("服务器交互事件\n"); bzero(buf, sizeof(buf)); res = recv(sfd, buf, sizeof(buf), 0); if(res < 0) { ERR_MSG("recv"); return -1; } else if(0 == res) { printf("服务器关闭\n"); break; } printf(":%s\n", buf); } } close(sfd); return 0; }