非阻塞 Connect

1.非阻塞Connect有什麼用?

  1. 可讓三路握手的處理等同與通常數據的處理,而不是一直讓 connect一直嘗試重連或者花費一個RTT時間。並且RTT時間從幾毫秒到幾秒不等,萬一有許多鏈接,不管是嘗試重連仍是花費一個RTT時間,都將是致命的延時。
  2. 可使用該技術同時創建多個鏈接。Web瀏覽器中經常使用。
  3. 既然使用select等待鏈接的創建,咱們就能夠質地不嗯一個時間限制,使得咱們可以縮短connect的超時。

2.必須去處理的細節:

  1. 處理connect當即創建的狀況。(好比咱們鏈接的是同一個主機時)
  2. 使用selcet與非阻塞connect的一些注意事項:web

    2.1. 當鏈接成功創建後,描述符變爲可寫。
    2.2 當遇到錯誤時,描述符變爲便可寫又可讀。瀏覽器

3. 兩個例子:

(1)非阻塞connect:時間獲取客戶程序

int Connect_nonblock(int sockfd, const SA *saptr, socklen_t salen, int nsec) //返回 -1 失敗
{
    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);
    }
    if (n == 0) //表示鏈接創建 當即完成
        goto done;

    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)//返回0,超時,關閉套接字
    {
        Close(sockfd);
        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);
    }
    else
        err_quit("selcet error :sockfd not set\n");
done: //直到創建才返回

    Fcntl(sockfd, F_SETFL, flags);

    if (error)
    {
        Close(sockfd);
        errno = error;
        return (-1);
    }
    return 0; //成功鏈接
}

一些說明:

  其實比較簡單,就是connect去鏈接,若是可以連上就鏈接便可,若是沒有連上就讓select看成通常數據去處理便可!對於鏈接,select有兩種狀況,成功就是可寫,失敗便可讀又可寫。那麼問題來了?網絡

  如何去判斷成功仍是失敗吶?emmmm,所謂的失敗就是發生了錯誤,那麼咱們直接檢測是否有錯誤便可 。使用getsockopt函數 。socket

<1> getsockopt函數說明:獲取某個套接字關聯的選項

int getsockopt(int socket, int level, int option_name,
           void *restrict option_value, socklen_t *restrict option_len);
  1. getcoksoptsetsockopt都只用於套接字tcp

  2. level指定系統中解釋選項的代碼或爲通用套接字代碼,或爲特定於某個協議的代碼 。svg

  3. option_value將已獲取的選項當前值,存放在*option_value中,option_len*option_value的大小 。函數

  4. option_name表明選項 。測試

返回值:
RETURN VALUE
       Upon  successful  completion,  getsockopt()  shall  return 0; otherwise, −1 shall be returned and errno set to indicate the
       error.
  1. Berkeley系統中:在*option_value中返回待處理錯誤,函數返回 0
  2. Solaris系統中:將errno置爲待處理錯誤,函數返回 -1

因此在咱們的代碼中,咱們將這兩種狀況都進行處理 。ui

<2>測試:

