套接字的默认状态是阻塞的。阻塞的套接字调用可分为以下四类:
1.输入操作:包括read,readv,recv,recvfrom和recvmsg共5个函数。
2.输出操作:包括write,writev,send,sendto和sendmsg共5个函数。
3.接收外来连接,即accept函数。调用accept函数时尚无新的连接到达,调用进程将进入睡眠。
4.发起外出连接,即connect函数。connect函数引起三路握手过程,要一直等到客户收到对于自己的SYN的ACK为止才返回。
非阻塞读和写:str_cli函数
两个缓冲区:to容纳从标准输入到服务器去的数据,fr容纳自服务器到标准输出的数据。
str_cli函数:
#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, 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; friptr = froptr = fr; stdineof = 0; maxfdp1 = max(max(STDIN_FILENO,STDOUT_FILENO),sockfd) + 1; while (1) { FD_ZERO(&rset); FD_ZERO(&wset); /* 指定所关注的描述符 */ if (stdineof == 0 && toiptr < &to[MAXLINE]) FD_SET(STDIN_FILENO, &rset); //read from stdin if (friptr < &fd[MAXLINE]) FD_SET(sockfd,&rset); //read from sockfd 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); /* 从标准输入或者从套接字读入*/ 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) { fprintf(stderr, "EOF on stdin\n"); stdineof = 1; if (tooptr = toiptr) shutdown(sockfd, SHUT_WR); //send FIN } else { fprintf(stderr, "read %d bytes from stdin\n",n); toiptr += n; //jusy read FD_SET(sockfd,&wset); //try to write to socket } } 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) { fprintf(stderr, "EOF on socket\n",); if (stdineof) return; //normal termination else err_quit("str_cli: server terminated prematurely"); } else { fprintf(stderr,"read %d bytes from socket",n); friptr += n; FD_SET(STDOUT_FILENO,&wset); //try to write to stdout } } /* 写到标准输出或者套接字 */ 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 { fprintf(stderr, "wrote %d bytes to stdout \n",nwritten); 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(socket,tooptr,n)) < 0) { if (errno != EWOULDBLOCK) err_sys("write error to socket"); } else { fprintf(stderr, "wrote %d bytes to socket \n",nwritten); tooptr += nwritten; //just written if (tooptr == toiptr) { toiptr = tooptr = to; //back to beginning of buffer if (stdineof) shutdown(sockfd, SHUT_WR); //send FIN } } } } }
非阻塞式I/O例子的时间线:
str_cli的较简单版本
str_cli函数的另一个版本,该函数使用fork把当前进程划分成两个进程。子进程把来自服务器的文本行复制到标准输出,父进程把来自标准输入的文本行复制到服务器。
str_cli_fork.h
#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); //防止父进程一直运行 exit(0); } /* parent: stdin -> server */ while (fgets(sendline,MAXLINE,fp) != NULL) writen(sockfd, sendline, strlen(sendline)); shutdown(sockfd,SHUT_WR); //EOF on stdin, sen FIN pause(); return; }
非阻塞connect
当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的TCP三路握手继续进行。使用select检测这个连接或成功或失败的已建立条件。非阻塞的connect有三个用途。
1.把三路握手叠加在其它处理上。
2.可以使用这个技术同时建立多个连接。
3.使用select等待连接的建立,可以给select指定一个时间限制,使得我们能够缩短connect的超时。
非阻塞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 timaval tval; flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd,F_SETFL, flags | O_NONBLOCK); //把套接字设置为非阻塞 error = 0; if ( (n = connect(sockfd,saptr,salen)) < 0) //发起非阻塞connect,期望错误是EINPROGRESS,表示连接建立已启动但是尚未完成 if (errno != EINPROGRESS) return -1; //在connect完成期间可以做其它事 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) { //调用select等待套接字可读或者可写 close(sockfd); //timeout errno = ETIMEDOUT; return -1; //超时则关闭套接字,防止三路握手继续下去,并返回TIMEOUT错误给调用者。 } if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) return (-1); } 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
......................