第十六章非阻塞connect实例 与 非阻塞accept

非阻塞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个文件名

//程序主函数:
#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值