int main(int argc, char **argv)
{
    int sockfd, n;
    char recvline[MAXLINE + 1] = {0};
    struct sockaddr_in servaddr;

    if (argc != 2)
        err_quit("usage: a.out <IPaddress>");

    if ((sockfd = Socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_sys("Socket error");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(13); /* daytime server */
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
        err_quit("inet_pton error for %s", argv[1]);

    if (Connect_nonblock(sockfd, (SA *)&servaddr, sizeof(servaddr), 10) < 0)
        err_sys("connect error");

    while ((n = recv(sockfd, recvline, MAXLINE, 0)) > 0)
    {
        recvline[n] = 0; /* null terminate */
        printf("recvline == %s\n", recvline);
    }
    if (n < 0)
        err_sys("read error");
    return 0;
}

這裏寫圖片描述

(2)非阻塞Connect : Web 客戶程序

  先獲取一個主頁,而後並行多個鏈接獲取主頁的其餘網絡資源。很顯然,這樣子的並行鏈接序列要比串行獲取資源來的快。spa

  1. 結構體設計
#define MAXFILES 20
#define SERV "80"
struct file
{
    char *f_name; //資源路徑
    char *f_host; //主機
    int f_fd;//套接字
    int f_flags; //當前狀態,有四種值,分別是 { 0, F_CONNECTING, F_READING, F_DONE }
}
file[MAXFILES];
  1. 大體思路:
// 假設咱們下載 10 資源
初始化 struct file files[10];

先成功創建第一個鏈接(獲取主頁)

while(xxx) {
   使用非阻塞I/O, 同時創建多個鏈接,每個 f_flags = F_CONNECTING.
   select 監聽套接字
   for (f in files) { // 遍歷全部文件
     if (f.f_flags == F_CONNECTING) {
       // 檢查鏈接是否成功或失敗。使用咱們上面用到的知識,主要是 getsockopt 函數
       若是鏈接成功,則發起 GET 請求,同時 f_flags = F_READING.
       若是鏈接失敗,f_flags = F_DONE;
     }
     else if (f.f_flags == F_READING) {
       // 下載資源
       nr = read(f.f_fd, buf);
       if (nr == 0) {
         對端關閉, f.f_flags = F_DONE;
       }
     }
   }
}

web.h文件

#ifndef _WEB_H
#define _WEB_H

#include "../myhead.h"

#define MAXFILES 20
#define SERV "80"
struct file
{
    char *f_name;
    char *f_host;
    int f_fd;
    int f_flags;
}
file[MAXFILES];

#define F_CONNECTING 1
#define F_READING 2
#define F_DONE 4

#define GET_CMD "GET %s HTTP/1.0\r\n\r\n"

int nconn, nfiles, nlefttoconn, nlefttoread, maxfd;
fd_set rset, wset;
/* nconn:當前打開的鏈接數,不超過第一個命令行參數 nlefttoread:待讀取的文件數量 nlefttoconn:還沒有鏈接的文件數 nfiles:文件數量 */
#endif

web.c文件

#include "web.h"

struct addrinfo *Host_serv(const char *host, const char *serv, int family, int socktype);
void home_pages(const char *host, const char *fname);
void start_connect(struct file *fptr); //非阻塞鏈接;
void write_get_cmd(struct file *fptr);

int Tcp_connect(const char *host, const char *serv)
{
    int sockfd, n;
    struct addrinfo hints, *res, *ressave;

    bzero(&hints, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
        err_quit("tcp_connect error for %s ,%s,%s : %s", host, serv, gai_strerror(n));
    ressave = res;
    do
    {
        sockfd = Socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (sockfd < 0)
            continue;
        if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
            break;
        Close(sockfd);
    } while ((res = res->ai_next) != NULL);
    if (res == NULL)
        err_sys("tcp_coonnect error for %s,%s", host, serv);
    freeaddrinfo(ressave);
    return (sockfd);
}

struct addrinfo *Host_serv(const char *host, const char *serv, int family, int socktype)
{
    int n;
    struct addrinfo hints, *res;
    bzero(&hints, sizeof(struct addrinfo));
    hints.ai_flags = AI_CANONNAME;
    hints.ai_family = family;
    hints.ai_socktype = socktype;

    if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
        err_quit("host_serv error for %s, %s: %s",
                 (host == NULL) ? "(no hostname)" : host,
                 (serv == NULL) ? "(no service name)" : serv,
                 gai_strerror(n));
    return (res);
}

void home_pages(const char *host, const char *fname)
{
    int fd, n;
    char line[MAXLINE] = {0};
    fd = Tcp_connect(host, SERV);
    n = snprintf(line, sizeof(line), GET_CMD, fname);
    Sendlen(fd, line, n, 0);
    for (;;)
    {
        if ((n = Recvlen(fd, line, MAXLINE, 0)) == 0)
            break; //serv closed
        fprintf(stderr, "recv %d bytes from server \n", n);
    }
    fprintf(stderr, "end-of-home-pages\n");
    Close(fd);
}
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;

    fprintf(stderr, "start_connect for %s ,fd %d \n", fptr->f_name, fd);

    flags = Fcntl(fd, F_GETFL, 0);
    Fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    if ((n = connect(fd, ai->ai_addr, ai->ai_addrlen)) < 0)
    {
        if (errno != EINPROGRESS) // EINPROGRESS套接字爲非阻塞套接字,且鏈接請求沒有當即完成
            err_sys("nonblocking connect error ", __LINE__);
        fptr->f_flags = F_CONNECTING;
        FD_SET(fd, &rset);
        FD_SET(fd, &wset);
        if (fd > maxfd)
            maxfd = fd;
    }
    else if (n >= 0)
    { /* connect is already done */
        write_get_cmd(fptr);
    }
}
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);

    fprintf(stderr, "send %d bytes for %s \n\n\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;
}

int main(int argc, char **argv)
{
    int i, fd, n, maxconn, flags, error;
    char buf[MAXLINE] = {0};
    fd_set rs, ws;
    if (argc < 5)
    {
        fprintf(stderr, "use :web conns hostname homepages files.....");
        return 0;
    }
    maxconn = 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;
    }

    fprintf(stderr, "nfiles == %d \n", nfiles);

    home_pages(argv[2], argv[3]); //創建第一個鏈接

    FD_ZERO(&rset);
    FD_ZERO(&wset);

    maxfd = -1;
    nlefttoread = nlefttoconn = nfiles;
    nconn = 0;

    /* nconn :當前打開的鏈接數,不超過第一個命令行參數 nlefttoread:待讀取的文件數量 nlefttoconn:還沒有鏈接的文件數 nfiles:文件數量 */
    while (nlefttoread > 0)
    {
        while (nconn < maxconn && 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);
                    file[i].f_flags = F_DONE;
                }
                /* 4connection established */
                fprintf(stderr, "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)
                {
                    fprintf(stderr, "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
                {
                    fprintf(stderr, "read %d bytes from %s\n", n, file[i].f_name);
                }
            }
        }
    }
    exit(0);
}

測試:

這是最大並行鏈接數是3時的狀況:

這裏寫圖片描述

附錄:

1. connect函數說明(總結unp connect 便可 )

  1. connect 激發TCP的三路握手過程,並且僅在鏈接創建成功或者出錯後纔會返回。
  2. 在一個非阻塞的套接字上調用 connect 時,connect將當即返回一個EINPROGRESS錯誤,不過三路握手會繼續進行。而後咱們經過select去檢測該鏈接成功或者失敗。

  3. 若是connect鏈接失敗,則該套接字不能再用,必須關閉! 不能對這樣的套接字再次調用connect

討論:

相關文章
相關標籤/搜索