首页 > 代码库 > 《网络编程》非阻塞 I/O
《网络编程》非阻塞 I/O
概述
在前面文章中,我们介绍了 I/O 的五种模型《I/O 模型》。从那里可以知道,非阻塞式的 I/O 是进程调用 I/O 操作时,若数据未准备就绪,则立即返回一个 EWOULDBLOCK 错误,在数据准备就绪之前,应用进程采用轮询的方式检查数据是否准备就绪。直到数据准备就绪,则内核把该数据复制到应用进程的缓冲区,完成数据复制之前进程处于阻塞状态,直到数据复制完成后才返回。即 I/O 操作第一阶段处于轮询检查状态,第二阶段处于阻塞状态。
套接字的 I/O 操作默认状态是采用阻塞式。即当不能立即完成套接字调用时,其进程会处于阻塞状态,直到相应操作完成。阻塞套接字大致可分为以下四种类型:
- 输入操作引起的阻塞状态,包括 read、readv、recv、recvfrom 和 recvmsg 函数。若某个进程对一个阻塞的 TCP 调用这些输入函数,且此时套接字的接收缓冲区没有可读数据,则该进程会处于阻塞状态,直到有一些数据到达。对于字节流协议的 TCP 来说,只要有一些数据到达,哪怕是单个字节数据,或者是完成的 TCP 报文段数据,该进程都会被唤醒;对于数据报协议的 UDP 来说,若一个阻塞的 UDP 套接字接收缓冲区为空,则该进程也会处于阻塞状态,直到完整 UDP 数据报到达;对于非阻塞的套接字,若不能满足输入操作要求,则会立即返回一个 EWOULDBLOCK 错误;
- 输出操作引起的阻塞状态,包括 write、writev、send、sendto 和 sendmsg 函数。对于一个阻塞 TCP 套接字,内核将从应用进程的缓冲区复制数据到该套接字的发送缓冲区,若该套接字的发送缓冲区没有存储空间,则进程会进入阻塞状态,直到有空间为止;对于一个非阻塞的 TCP 套接字,若其发送缓冲区没有存储空间,则输出函数调用立即返回一个 EWOULDBLOCK 错误,当发送缓冲区有空间时,则内核把应用进程缓冲区的数据复制到发送缓冲区中,并返回已复制的字节数;由于 UDP 套接字不存在真正的发送缓冲区,内核只是将应用进程的数据复制并把它沿协议栈向下传送,因此对于一个阻塞 UDP 套接字调用输出函数时不会和 TCP 一样的原因而阻塞,是因为其他的原因(在书本上没有说明是什么原因);
- 接受连接请求引起的阻塞状态,即 accept 函数。当阻塞套接字调用 accept 函数时,若没有新的连接请求,则进程就会进入阻塞状态;非阻塞套接字调用 accept 函数时,且不存在新的连接请求,则 accept 函数调用立即返回一个 EWOULDBLOCK 错误;
- 发出连接请求引起的阻塞状态,即用于 TCP 的 connect 函数。由于 TCP 的连接建立需要三次握手过程,且 connect 函数一直等待客户端收到对自己的 SYN 的应答响应 ACK 才返回,因此,每个 connect 发起的连接请求在 RTT 时间内处于阻塞状态;对于 非阻塞 TCP 套接字调用 connect 函数时,若连接不能立即建立,则连接请求正常发出,但是会返回一个 EINPROGRESS 错误;
非阻塞读写
在非阻塞读写的编程中,维护两个缓冲区:to 容纳从标准输入到服务器去的数据,fr 容纳自服务器到标准输出来的数据,这两个缓冲区具体结构如下图所示:
其中 toiptr 指针指向标准输入读入的数据可以存放的下一个字节,tooptr 指向下一个必须写到套接字的字节。一旦 tooptr 移动到 toiptr,则这两个指针就一起恢复到缓冲区开始处。
/* include nonb1 */ #include <sys/select.h> #include <sys/socket.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define MAXLINE 4096 inline int Max(int a, int b) { return(a >= b?a:b); } extern void err_sys(const char *, ...); extern void err_quit(const char *, ...); static void set_fl(int fd, int flags); void str_cli(FILE *fp, int sockfd) { int maxfdp1, stdineof; ssize_t n, nwritten; fd_set rset, wset; char to[MAXLINE], fr[MAXLINE]; char *toiptr, *tooptr, *friptr, *froptr; /* 设置套接字描述符为非阻塞 */ set_fl(sockfd, O_NONBLOCK); /* 设置标准输入为非阻塞 */ set_fl(STDIN_FILENO, O_NONBLOCK); /* 设置标准输出为非阻塞 */ set_fl(STDOUT_FILENO, O_NONBLOCK); /* 初始化两个缓冲区指针 */ toiptr = tooptr = to; /* initialize buffer pointers */ friptr = froptr = fr; stdineof = 0;/* 标准输入键入EOF的标志 */ maxfdp1 = Max(Max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1; for ( ; ; ) { /* 初始化,为调用select函数做准备 */ FD_ZERO(&rset); FD_ZERO(&wset); if (stdineof == 0 && toiptr < &to[MAXLINE]) FD_SET(STDIN_FILENO, &rset); /* read from stdin */ if (friptr < &fr[MAXLINE]) FD_SET(sockfd, &rset); /* read from socket */ if (tooptr != toiptr) FD_SET(sockfd, &wset); /* data to write to socket */ if (froptr != friptr) FD_SET(STDOUT_FILENO, &wset); /* data to write to stdout */ if(select(maxfdp1, &rset, &wset, NULL, NULL) < 0) err_sys("select error"); /* end nonb1 */ /* include nonb2 */ /* 若标准输入在rset有效,则从标准输入读取数据到发送缓冲区 */ if (FD_ISSET(STDIN_FILENO, &rset)) { if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) { if (errno != EWOULDBLOCK) err_sys("read error on stdin"); } else if (n == 0) { stdineof = 1; /* all done with stdin */ if (tooptr == toiptr) if(shutdown(sockfd, SHUT_WR) < 0)/* send FIN */ err_sys("shutdown error"); } else { toiptr += n; /* # just read */ FD_SET(sockfd, &wset); /* try and write to socket below */ } } /* 若套接字在rset有效,则从套接字读取数据到接收缓冲区中 */ if (FD_ISSET(sockfd, &rset)) { if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) { if (errno != EWOULDBLOCK) err_sys("read error on socket"); } else if (n == 0) { if (stdineof) return; /* normal termination */ else err_quit("str_cli: server terminated prematurely"); } else { friptr += n; /* # just read */ FD_SET(STDOUT_FILENO, &wset); /* try and write below */ } } /* end nonb2 */ /* include nonb3 */ /* 若标准输出在wset有效,则从接收缓冲区写数据到标准输出 */ if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0)) { if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) { if (errno != EWOULDBLOCK) err_sys("write error to stdout"); } else { froptr += nwritten; /* # just written */ if (froptr == friptr) froptr = friptr = fr; /* back to beginning of buffer */ } } /* 若套接字在wset有效,则从发送缓冲区写数据到套接字中 */ if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) { if ( (nwritten = write(sockfd, tooptr, n)) < 0) { if (errno != EWOULDBLOCK) err_sys("write error to socket"); } else { tooptr += nwritten; /* # just written */ if (tooptr == toiptr) { toiptr = tooptr = to; /* back to beginning of buffer */ /* 若套接字接收来自标准输入的数据,则关闭套接字写端,相当于从标准输入键入EOF */ if (stdineof) if(shutdown(sockfd, SHUT_WR) <0) /* send FIN */ err_sys("Shutdown error"); } } } } } /* end nonb3 */ static void set_fl(int fd, int flags) { int val; /* 获取描述符状态标志 */ if( (val = fcntl(fd, F_GETFL, 0)) < 0) err_sys("fcntl get error"); /* 添加描述符状态标志flags*/ val |= flags; /* 设置描述符状态标志 */ if(fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl set error"); }
非阻塞 connect
#include <sys/select.h> #include <sys/socket.h> #include <errno.h> #include <fcntl.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> extern void err_quit(const char *, ...); int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec) { int flags, n, error; socklen_t len; fd_set rset, wset; struct timeval tval; flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); error = 0; if ( (n = connect(sockfd, saptr, salen)) < 0) if (errno != EINPROGRESS) return(-1); /* Do whatever we want while the connect is taking place. */ if (n == 0) goto done; /* connect completed immediately */ FD_ZERO(&rset); FD_SET(sockfd, &rset); wset = rset; tval.tv_sec = nsec; tval.tv_usec = 0; if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) { close(sockfd); /* timeout */ errno = ETIMEDOUT; return(-1); } if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) return(-1); /* Solaris pending error */ } else err_quit("select error: sockfd not set"); done: fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ if (error) { close(sockfd); /* just in case */ errno = error; return(-1); } return(0); }
非阻塞 accept
《网络编程》非阻塞 I/O
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。