阻塞套接字意味着当发出一个不能立即完成的套接字调用时,其进程将投入睡眠,等待相应操作完成。
非阻塞套接字,如果输入操作不能被满足(对于tcp套接字即至少有一个字节的数据可读,对于udp套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误。
注意:selectc通常结合非阻塞式I/O一起使用
1、非阻塞读和写:str_cli函数的开发
如果套接字发送缓冲区已满,writen调用将会阻塞。在进程阻塞于writen调用期间,可能有来自套接字接收缓冲区的数据区的数据可供读取(偏偏由于进程被阻塞而读不了)。同理,如果从套接字中有一行输入文本可读,那么照样可能阻塞于后续的write调用。这个时候就显示出非阻塞读和写的好处,但非阻塞式I/O的加入str_cli函数的缓冲区管理显著地复杂化。于是可简化版本就是把应用程序任务划分到多个进程(使用fork)或多个线程。
首先介绍一下容纳从标准输入到套接字的数据的buff和从套接字到标准输入出的buff,如下所示:
相应代码此处贴出(实在是有点长,增加了复杂性)
/* include nonb1 */ #include "unp.h" void str_cli(FILE *fp, int sockfd) { int maxfdp1, val, stdineof; ssize_t n, nwritten; fd_set rset, wset; char to[MAXLINE], fr[MAXLINE]; char *toiptr, *tooptr, *friptr, *froptr; //以下是利用fcntl函数设置套接字为非阻塞的惯用手法 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; 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) {//遇见EOF结束符 #ifdef VOL2 fprintf(stderr, "%s: EOF on stdin\n", gf_time()); #endif stdineof = 1; /* all done with stdin */ if (tooptr == toiptr)//若为真,表明不再有东西发出 Shutdown(sockfd, SHUT_WR);/* send FIN */ } else { #ifdef VOL2 fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n); #endif toiptr += 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) { #ifdef VOL2 fprintf(stderr, "%s: EOF on socket\n", gf_time()); #endif if (stdineof) return; /* normal termination */ else err_quit("str_cli: server terminated prematurely"); } else { #ifdef VOL2 fprintf(stderr, "%s: read %d bytes from socket\n", gf_time(), n); #endif friptr += 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 { #ifdef VOL2 fprintf(stderr, "%s: wrote %d bytes to stdout\n", gf_time(), nwritten); #endif froptr += 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 { #ifdef VOL2 fprintf(stderr, "%s: wrote %d bytes to socket\n", gf_time(), nwritten); #endif tooptr += nwritten; /* # just written */ if (tooptr == toiptr) { toiptr = tooptr = to; /* back to beginning of buffer */ if (stdineof) Shutdown(sockfd, SHUT_WR); /* send FIN */ } } } } } /* end nonb3 */
str_cli的较简单版本(把应用程序任务划分到多个进程(使用fork))
#include "unp.h" void str_cli(FILE *fp, int sockfd) { pid_t pid; char sendline[MAXLINE], recvline[MAXLINE]; if ((pid = Fork()) == 0) { /* child: server -> stdout */ while (Readline(sockfd, recvline, MAXLINE) > 0) Fputs(recvline, stdout); /*当子进程读到EOF时,表明服务器进程终止,必须终止父进程从stdin到sockfd复制data*/ 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关闭写半部分,因为子进程还可能存在读,但不能用close(还出于存在引用计数问题考虑) Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */ pause(); return; }
2、非阻塞connect
当在一个非阻塞的tcp套接字上调用connect时,connect将立即返回一个EINPROGESS错误,另如果已将发起的tcp三路握手继续进行。于是关天select和非阻塞connect的两个规则i、当连接成功建立时,描述符变为可写;ii、当连接建立遇到错误时,描述符变为既可读又可写。
#include "unp.h" int connect_nonb(int sockfd, const SA *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)//期待错误EINPROGRESS,表示建立已连接但未完成 if (errno != EINPROGRESS) //此是non-block socket connect的特点 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)) {//如果套接字变为可读或可写时,调用getsockopt取得套接字的待处理错误 len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)//error值为0表示禁止option,非0表示启用option 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); }
3、非阻塞accept
很矛盾哈,这是因为select告诉我们该套接字上已有连接就绪,那么随后的accept调用不应该阻塞,因为此时阻塞完全没必要。但有时有特定的条件时,还是需要非阻塞accept的,由于情况比较特殊,此处就不,贴代码了,详情见书上P363页。
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。