1.可能阻塞的4类套接字调用:
1)输入操作,包括read,readv,recv,recvfrom,recvmsg。对于非阻塞的套接字,如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误。
2)输出操作,包括write、writev、send、sendto和sendmsg。对于一个非阻塞的TCP套接字,如果其发送缓冲区中根本没有空间,输出函数调用将立即返回一个EWOULDBLOCK错误。如果其发送缓冲区中有一些空间,返回值将是内核能够复制到该缓冲区中的字节数。
3)接受外来连接,即accept函数。如果对于一个非阻塞的套接字调用accept函数,并且尚无新的连接到达,accept调用将立即返回一个EWOULDBLOCK错误。
4)发起外来连接,即用于tcp的connect函数。对于一个非阻塞的tcp套接字调用connect,并且连接不能立即建立,那么连接的建立能照样发起,不过会返回一个EINPROGRESS错误。且,有些连接可以立即建立,通常发生在服务器和客户处于同一个主机
以下代码来自unix网络编程卷1
使用非阻塞式I/O版本,防止进程在可做任何有效工作期间发生阻塞
2.非阻塞式I/O的str_cli版本
非阻塞式I/O的加入,使得程序对缓冲区的管理明显复杂化
以下程序维护两个缓冲区:to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出的数据。
/* 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; val = Fcntl(sockfd,F_GETFL,0); Fcntl(sockfd,F_SETFL,val | O_NONBLOCK); val = Fcntl(STDIN_FILENO,0); Fcntl(STDIN_FILENO,val | O_NONBLOCK); val = Fcntl(STDOUT_FILENO,0); Fcntl(STDOUT_FILENO,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) { #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",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",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",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的较简单版本(把应用程序任务划分到多个进程或多个线程) #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); 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; }我们首先要明确几点:
3.str_cli执行时间对比
以下来自unix网络编程卷1的测试结果
测试环境:从一个solaris客户机向RTT为175毫秒的一个服务器主机复制2000行文本
非阻塞式I/O版本 | 6.9秒 | @H_502_73@
线程化版本 | 8.5秒 | @H_502_73@
fork版本 | 8.7秒 | @H_502_73@
select加阻塞式I/O版本 | 12.3秒 | @H_502_73@
停等版本 | 354.0秒 | @H_502_73@
4.非阻塞connect
非阻塞connect的三个用途:
- 我们可以把三路握手叠加在其他处理上,完成一个connect要花一个RTT时间,这段时间内我们可以执行其他要处理的工作。
- 我们可以使用这个技术同时建立多个连接
- 使用select等待连接的建立,我们可以给select指定一个时间限制,使得我们能够缩短connect的超时
怎么使用connect:
先将套接字描述符设置为非阻塞,然后按照常规网络编程,直到调用connect时,connect将立即返回一个EINPROGRESS错误(ps: 已经发起的三路握手继续执行)。我们
接着使用I/O复用(比如 select)检测这个连接或成功或失败的已建立条件
需要注意的处理细节
- 尽管套接字是非阻塞的,如果连接的服务器在同一主机上,我们调用connect时,连接通常立刻建立。
- 关于select和非阻塞connect的以下两个规则:当连接成功建立时,描述符变为可写,当连接建立遇到失败时,描述符变为既可读又可写。
注:以下代码来自unix网络编程卷1
1.非阻塞connect:
#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,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,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,flags); /* restore file status flags */ if (error) { close(sockfd); /* just in case */ errno = error; return(-1); } return(0); }
如何判断非阻塞connect调用后,连接是否成功建立:
- 上述程序使用的方法:先判断套接字可读或可写条件,然后调用getsockopt,检查套接字上是否存在待处理错误
- 调用getpeername代替getsockopt。
- 以值为0的长度参数调用read
- 再调用connect一次
以上具体参见unix网络编程卷1
5.非阻塞accept
1.当使用select获悉某个监听套接字上何时有已完成连接准备被accept时,总是把这个监听套接字设置为非阻塞
2.在后续的accept调用中忽略以下错误:EWOULDBLOCK(源自bk,客户终止连接时),ECONNABORTED(POSIX实现,客户终止连接时,EPROTO(SVR4实现,客户中止连接时)和EINTR(如果有信号被捕获)
原文链接:https://www.f2er.com/bash/391949.html