第十六章 非阻塞式I/O:
示例:
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include "err_exit.h"
#define MAXLINE 4096
char *
gf_time(void)
{
struct timeval tv;
time_t t;
static char str[30];
char * ptr;
if (gettimeofday(&tv, NULL) != 0) { /* should always return 0 */
err_exit("gettimeofday error");
}
t = tv.tv_sec; /* POSIX says tv.tv_sec is time_t; some BSDs don't agree */
ptr = ctime(&t);
strcpy(str, &ptr[11]);
/* Fri Sep 13 00:00:00 1986\n\0 */
/* 0123456789012345678901234 5 */
snprintf(str+8, sizeof(str)-8, ".%06ld", tv.tv_usec);
return(str);
}
void
str_cli(FILE * fp, int sockfd)
{
int maxfdp1;
int val;
int stdineof;
ssize_t n;
ssize_t nwritten;
fd_set rset;
fd_set wset;
char to[MAXLINE];
char fr[MAXLINE];
char * toiptr;
char * tooptr;
char * friptr;
char * froptr;
if ((val = fcntl(sockfd, F_GETFL, 0)) == -1) {
err_exit("fcntl error");
}
if (fcntl(sockfd, F_SETFL, val | O_NONBLOCK) == -1) {
err_exit("fcntl error");
}
if ((val = fcntl(STDIN_FILENO, F_GETFL, 0)) == -1) {
err_exit("fcntl error");
}
if (fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK) == -1) {
err_exit("fcntl error");
}
if ((val = fcntl(STDOUT_FILENO, F_GETFL, 0)) == -1) {
err_exit("fcntl error");
}
if (fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK) == -1) {
err_exit("fcntl error");
}
/* initialize buffer pointers */
toiptr = tooptr = to;
friptr = froptr = fr;
stdineof = 0;
maxfdp1 = max(STDIN_FILENO, STDOUT_FILENO);
maxfdp1 = max(maxfdp1, 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 */
}
if (select(maxfdp1, &rset, &wset, NULL, NULL) == -1) {
err_exit("select error");
}
if (FD_ISSET(STDIN_FILENO, &rset)) {
if ((n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
if (errno != EWOULDBLOCK) {
printf("read error on stdin\n");
exit(1);
}
}
else if (n == 0) {
fprintf(stderr, "%s: EOF on stdin\n", gf_time());
stdineof = 1; /* all done with stdin */
if (tooptr == toiptr) {
if (shutdown(sockfd, SHUT_WR) == -1) { /* send FIN */
err_exit("shutdown error");
}
}
}
else {
fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);
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) {
printf("read error on socket\n");
exit(1);
}
}
else if (n == 0) {
fprintf(stderr, "%s: EOF on socket\n", gf_time());
if (stdineof) {
return; /* normal termination */
}
else {
printf("str_cli: server terminated prematurely");
exit(1);
}
}
else {
fprintf(stderr, "%s: read %d bytes from socket\n",
gf_time(), n);
friptr += n; /* just read */
FD_SET(STDOUT_FILENO, &wset); /* try and write below */
}
}
if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {
if ((nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
if (errno != EWOULDBLOCK) {
printf("write error to stdout\n");
exit(1);
}
}
else {
fprintf(stderr, "%s: wrote %d bytes to stdout\n",
gf_time(), 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(sockfd, tooptr, n)) < 0) {
if (errno != EWOULDBLOCK) {
printf("write error to socket\n");
exit(1);
}
}
else {
fprintf(stderr, "%s: wrote %d bytes to socket\n",
gf_time(), nwritten);
tooptr += nwritten; /* just written */
if (tooptr == toiptr) {
toiptr = tooptr = to; /* back to beginning of buffer */
if (stdineof) {
if (shutdown(sockfd, SHUT_WR) == -1) { /* send FIN */
err_exit("shutdown error");
}
}
}
}
}
}
}
#include <signal.h>
#include <sys/socket.h>
#include "err_exit.h"
#define MAXLINE 4096
void
str_cli(FILE * fp, int sockfd)
{
int n;
pid_t pid;
char sendline[MAXLINE];
char recvline[MAXLINE];
if ((pid = fork()) < 0) {
err_exit("fork error");
}
else if (pid == 0) { /* child: server -> stdout */
while ((n = readline(sockfd, recvline, MAXLINE)) > 0) {
if (fputs(recvline, stdout) == EOF) {
err_exit("fputs error");
}
}
if (n < 0) {
err_exit("readline error");
}
if (getppid() != 1) {
kill(getppid(), SIGTERM); /* in case parent still running */
}
exit(0);
}
else { /* parent: stdin -> server */
while (fgets(sendline, MAXLINE, fp) != NULL) {
n = strlen(sendline);
if (writen(sockfd, sendline, n) != n) {
err_exit("writen error");
}
}
if (ferror(fp)) {
err_exit("fgets error");
}
if (shutdown(sockfd, SHUT_WR) == -1) { /* EOF on stdin, send FIN */
err_exit("shutdown error");
}
pause();
return;
}
}
#include <fcntl.h>
#include <sys/socket.h>
#include "err_exit.h"
int
connect_nonb(int sockfd, const struct sockaddr * saptr,
socklen_t salen, int nsec)
{
int flags;
int n;
int error;
socklen_t len;
fd_set rset;
fd_set wset;
struct timeval tval;
if ((flags = fcntl(sockfd, F_GETFL, 0)) == -1) {
err_exit("fcntl error");
}
if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
err_exit("fcntl error");
}
error = 0;
if ((n = connect(sockfd, saptr, salen)) == -1) {
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)) == -1) {
err_exit("select error");
}
else if (n == 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) == -1) {
return(-1); /* Solaris pending error */
}
}
else {
printf("select error: sockfd not set\n");
exit(1);
}
done:
/* restore file status flags */
if (fcntl(sockfd, F_SETFL, flags) == -1) {
err_exit("fcntl error");
}
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
return(0);
}
#include <fcntl.h>
#include <sys/select.h>
#include "myio.h"
#include "err_exit.h"
#include "host_serv.h"
#include "tcp_connect.h"
#define MAXFILES 20
#define SERV "80" /* port number or service name */
struct file {
char * f_name; /* filename */
char * f_host; /* hostname or IPv4/IPv6 address */
int f_fd; /* descriptor */
int f_flags; /* F_xxx below */
} file[MAXFILES];
#define F_CONNECTING 1 /* connect() in progress */
#define F_READING 2 /* connect() complete; now reading */
#define F_DONE 4 /* all done */
#define GET_CMD "GET %s HTTP/1.0\r\n\r\n"
#define min(a, b) ((a) < (b) ? (a) : (b))
int nconn;
int nfiles;
int nlefttoconn;
int nlefttoread;
int maxfd;
fd_set rset;
fd_set wset;
void
home_page(const char * host, const char * fname)
{
int fd;
int n;
char line[MAXLINE];
fd = tcp_connect(host, SERV); /* blocking connect() */
n = snprintf(line, sizeof(line), GET_CMD, fname);
if (writen(fd, line, n) != n) {
err_exit("writen error");
}
for ( ; ; ) {
if ((n = read(fd, line, MAXLINE)) < 0) {
err_exit("read error");
}
else if (n == 0) {
break; /* server closed connection */
}
printf("read %d bytes of home page\n", n);
/* do whatever with data */
}
printf("end-of-file on home page\n");
if (close(fd) == -1) {
err_exit("close error");
}
}
void
write_get_cmd(struct file * fptr)
{
int n;
char line[MAXLINE];
n = snprintf(line, sizeof(line), GET_CMD, fptr->f_name);
if (writen(fptr->f_fd, line, n) != n) {
err_exit("writen error");
}
printf("wrote %d bytes for %s\n", n, fptr->f_name);
fptr->f_flags = F_READING; /* clears F_CONNECTING */
FD_SET(fptr->f_fd, &rset); /* will read server's reply */
if (fptr->f_fd > maxfd) {
maxfd = fptr->f_fd;
}
}
void
start_connect(struct file * fptr)
{
int fd;
int flags;
int n;
struct addrinfo * ai;
ai = host_serv(fptr->f_host, SERV, 0, SOCK_STREAM);
if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
err_exit("socket error");
}
fptr->f_fd = fd;
printf("start_connect for %s, fd %d\n", fptr->f_name, fd);
/* Set socket nonblocking */
if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
err_exit("fcntl error");
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
err_exit("fcntl error");
}
/* Initiate nonblocking connect to the server. */
if ((n = connect(fd, ai->ai_addr, ai->ai_addrlen)) == -1) {
if (errno != EINPROGRESS) {
printf("nonblocking connect error\n");
exit(1);
}
fptr->f_flags = F_CONNECTING;
FD_SET(fd, &rset); /* select for reading and writing */
FD_SET(fd, &wset);
if (fd > maxfd) {
maxfd = fd;
}
}
else { /* connect is already done */
write_get_cmd(fptr); /* write() the GET command */
}
}
int
main(int argc, char ** argv)
{
int i;
int fd;
int n;
int maxnconn;
int flags;
int error;
char buf[MAXLINE];
fd_set rs;
fd_set ws;
if (argc < 5) {
printf("usage: web <#conns> <hostname> <homepage> <file1> ...\n");
exit(1);
}
maxnconn = atoi(argv[1]);
nfiles = min(argc - 4, MAXFILES);
for (i = 0; i < nfiles; i++) {
file[i].f_name = argv[i + 4];
file[i].f_host = argv[2];
file[i].f_flags = 0;
}
printf("nfiles = %d\n", nfiles);
home_page(argv[2], argv[3]);
FD_ZERO(&rset);
FD_ZERO(&wset);
maxfd = -1;
nlefttoread = nlefttoconn = nfiles;
nconn = 0;
while (nlefttoread > 0) {
while (nconn < maxnconn && nlefttoconn > 0) {
/* find a file to read */
for (i = 0 ; i < nfiles; i++) {
if (file[i].f_flags == 0) {
break;
}
}
if (i == nfiles) {
printf("nlefttoconn = %d but nothing found\n", nlefttoconn);
}
start_connect(&file[i]);
nconn++;
nlefttoconn--;
}
rs = rset;
ws = wset;
if ((n = select(maxfd+1, &rs, &ws, NULL, NULL)) == -1) {
err_exit("select error");
}
for (i = 0; i < nfiles; i++) {
flags = file[i].f_flags;
if (flags == 0 || flags & F_DONE) {
continue;
}
fd = file[i].f_fd;
if (flags & F_CONNECTING &&
(FD_ISSET(fd, &rs) || FD_ISSET(fd, &ws))) {
n = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &n) < 0 ||
error != 0) {
printf("nonblocking connect failed for %s\n",
file[i].f_name);
}
else { /* connection established */
printf("connection established for %s\n", file[i].f_name);
FD_CLR(fd, &wset); /* no more writeability test */
write_get_cmd(&file[i]); /* write() the GET command */
}
}
else if (flags & F_READING && FD_ISSET(fd, &rs)) {
if ((n = read(fd, buf, sizeof(buf))) < 0) {
err_exit("read error");
}
else if (n == 0) {
printf("end-of-file on %s\n", file[i].f_name);
if (close(fd) == -1) {
err_exit("close error");
}
file[i].f_flags = F_DONE; /* clears F_READING */
FD_CLR(fd, &rset);
nconn--;
nlefttoread--;
}
else {
printf("read %d bytes from %s\n", n, file[i].f_name);
}
}
}
}
exit(0);
}