在前面文章中,我们介绍了 I/O 的五种模型《I/O 模型》。从那里可以知道,非阻塞式的 I/O 是进程调用 I/O 操作时,若数据未准备就绪,则立即返回一个 EWOULDBLOCK 错误,在数据准备就绪之前,应用进程采用轮询的方式检查数据是否准备就绪。直到数据准备就绪,则内核把该数据复制到应用进程的缓冲区,完成数据复制之前进程处于阻塞状态,直到数据复制完成后才返回。即 I/O 操作第一阶段处于轮询检查状态,第二阶段处于阻塞状态。
套接字的 I/O 操作默认状态是采用阻塞式。即当不能立即完成套接字调用时,其进程会处于阻塞状态,直到相应操作完成。阻塞套接字大致可分为以下四种类型:
在非阻塞读写的编程中,维护两个缓冲区:to 容纳从标准输入到服务器去的数据,fr 容纳自服务器到标准输出来的数据,这两个缓冲区具体结构如下图所示:
其中 toiptr 指针指向标准输入读入的数据可以存放的下一个字节,tooptr 指向下一个必须写到套接字的字节。一旦 tooptr 移动到 toiptr,则这两个指针就一起恢复到缓冲区开始处。
/* include nonb1 */
#include <sys/select.h>
#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAXLINE 4096
inline int Max(int a, int b)
{
return(a >= b?a:b);
}
extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
static void set_fl(int fd, int flags);
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
ssize_t n, nwritten;
fd_set rset, wset;
char to[MAXLINE], fr[MAXLINE];
char *toiptr, *tooptr, *friptr, *froptr;
/* 设置套接字描述符为非阻塞 */
set_fl(sockfd, O_NONBLOCK);
/* 设置标准输入为非阻塞 */
set_fl(STDIN_FILENO, O_NONBLOCK);
/* 设置标准输出为非阻塞 */
set_fl(STDOUT_FILENO, O_NONBLOCK);
/* 初始化两个缓冲区指针 */
toiptr = tooptr = to; /* initialize buffer pointers */
friptr = froptr = fr;
stdineof = 0;/* 标准输入键入EOF的标志 */
maxfdp1 = Max(Max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
for ( ; ; ) {
/* 初始化,为调用select函数做准备 */
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 */
if(select(maxfdp1, &rset, &wset, NULL, NULL) < 0)
err_sys("select error");
/* end nonb1 */
/* include nonb2 */
/* 若标准输入在rset有效,则从标准输入读取数据到发送缓冲区 */
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)
{
stdineof = 1; /* all done with stdin */
if (tooptr == toiptr)
if(shutdown(sockfd, SHUT_WR) < 0)/* send FIN */
err_sys("shutdown error");
}
else
{
toiptr += n; /* # just read */
FD_SET(sockfd, &wset); /* try and write to socket below */
}
}
/* 若套接字在rset有效,则从套接字读取数据到接收缓冲区中 */
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)
{
if (stdineof)
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
}
else
{
friptr += n; /* # just read */
FD_SET(STDOUT_FILENO, &wset); /* try and write below */
}
}
/* end nonb2 */
/* include nonb3 */
/* 若标准输出在wset有效,则从接收缓冲区写数据到标准输出 */
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
{
froptr += nwritten; /* # just written */
if (froptr == friptr)
froptr = friptr = fr; /* back to beginning of buffer */
}
}
/* 若套接字在wset有效,则从发送缓冲区写数据到套接字中 */
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
{
tooptr += nwritten; /* # just written */
if (tooptr == toiptr) {
toiptr = tooptr = to; /* back to beginning of buffer */
/* 若套接字接收来自标准输入的数据,则关闭套接字写端,相当于从标准输入键入EOF */
if (stdineof)
if(shutdown(sockfd, SHUT_WR) <0) /* send FIN */
err_sys("Shutdown error");
}
}
}
}
}
/* end nonb3 */
static void set_fl(int fd, int flags)
{
int val;
/* 获取描述符状态标志 */
if( (val = fcntl(fd, F_GETFL, 0)) < 0)
err_sys("fcntl get error");
/* 添加描述符状态标志flags*/
val |= flags;
/* 设置描述符状态标志 */
if(fcntl(fd, F_SETFL, val) < 0)
err_sys("fcntl set error");
}
#include <sys/select.h>
#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern void err_quit(const char *, ...);
int
connect_nonb(int sockfd, const struct sockaddr *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)
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, &rset, &wset, NULL,
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, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
return(0);
}
原文:http://blog.csdn.net/chenhanzhun/article/details/41965461