非阻塞connect: Web客户程序
客户先建立一个与某个Web服务器的HTTP连接,再获取一个主页(homepage)。该主页往往含有多个对于其他网页的引用,客户可以使用非阻塞connect同时获取多个网页,以此取代每次只获取一个网页的串行获取手段。
在处理web客户时,第一个连接独立执行,来自该连接的数据含有多个引用,随后用于访问这些引用的多个连接则并行执行
这里因为要处理多个非阻塞connect,就不能再使用上述connect_nonb函数。
我们的程序会按如下形式运行:
% web 3 www.xxxx.com /index.html image1.gif image2.gif image3.gif image4.gif image5.gif image6.gif image7.gif
参数分别是:指定并行执行最多3个、服务器主机名、主页文件名以及随后的7个文件(这里为方便使用7该GIF文件,这7个文件通常在主页中被引用)
现实的Web客户将读取指定主页并通过分析HTML获悉这些文件名。但我们不想因HTML分析而复杂,于是直接指定了这7个文件名
这里有几个地方说一下
1、调用select之前我们可以设置变量来代替rset和wset,防止他们本身发生变化从而导致以后每次调用select都要初始化他们。并且我们可以在select之后的处理过程中对最初的rset和wset进行调整
2、select返回的n个文件处理完之后,我们可以结束select之后的for循环,这里没有这样处理
3、maxfd的值也可以动态的改变,这里也没有
4、我们在后续的(第一个之后)连接套接字都设置为非阻塞后不再还原,那会不会对write或read造成影响呢。read不会有影响,因为后续的都是在循环中度过,总会读完。write也基本上不会,因为我们只是发送少量数据,即使会发生影响,也因为我们调用的是自己的written函数而将影响克服了
以下是written函数
还有就是, 如果网络中存在拥塞,那么这个计数就会有缺陷。
当一个客户到一个服务器建立多个连接的时候,这些连接在TCP层没有通信。即其中一个遇到分组丢失(隐式指示网络拥塞),其他连接也不会得到通知,这种情况下这些连接可能马上会遇到分组丢失,除非他们事先得到通知慢下来。
这些额外的连接是在往已经拥塞的网络中发送更多的分组,这个计数还会增加服务器主机的负荷。
非阻塞accept
在服务器中,当有一个已完成的连接准备好被accept时,select将作为可读描述符返回该连接的监听套接字。 那么这就意味着,select既然告知了我们套接字已就绪,接下来的accept就不应该阻塞。
不幸的是,在一个繁忙的服务器中,它无法在select返回可读条件后立马调用accept,这期间客户如果发出RST数据报,问题也就随之出现了,大致步骤如下:
1、服务器从select返回到调用accept期间,服务器TCP接收到来自客户的RST
2、这个已完成的连接被TCP驱除出队列,我们假设队列中没有已完成的连接
3、 服务器调用accept,由于没有任何已完成的连接,于是阻塞
解决办法如下:
1、当使用select获悉某个监听套接字上何时有已完成连接准备好被accept时,总是把这个监听套接字设置为非阻塞
2、在后续的accept中忽略以下错误:
EWOULDBLOCK(Berkeley实现客户终止连接错误)、ECONNABORTED(POSIX实现客户终止)、EPROTO(SVR4)和EINTR
客户先建立一个与某个Web服务器的HTTP连接,再获取一个主页(homepage)。该主页往往含有多个对于其他网页的引用,客户可以使用非阻塞connect同时获取多个网页,以此取代每次只获取一个网页的串行获取手段。
在处理web客户时,第一个连接独立执行,来自该连接的数据含有多个引用,随后用于访问这些引用的多个连接则并行执行
这里因为要处理多个非阻塞connect,就不能再使用上述connect_nonb函数。
我们的程序会按如下形式运行:
% web 3 www.xxxx.com /index.html image1.gif image2.gif image3.gif image4.gif image5.gif image6.gif image7.gif
参数分别是:指定并行执行最多3个、服务器主机名、主页文件名以及随后的7个文件(这里为方便使用7该GIF文件,这7个文件通常在主页中被引用)
现实的Web客户将读取指定主页并通过分析HTML获悉这些文件名。但我们不想因HTML分析而复杂,于是直接指定了这7个文件名
//程序主函数:
#include "web.h"
int main(int ac, char *av[])
{
int i; //used for loop
int maxconn; //the max connects that can be excuted at the same time
fd_set rs,ws; //used to make the code more clear (used in select)
int n; //used to get the result of select
int flags,fd; //just for convenience
int error,len; //just like nonb_connect
char buf[MAXLEN];
if(ac < 5)
{
fprintf(stderr,"Usage: web <#conns> <hostname> <homepage> <file1> ...");
exit(22);
}
maxconn = atoi(av[1]);
nfiles = min(ac-4, MAXFILES);
for(i=0; i<nfiles; i++) //initialization
{
file[i].f_name = av[4+i];
file[i].f_host = av[2];
file[i].f_flags = 0; //wait to become F_CONNECTING
}
printf("you have %d files to read\n",nfiles);
home_page(av[2],av[3]); //get the homepage
FD_ZERO(&rset);
FD_ZERO(&wset);
maxfd = -1;
nlefttoconn = nlefttoread = nfiles;
nconn = 0;
//start
while(nlefttoconn > 0)
{
//first we have to set nonblocking connecting
while(nconn < maxconn && nlefttoconn > 0)
{
for(i=0;i<nfiles;i++)
if(file[i].f_flags == 0)
break;
if(nfiles == i)
{
fprintf(stderr,"no file waiting to be connected but nlefttoconn is not 0");
exit(11);
}
start_connect(&file[i]);
nconn++;
nlefttoconn--;
}
//after calling connect() for every file
rs = rset;
ws = wset;
n = select(maxfd+1,&rs,&ws,NULL,NULL);
if(n < 0)
oops("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))) //remember nonb_connect ?
{
len = sizeof(error);
if(getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&len) < 0 || error !=0)
{
fprintf(stderr,"nonblock connect failed for this file %s\n",file[i].f_name);
continue;
}
//connection established
printf("connection established for %s\n",file[i].f_name);
//we would never have to write to this socket
FD_CLR(fd,&wset); //pay attention! here we address "wset" not "ws"
write_get_cmd(&file[i]); //in this function, we'll change the flags into F_READING
}
else if(flags&F_READING && FD_ISSET(fd,&rs)) //can be read
{
if((n=read(fd,buf,MAXLEN)) < 0)
{
if(errno != EWOULDBLOCK)
oops("read error");
}
else if(n == 0)
{
printf("end-of-file on %s\n",file[i].f_name);
close(fd);
file[i].f_flags = F_DONE;
//it is over so we have to clear it
FD_CLR(fd,&rset);
nlefttoread--;
nconn--;
}
else
printf("read %d bytes for %s\n",n,file[i].f_name);
}
}
}
return 0;
}
//头文件 web.h
#include "unp.h"
#define MAXFILES 20
#define SERV "13000" //port number
struct file
{
char *f_name; //filename
char *f_host; //hostname or address
int f_fd; //file descriptor
int f_flags; //F_CONNECTING or F_READING or F_DONE
}file[MAXFILES];
#define F_CONNECTING 1 //in connect() progress
#define F_READING 2 //after connecting and start to read()
#define F_DONE 4 //finish
#define GET_CMD "GET %s HTTP/1.0\r\n\r\n"
//global variables
int nconn; //the number of the connects that we have already call connect()
int nfiles; //the number of files that the client has to read from server
int nlefttoconn, nlefttoread, maxfd;
fd_set rset, wset;
//functions
void home_page(char *, char *); //used to read homepage from the server with blocking connect
void start_connect(struct file *); //use nonblocking connecting
void write_get_cmd(struct file *); //send a HTTP command to server
//web.c为web.h中的函数实现
#include "web.h"
void home_page(char *host, char *fname)
{
int fd, n;
char buf[MAXLEN];
fd = tcp_connect(host, SERV); //common connect (blocking connect)
n = snprintf(buf,sizeof(buf),GET_CMD,fname); //form the command
written(fd, buf, n); //send the command
for(;;)
{
if((n=read(fd,buf,MAXLEN)) < 0)
{
if(errno != EINTR)
exit(66);
else
continue;
}
else if(n==0)
break; //server close the connection
printf("read %d bytes of homepage",n);
}
printf("end-of-file on homepage\n");
close(fd);
}
void start_connect(struct file *fptr) //read file from the host with O_NONBLOCK connecting
{
int fd;
struct addrinfo *info;
int flag,res;
info = host_serv(fptr->f_host,SERV,0,SOCK_STREAM);
if((fd=socket(AF_INET,SOCK_STREAM,0))<0)
oops("socket error");
fptr->f_fd = fd;
printf("start connect for %s, fd %n\n",fptr->f_name,fptr->f_fd);
//set descriptor O_NONBLOCK
flag = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);
//start noblocking connecting
if((res=connect(fd,info->ai_addr,info->ai_addrlen)) < 0) //EINPROGRESS is expected error for nonblocking connect
{
if(errno != EINPROGRESS)
{
perror("nonblocking connect error");
exit(11);
}
//since it is still trying to connect ,we'll use select to test.
fptr->f_flags = F_CONNECTING; //set state
FD_SET(fd,&rset); //used for select below
FD_SET(fd,&wset);
if(maxfd < fd)
maxfd = fd;
}
else if(res >= 0) //connect is already done
write_get_cmd(fptr); //since it is ready ,then send command and try to get file
}
void write_get_cmd(struct file *fptr)
{
int n;
char buf[MAXLEN];
n = snprintf(buf,sizeof(buf),GET_CMD,fptr->f_name);
written(fptr->f_fd, buf, n);
printf("wrote %d bytes for the file %s\n",n,fptr->f_name);
fptr->f_flags = F_READING; //replace F_CONNECTING and set F_READING
FD_SET(fptr->f_fd,&rset); //use select to wait until it can be read
if(maxfd < fptr->f_fd)
maxfd = fptr->f_fd;
}
这里有几个地方说一下
1、调用select之前我们可以设置变量来代替rset和wset,防止他们本身发生变化从而导致以后每次调用select都要初始化他们。并且我们可以在select之后的处理过程中对最初的rset和wset进行调整
2、select返回的n个文件处理完之后,我们可以结束select之后的for循环,这里没有这样处理
3、maxfd的值也可以动态的改变,这里也没有
4、我们在后续的(第一个之后)连接套接字都设置为非阻塞后不再还原,那会不会对write或read造成影响呢。read不会有影响,因为后续的都是在循环中度过,总会读完。write也基本上不会,因为我们只是发送少量数据,即使会发生影响,也因为我们调用的是自己的written函数而将影响克服了
以下是written函数
还有就是, 如果网络中存在拥塞,那么这个计数就会有缺陷。
当一个客户到一个服务器建立多个连接的时候,这些连接在TCP层没有通信。即其中一个遇到分组丢失(隐式指示网络拥塞),其他连接也不会得到通知,这种情况下这些连接可能马上会遇到分组丢失,除非他们事先得到通知慢下来。
这些额外的连接是在往已经拥塞的网络中发送更多的分组,这个计数还会增加服务器主机的负荷。
非阻塞accept
在服务器中,当有一个已完成的连接准备好被accept时,select将作为可读描述符返回该连接的监听套接字。 那么这就意味着,select既然告知了我们套接字已就绪,接下来的accept就不应该阻塞。
不幸的是,在一个繁忙的服务器中,它无法在select返回可读条件后立马调用accept,这期间客户如果发出RST数据报,问题也就随之出现了,大致步骤如下:
1、服务器从select返回到调用accept期间,服务器TCP接收到来自客户的RST
2、这个已完成的连接被TCP驱除出队列,我们假设队列中没有已完成的连接
3、 服务器调用accept,由于没有任何已完成的连接,于是阻塞
解决办法如下:
1、当使用select获悉某个监听套接字上何时有已完成连接准备好被accept时,总是把这个监听套接字设置为非阻塞
2、在后续的accept中忽略以下错误:
EWOULDBLOCK(Berkeley实现客户终止连接错误)、ECONNABORTED(POSIX实现客户终止)、EPROTO(SVR4)和EINTR