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,杀死父进程。 *




0 0
原创粉丝点击