UNIX网络编程卷一:第十六章 非阻塞I/O
来源:互联网 发布:剑侠情缘3网游mac 编辑:程序博客网 时间:2024/05/18 06:27
套接字的默认状态是阻塞的。当发出一个套接字调用,但是不能立即完成时,该进程被投入睡眠。
可阻塞的套接字调用有4类:
1)输入操作
read readv recv recvfrom recvmsg
某进程对一个阻塞的TCP套接字调用这些输入函数,并且该套接字的接受缓冲区种没有数据可读,该进程被投入睡眠,直到有一些数据到达。
因为TCP是字节流协议,该进程的唤醒就是只有一些数据到达(可能不是所有数据)。
如果想等到某个固定数目的数据,那么可以使用unpvol1提供的readn函数,或者使用MSG_WAITALL标志。
UDP是数据报协议,如果一个阻塞的UDP套接字的接收缓冲区为空,那对它调用的进程被投入睡眠,直到有UDP数据报到达。
对于非阻塞套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节数据可读,对于UDP即有一个完整的数据报可读),
相应调用立即返回一个 EWOULDBLOCK错误。
readn函数:
ssize_t/* Read "n" bytes from a descriptor. */readn(int fd, void *vptr, size_t n){size_tnleft;ssize_tnread;char*ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nread = read(fd, ptr, nleft)) < 0) {if (errno == EINTR)nread = 0;/* and call read() again */elsereturn(-1);} else if (nread == 0)break;/* EOF */nleft -= nread;ptr += nread;}return(n - nleft);/* return >= 0 */}/* end readn */read被信号处理中断了, errno为EINTR。中断返回后继续调用read即可。
2)输出操作
write writev send sendto sendmsg
对于TCP套接字,内核将从应用进程的缓冲区到该套接字的发送缓冲区复制数据。
对于阻塞的套接字,如果发送缓冲区中没有空间,进程被投入睡眠,直到有空间为止。
对于非阻塞的TCP套接字,发送缓冲区没有任何空间,函数立即返回一个EWOULDBLOCK错误。
如果发送缓冲区有一些空间,返回值时内核能复制到该缓冲区的字节数(不足计数,short count)。
UDP不存在真正的发送缓冲区。内核只是复制应用进程数据并把它沿协议栈向下传送,以此加上udp首部和IP首部。
所以UDP阻塞的原因与上面TCP阻塞的原因不同。
3) 接受外来连接
accept
对一个阻塞的套接字调用accept,并尚无新的连接到达,调用进程被投入睡眠。
对于非阻塞的,无新连接到达时,accept立即返回一个EWOULDBLOCK错误。
4)发起连接
connect
TCP连接的建立涉及三次握手,connect一直等到客户收到对于自己的SYN的ACK为止才返回。
这意味着TCP的每个connect总会阻塞其调用进程至少一个到服务器的RTT时间。
对于非阻塞的TCP调用connect,并且连接不能立即建立,那么连接的建立能照样发起(譬如发出TCP三次握手的第一个分组),
不过会返回一个EINPROGRESS错误。
非阻塞+select
/* include nonb1 */#include"unp.h"voidstr_cli(FILE *fp, int sockfd){intmaxfdp1, val, stdineof;ssize_tn, nwritten;fd_setrset, wset;charto[MAXLINE], fr[MAXLINE];char*toiptr, *tooptr, *friptr, *froptr;// 设置描述符为非阻塞模式val = Fcntl(sockfd, F_GETFL, 0);Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);val = Fcntl(STDIN_FILENO, F_GETFL, 0);Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);val = Fcntl(STDOUT_FILENO, F_GETFL, 0);Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);toiptr = tooptr = to;/* initialize buffer pointers */friptr = froptr = fr;stdineof = 0;// 使用select监控这些描述符maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;for ( ; ; ) {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 */Select(maxfdp1, &rset, &wset, NULL, NULL);/* end nonb1 *//* include nonb2 */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) {#ifdefVOL2fprintf(stderr, "%s: EOF on stdin\n", gf_time());#endifstdineof = 1;/* all done with stdin */if (tooptr == toiptr)Shutdown(sockfd, SHUT_WR);/* send FIN */} else {#ifdefVOL2fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);#endiftoiptr += n;/* # just read */FD_SET(sockfd, &wset);/* try and write to socket below */}}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) {#ifdefVOL2fprintf(stderr, "%s: EOF on socket\n", gf_time());#endifif (stdineof)return;/* normal termination */elseerr_quit("str_cli: server terminated prematurely");} else {#ifdefVOL2fprintf(stderr, "%s: read %d bytes from socket\n",gf_time(), n);#endiffriptr += n;/* # just read */FD_SET(STDOUT_FILENO, &wset);/* try and write below */}}/* end nonb2 *//* include nonb3 */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 {#ifdefVOL2fprintf(stderr, "%s: wrote %d bytes to stdout\n",gf_time(), nwritten);#endiffroptr += nwritten;/* # just written */if (froptr == friptr)froptr = friptr = fr;/* back to beginning of buffer */}}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 {#ifdefVOL2fprintf(stderr, "%s: wrote %d bytes to socket\n",gf_time(), nwritten);#endiftooptr += nwritten;/* # just written */if (tooptr == toiptr) {toiptr = tooptr = to;/* back to beginning of buffer */if (stdineof)Shutdown(sockfd, SHUT_WR);/* send FIN */}}}}}/
维护了两个缓冲区,to和fr。结合select。实现比较复杂。但是效率高。
客户端通过fork,处理不同的功能。
#include"unp.h"voidstr_cli(FILE *fp, int sockfd){pid_tpid;charsendline[MAXLINE], recvline[MAXLINE];if ( (pid = Fork()) == 0) {/* child: server -> stdout */while (Readline(sockfd, recvline, MAXLINE) > 0)Fputs(recvline, stdout);kill(getppid(), SIGTERM);/* in case parent still running */exit(0);}/* parent: stdin -> server */while (Fgets(sendline, MAXLINE, fp) != NULL)Writen(sockfd, sendline, strlen(sendline));Shutdown(sockfd, SHUT_WR);/* EOF on stdin, send FIN */pause();return;}/*使用shutdown的原因:fork之后,父进程和子进程共享sockfd,sockfd的引用计数为2.如果此处使用close,仅仅是将sockfd的引用计数减1,那么sockfd的引用计数变为1,仍然不为0。不会发送FIN分节。而shutdown一定会发送FIN分节。 使用kill的原因: 有可能服务器提前终止了,子进程将在套接字上读到EOF,这样子进程必须告诉父进程:不要再往套接字写入数据了。 所以通过kill,杀死父进程。 *
- UNIX网络编程卷一:第十六章 非阻塞I/O
- UNIX网络编程卷1:套接字联网-第16章:非阻塞式I/O
- 《UNIX网络编程 卷1》 笔记: 非阻塞式I/O
- UNIX网络编程卷一:第八章 I/O UDP
- UNIX网络编程卷一:第十四章 高级I/O
- UNIX网络编程----非阻塞式I/O(十六)
- 第十六章 非阻塞I/O
- Unix网络编程代码 第16章 非阻塞式I/O
- UNIX网络编程1 理解同步、阻塞、非阻塞、异步网络I/O
- UNIX网络编程卷一 笔记 第六章 第6章 I/O复用
- UNIX网络编程卷一:第六章 I/O 复用 select, poll
- 【UNIX网络编程】五种I/O模型,阻塞非阻塞同步异步问题详解
- 《网络编程》非阻塞 I/O
- 《Unix网络编程》卷1:套接字联网API(第3版):非阻塞I/O、ioctl操作、路由套接字
- UNIX网络编程——非阻塞式I/O(套接字)
- UNP学习笔记(第十六章 非阻塞I/O)
- UNP第十六章 非阻塞式I/O
- 《UNIX网络编程 卷1》 笔记: 高级I/O函数
- 生成者与消费者
- C++ STL标准程序库初探
- 计算机组成原理之 I/O总结---来自北大陆俊林老师的视频内容(3)
- FreeCMS怎么动态访问模板?
- [高考]18个文言虚词之"而"
- UNIX网络编程卷一:第十六章 非阻塞I/O
- 我们如何学好java
- Oracle数据库SQLPLUS中几个常用set语句
- leetcode_Decode Ways
- 程序员爱情表白专用html5动画网页的代码
- B - {A} + {B}
- 对象数组的使用
- Oracle整形转字符串to_char()
- A. Music(Codeforces Round #315 (Div. 2) 求最大的容纳量)