这次我们将要学习五种I/O模型(阻塞I/O、非阻塞I/O、I/O复用、信号驱动I/O、异步I/O), select函数,以及用select改进回射客户端程序。
1.五种I/O模型
(1)阻塞I/O
1)前面用到的套接口编程都是用的阻塞I/O来进行编程的。
2)套接口完成连接请求之后,就可以向系统提交一个recv请求来接收数据,这个请求是阻塞的。
(2)非阻塞I/O
1)调用recv,并将套接口设置为非阻塞模式,将套接口设置为非阻塞模式,可以使用如下函数:
fcntl(fd, F_SETFL, flag|O_NONBLOCK);
2)这个时候,recv函数,即使没有数据到来,也不会阻塞。
3)该模型在数据没有到来时,需要循环接收。占用CPU时间片,该模型不推荐使用。
(3)I/O复用
1)该模型主要通过select来实现的,思想是通过select来管理多个文件描述符。一旦检测到数据到来,select就返回,再调用recv函数就不会阻塞了。
2)然后将数据从内核空间拷贝到用户空间。一旦拷贝完成,就返回。
(4)信号驱动I/O
1)这种模型也不常用。
(5)异步I/O
1)这种I/O的效率最高,通过aio_read函数实现。该函数提交一个请求并会递交了一个应用层缓冲区buf。即使内核中没有数据到来,该函数也立即返回。
2)当有数据到来,内核就会自动地将这些数据拷贝到应用层的缓冲区buf。一旦复制完成,会通过一个信号来通知应用进程的程序。
2.select
- 原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); - 参数:
- nfds:读、写、异常集合中的文件描述符的最大值+1
- readfds:读集合
- writefds:写集合
- exceptfds:异常集合
- timeout:超时结构体
- 返回值:成功返回检测到的事件个数;失败返回-1
void FD_ZERO(fd_set *set); /* clear all bits in set */
void FD_SET(int fd, fd_set *set); /* turn on the bit for fd in set */
int FD_ISSET(int fd, fd_set *set); /* is the bit for fd on in set? */
void FD_CLR(int fd, fd_set *set); /* trun off the bit for fd in set */
(1)select可以管理多个I/O,一旦其中的一个或多个I/O检测到了我们感兴趣的事件,select就立刻返回,返回值为检测到的事件个数。将刚兴趣的时间填充到对应的集合当中。
(2)然后我们就可以轮询这些事件,一个个的来处理它们。
(3)这个时候的处理就不会阻塞了,因为select已经提前阻塞了。
3.select改进回射客户/服务端程序
(1)首先,我们先看一下客户端的通信模块代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void echo_cli(int sock) { char sendbuf[1024]= {0}; char recvbuf[1024]= {0}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){ writen(sock, sendbuf, strlen(sendbuf)); int ret = readline (sock, recvbuf, sizeof(recvbuf)); if(ret == -1) ERR_EXIT("readline"); else if(ret ==0 ){ printf("server close\n"); break; } fputs(recvbuf,stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close (sock); } |
其中,第5、8行分别涉及到标准输入I/O(stdin),和套接口I/O(sock)。
(2)现在我们使用select来统一管理标准输入I/O和套接口I/O
1)首先我们需要定义一个集合,随后清空该集合:
fd_set rset;
FD_ZERO(&ret);
2)然后写一个死循环去检测I/O是否产生可读事件。
如下客户端通信模块代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
void echo_cli(int sock) { fd_set rset; FD_ZERO(&rset); int nready; //表示检测到的事件个数 int fd_stdin = fileno(stdin); //fileno获得对应的文件描述符 int maxfd; if (fd_stdin > sock) maxfd = fd_stdin; else maxfd =sock; char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while(1) { FD_SET(fd_stdin, &rset);//将这两个描述符放入读的集合rset中 FD_SET(sock, &rset); nready = select(maxfd+1, &rset, NULL,NULL,NULL); if (nready == -1) ERR_EXIT("select"); if (nready == 0) continue; //判断sock文件描述符是否在rset中,在,即表明检测到了事件 if (FD_ISSET(sock, &rset)) { //sock在集合中,处理该I/O事件 int ret = readline(sock, recvbuf, sizeof(recvbuf)); if (ret == -1) ERR_EXIT("readline"); else if (ret == 0) { printf("server close\n"); break; } fputs(recvbuf, stdout); memset(recvbuf, 0, sizeof(recvbuf)); } //判断fd_stdin是否在集合中 if (FD_ISSET(fd_stdin, &rset)) { //一旦从键盘获取了动作 if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) break; writen(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } } close(sock); } |
(3)回射客户端/服务器源码
echocli.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while (0) ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char*)buf; while(nleft > 0){ if((nread = read(fd,bufp, nleft)) < 0){ if (errno == EINTR) continue; return -1; } else if (nread ==0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char*)buf; while(nleft> 0){ if((nwritten = write(fd, bufp, nleft)) < 0){ if(errno == EINTR) continue; return -1; } else if(nwritten ==0) continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { while(1){ int ret = recv(sockfd, buf ,len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } size_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while(1){ ret = recv_peek(sockfd, bufp, nleft); if(ret < 0) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i=0; i<nread; i++){ if (bufp[i] == '\n'){ ret = readn(sockfd, bufp, i+1); if (ret != i+1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; } void echo_cli(int sock) { /* char sendbuf[1024]= {0}; char recvbuf[1024]= {0}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){ writen(sock, sendbuf, strlen(sendbuf)); int ret = readline (sock, recvbuf, sizeof(recvbuf)); if(ret == -1) ERR_EXIT("readline"); else if(ret ==0 ){ printf("server close\n"); break; } fputs(recvbuf,stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close (sock);*/ fd_set rset; FD_ZERO(&rset); int nready; int maxfd; int fd_stdin = fileno(stdin); if(fd_stdin > sock) maxfd = fd_stdin; else maxfd = sock; char sendbuf[1024]= {0}; char recvbuf[1024]= {0}; while(1){ FD_SET(fd_stdin, &rset); FD_SET(sock, &rset); nready = select(maxfd + 1, &rset, NULL, NULL, NULL); if(nready == -1) ERR_EXIT("select"); if (nready == 0) continue; if(FD_ISSET(sock, &rset)){ int ret = readline (sock, recvbuf, sizeof(recvbuf)); if(ret == -1) ERR_EXIT("readline"); else if(ret ==0 ){ printf("server close\n"); break; } fputs(recvbuf,stdout); memset(recvbuf, 0, sizeof(recvbuf)); } if (FD_ISSET(fd_stdin, &rset)){ if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) break; writen(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } } close(sock); } int main (void) { int sock; if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5581); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(connect(sock, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); struct sockaddr_in localaddr; socklen_t addrlen = sizeof(localaddr); if(getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0) ERR_EXIT("getsockname"); printf("ip = %s port = %d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); echo_cli(sock); return 0; } |
附echosrv.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #include<signal.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while (0) ssize_t readn(int fd, void *buf, size_t count) { size_t nleft = count; ssize_t nread; char *bufp = (char*)buf; while(nleft > 0){ if((nread = read(fd,bufp, nleft)) < 0){ if (errno == EINTR) continue; return -1; } else if (nread ==0) return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd, const void *buf, size_t count) { size_t nleft = count; ssize_t nwritten; char *bufp = (char*)buf; while(nleft> 0){ if((nwritten = write(fd, bufp, nleft)) < 0){ if(errno == EINTR) continue; return -1; } else if(nwritten ==0) continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd, void *buf, size_t len) { while(1){ int ret = recv(sockfd, buf ,len, MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } size_t readline(int sockfd, void *buf, size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while(1){ ret = recv_peek(sockfd, bufp, nleft); if(ret < 0) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i=0; i<nread; i++){ if (bufp[i] == '\n'){ ret = readn(sockfd, bufp, i+1); if (ret != i+1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd, bufp, nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; } void echo_srv(int conn) { char recvbuf[1024]; while(1){ memset(recvbuf, 0, sizeof(recvbuf)); int ret = readline(conn, recvbuf,1024); if(ret == -1) ERR_EXIT("readline"); else if(ret ==0 ){ printf("client close\n"); break; } fputs(recvbuf, stdout); writen(conn, recvbuf, strlen(recvbuf)); } } int main (void) { signal(SIGCHLD, SIG_IGN); int listenfd; if((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5581); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); int on = 1; if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt"); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if(listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("liten"); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; pid_t pid; while(1) { if((conn = accept(listenfd, (struct sockaddr*) &peeraddr, &peerlen)) < 0) ERR_EXIT("accept"); printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); pid = fork(); if(pid == -1) ERR_EXIT("fork"); if(pid == 0) { close(listenfd); echo_srv(conn); exit(EXIT_SUCCESS); } else close(conn); } return 0; } |
小结:
(1)select可以同时检测标准输入以及套接口读事件。
(2)对等方发来的数据,客户端可以通过select进行及时处理,而不会因为程序阻塞在标准输入而无法对套接口进行读操作。
(3)当要处理多个I/O,又想用单进程的方式进行处理时,用select比较方便。因为单进程可以轮询这些事件,对一个个事件可以进行相应的处理。