非阻塞 Connect

1.非阻塞Connect有什么用?

  1. 可以让三路握手的处理等同与一般数据的处理,而不是一直让 connect一直尝试重连或者花费一个RTT时间。而且RTT时间从几毫秒到几秒不等,万一有许多连接,不论是尝试重连还是花费一个RTT时间,都将是致命的延时。
  2. 可以使用该技术同时建立多个连接。Web浏览器中常用。
  3. 既然使用select等待连接的建立,我们就可以质地不嗯一个时间限制,使得我们能够缩短connect的超时。

2.必须去处理的细节:

  1. 处理connect立即建立的情况。(比如我们连接的是同一个主机时)
  2. 使用selcet与非阻塞connect的一些注意事项:

    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函数 。

<1> getsockopt函数说明:获取某个套接字关联的选项


       int getsockopt(int socket, int level, int option_name,
           void *restrict option_value, socklen_t *restrict option_len);
  1. getcoksoptsetsockopt都只用于套接字
  2. level指定系统中解释选项的代码或为通用套接字代码,或为特定于某个协议的代码 。
  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

所以在我们的代码中,我们将这两种情况都进行处理 。

<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 客户程序

??先获取一个主页,然后并行多个连接获取主页的其他网络资源。很显然,这样子的并行连接序列要比串行获取资源来的快。

  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

讨论:

原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/9611086.html

时间: 2024-10-12 08:07:57

非阻塞 Connect的相关文章

时间获取程序客户端 TCP 使用非阻塞connect

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie /** * TCP,使用非阻塞 connect **/ #include "unp.h" int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec) { int flags, n, error; socklen_t len; fd_set rset, wset; struct timeval t

非阻塞connect

步骤1: 设置非阻塞,启动连接 实现非阻塞 connect ,首先把 sockfd 设置成非阻塞的.这样调用 connect 可以立刻返回,根据返回值和 errno 处理三种情况: (1) 如果返回 0,表示 connect 成功. (2) 如果返回值小于 0, errno 为 EINPROGRESS, 表示连接 建立已经启动但是尚未完成.这是期望的结果,不是真正的错误. (3) 如果返回值小于0,errno 不是 EINPROGRESS,则连接出错了. 步骤2:判断可读和可写 然后把 sock

面向连接的socket数据处理过程以及非阻塞connect问题

对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET)在读写数据之前必须建立连接,首先服务器端socket必须在一个客户端知道的地址进行监听,也就是创建socket之后必须调用bind绑定到一个指定的地址,然后调用int listen(int sockfd, int backlog);进行监听.此时服务器socket允许客户端进行连接,backlog提示没被accept的客户连接请求队列的大小,系统决定实际的值,最大值定义为SOMAXCONN在头文件<sys/so

linux 非阻塞 connect函数

开发测试环境:虚拟机CentOS,windows网络调试助手        非阻塞模式有3种用途        1.三次握手同时做其他的处理.connect要花一个往返时间完成,从几毫秒的局域网到几百毫秒或几秒的广域网.这段时间可能有一些其他的处理要执行,比如数据准备,预处理等.        2.用这种技术建立多个连接.这在web浏览器中很普遍.        3.由于程序用select等待连接完成,可以设置一个select等待时间限制,从而缩短connect超时时间.多数实现中,connec

由select/epoll返回的非阻塞connect还会是EINPROGRESS状态吗?

一般情况下,我们像下面代码中所示的这样使用非阻塞connect: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

(转)非阻塞Connect对于select时应注意问题

对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET)在读写数据之前必须建立连接,首先服务器端socket必须在一个客户端知道的地址进行监听,也就是创建socket之后必须调用bind绑定到一个指定的地址,然后调用int listen(int sockfd, int backlog);进行监听.此时服务器socket允许客户端进行连接,backlog提示没被accept的客户连接请求队列的大小,系统决定实际的值,最大值定义为SOMAXCONN在头文件<sys/so

TCP非阻塞accept和非阻塞connect

http://blog.chinaunix.net/uid-20751538-id-238260.html 非阻塞accept 当一个已完成的连接准备好被accept的时候,select会把监听socket标记为可读.因此,如果用select等待外来的连接时,应该不需要 把监听socket设置为非阻塞模式,因为如果select告诉我们连接已经就绪,accept就不应该被阻塞.不过这样做的时候有一个BUG:当客户端 在跟服务器建立连接之后发送了一个RST包,这个时候accept就会阻塞,直到有下一

TCP之非阻塞connect和accept

套接字的默认状态是阻塞的,这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待响应操作完成,可能阻塞的套接字调用可分为以下四类: (1) 输入操作,包括read,readv,recv,recvfrom,recvmsg: (2) 输出操作,包括write,writev,send,sendto,sendmsg: (3) 接受外来连接,即accept函数. (4) 发起外出连接,即tcp的connect函数: 非阻塞connect: 当一个非阻塞的tcp套接字上调用connect时

UNIX网络编程-非阻塞connect和非阻塞accept

1.非阻塞connect 在看了很多资料之后,我自己的理解是:在socket发起一次连接的时候,这个过程需要一段时间来将三次握手的过程走完,如果在网络状况不好或者是其他的一些情况下,这个过程需要比较长的时间,我们在连接之前将socket设置为非阻塞模式之后,调用connect函数之后,立即返回,如果成功返回0,如果不成功则返回EINPROGRESS,这个值表明连接正在进行,我们可以设置一个超时时间,然后在这个时间段内不停的检查socket是否连接上了,如果在这个时间段内还没有连上,则返回失败.在

非阻塞connect的作用及代码示例

connect 函数的调用涉及到TCP连接的三次握手过程,通常阻塞的connect 函数会等待三次握手成功或失败后返回,0成功,-1失败.如果对方未响应,要隔6s,重发尝试,可能要等待75s的尝试并最终返回超时,才得知连接失败.即使是一次尝试成功,也会等待几毫秒到几秒的时间,如果此期间有其他事务要处理,则会白白浪费时间,而用非阻塞的connect 则可以做到并行,提高效率. 而通常,非阻塞的connect 函数与 select 函数配合使用:在一个TCP套接口被设置为非阻塞之后调用connect