15.5 非阻塞connect:Web客户程序
// web.h #include "unp.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" /* globals */
int nconn, nfiles, nlefttoconn, nlefttoread, maxfd;
fd_set rset, wset; /* function prototypes */
void home_page(const char *, const char *);
void start_connect(struct file *);
void write_get_cmd(struct file *);
******************************************* //home_page.c #include "web.h" void
home_page(const char *host, const char *fname)
{
int fd, n;
char line[MAXLINE]; fd = Tcp_connect(host, SERV); /* blocking connect() */ n = snprintf(line, sizeof(line), GET_CMD, fname);
Writen(fd, line, n); for ( ; ; ) {
if ( (n = Read(fd, line, MAXLINE)) == 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");
Close(fd);
}
**********************************************
//start_connect.c #include "web.h" void
start_connect(struct file *fptr)
{
int fd, flags, n;
struct addrinfo *ai; ai = Host_serv(fptr->f_host, SERV, 0, SOCK_STREAM); fd = Socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
fptr->f_fd = fd;
printf("start_connect for %s, fd %d\n", fptr->f_name, fd); /* 4Set socket nonblocking */
flags = Fcntl(fd, F_GETFL, 0);
Fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* 4Initiate nonblocking connect to the server. */
if ( (n = connect(fd, ai->ai_addr, ai->ai_addrlen)) < 0) {
if (errno != EINPROGRESS)
err_sys("nonblocking connect error");
fptr->f_flags = F_CONNECTING;
FD_SET(fd, &rset); /* select for reading and writing */
FD_SET(fd, &wset);
if (fd > maxfd)
maxfd = fd; } else if (n >= 0) /* connect is already done */
write_get_cmd(fptr); /* write() the GET command */
}
*********************************************
//write_get_cmd.c #include "web.h" void
write_get_cmd(struct file *fptr)
{
int n;
char line[MAXLINE]; n = snprintf(line, sizeof(line), GET_CMD, fptr->f_name);
Writen(fptr->f_fd, line, n);
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;
}
*******************************************
//web.c /* include web1 */
#include "web.h" int
main(int argc, char **argv)
{
int i, fd, n, maxnconn, flags, error;
char buf[MAXLINE];
fd_set rs, ws; if (argc < 5)
err_quit("usage: web <#conns> ...");
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;
/* end web1 */
/* include web2 */
while (nlefttoread > 0) {
while (nconn < maxnconn && nlefttoconn > 0) {
/* 4find a file to read */
for (i = 0 ; i < nfiles; i++)
if (file[i].f_flags == 0)
break;
if (i == nfiles)
err_quit("nlefttoconn = %d but nothing found", nlefttoconn);
start_connect(&file[i]);
nconn++;
nlefttoconn--;
} rs = rset;
ws = wset;
n = Select(maxfd+1, &rs, &ws, NULL, NULL); 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) {
err_ret("nonblocking connect failed for %s",
file[i].f_name);
}
/* 4connection 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) {
printf("end-of-file on %s\n", file[i].f_name);
Close(fd);
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);
}
/* end web2 */
这个例子中红色部分由两点没有优化(以避免使它变得更复杂)。首先,处理完select返回的已就绪的描述字后可以终止for循环。其次,可以尽可能地减少maxfd的值,省下select检查那些不再设置的描述字的时间。因为这段代码处理的描述字的数目在任何时候都小于10,而不是成千上万,所以这些优化与附加的复杂性相比是否值得,令人怀疑。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15.6 非阻塞accept
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct linger ling;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli ");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
ling.l_onoff = 1; /* cause RST to be sent on close() */
ling.l_linger = 0;
Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
Close(sockfd);
exit(0);
}
程序部分:
ling.l_onoff = 1; /* cause RST to be sent on close() */
ling.l_linger = 0;
Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
一旦连接建立,我们设置SO_LINGER选项,把l_onoff标志置为1,l_linger时间置为0,这样会导致在关闭连接时在TCP套接口上发送一个RST。我们随后关闭该套接口。
假如服务器的部分代码为:
if (FD_ISSET(listenfd, &rset)) {
+ printf(“listening socket readable\n”);
+ sleep(5)
client = sizeof(cliaddr);
connfd = Accept(listenfd, (SA * ) &cliaddr , &clien);
这里模拟的是一个繁忙的服务器,它不能做到在select一返回套接口可读时就调用accept。服务器的这种延迟一般情况下不会有问题(实际上这是为什么维护一个已完成连接队列的原因),但是如果在连接建立后又有从客户来的RST,就会出现问题。
服务器会一直阻塞在accept调用上,直到其他某个客户建立一个连接为止。但是在这期间,假定这个服务器由于某种原因仍会阻塞在accept调用上,不处理任何其他已就绪的描述字。
解决这个问题的办法是:
1 如果用select来获知何时有连接已就绪可以accept时,总是把监听套接口置为非阻塞,并且
2 在后面的accept调用中忽略以下错误:EWOULDBLOCK(源自Berkeley的实现在客户放弃连接时出现的错误)。ECONNABORTED(Posix.1g的实现在客户放弃连接时出现的错误)、EPROTO(SVR4的实现在客户放弃连接时出现的错误)和EINTR(如果信号被捕获)。
阅读(362) | 评论(0) | 转发(0) |