一个当真的客户端非阻塞的 connect一个真正的客户端非阻塞的 connect

序言  – 一个简便开场白 

前言  – 一个粗略开场白 

  winds 的 select 和 linux 的 select 是鲜单完全不同的东西.
然而凡人好将她揉在一起.

  winds 的 select 和 linux 的 select 是少单意不同的东西.
然而凡人喜爱将她揉在一起.

非阻塞的connect业务是单自带超时机制的 connect.
实现机制无外乎利用select(也出 epoll的).

非阻塞的connect业务是个自带超时机制的 connect.
实现机制无外乎利用select(也产生 epoll的).

正文是个源码软文, 专注解决客户端的跨平台的connect问题. 服务器的connect
要比较客户端多着想一丁点.

正文是个源码软文, 专注解决客户端的跨平台的connect问题. 服务器的connect
要比较客户端多着想一丁点.

产生会又扯. 对于 select 网上资料太多, 几乎都生接触不痛不痒. 了解真相推荐
man and msdn !!!

起机会再次扯. 对于 select 网上资料太多, 几乎都来硌不痛不痒. 了解本质推荐
man and msdn !!!

 

 

正好文 – 所有的且亟待前戏

刚好文 – 所有的还需要前戏

那起来吧 .  一切由丑陋之跨平台宏开始

那么开始吧 .  一切由丑陋之跨平台宏开始

#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <signal.h>

//
// IGNORE_SIGPIPE - 管道破裂,忽略SIGPIPE信号
//
#define IGNORE_SIGNAL(sig)    signal(sig, SIG_IGN)

#ifdef __GNUC__

#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/select.h>
#include <sys/resource.h>

/*
* This is used instead of -1, since the
* SOCKET type is unsigned.
*/
#define INVALID_SOCKET      (~0)
#define SOCKET_ERROR        (-1)

#define IGNORE_SIGPIPE()    IGNORE_SIGNAL(SIGPIPE)

// connect链接还在进行中, linux显示 EINPROGRESS,winds是 WSAEWOULDBLOCK
#define ECONNECTED          EINPROGRESS

typedef int socket_t;

#elif _MSC_VER

#undef    FD_SETSIZE
#define FD_SETSIZE          (1024)
#include <ws2tcpip.h>

#undef    errno
#define   errno              WSAGetLastError()

#define IGNORE_SIGPIPE()

// connect链接还在进行中, linux显示 EINPROGRESS,winds是 WSAEWOULDBLOCK
#define ECONNECTED           WSAEWOULDBLOCK

typedef int socklen_t;
typedef SOCKET socket_t;

static inline void _socket_start(void) {
    WSACleanup();
}

#endif

// 目前通用的tcp udp v4地址
typedef struct sockaddr_in sockaddr_t;

//
// socket_start    - 单例启动socket库的初始化方法
// socket_addr    - 通过ip, port 得到 ipv4 地址信息
// 
inline void socket_start(void) {
#ifdef _MSC_VER
#    pragma comment(lib, "ws2_32.lib")
    WSADATA wsad;
    WSAStartup(WINSOCK_VERSION, &wsad);
    atexit(_socket_start);
#endif
    IGNORE_SIGPIPE();
}
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <signal.h>

//
// IGNORE_SIGPIPE - 管道破裂,忽略SIGPIPE信号
//
#define IGNORE_SIGNAL(sig)    signal(sig, SIG_IGN)

#ifdef __GNUC__

#include <fcntl.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/select.h>
#include <sys/resource.h>

/*
* This is used instead of -1, since the
* SOCKET type is unsigned.
*/
#define INVALID_SOCKET      (~0)
#define SOCKET_ERROR        (-1)

#define IGNORE_SIGPIPE()    IGNORE_SIGNAL(SIGPIPE)

// connect链接还在进行中, linux显示 EINPROGRESS,winds是 WSAEWOULDBLOCK
#define ECONNECTED          EINPROGRESS

typedef int socket_t;

#elif _MSC_VER

#undef    FD_SETSIZE
#define FD_SETSIZE          (1024)
#include <ws2tcpip.h>

#undef    errno
#define   errno              WSAGetLastError()

#define IGNORE_SIGPIPE()

// connect链接还在进行中, linux显示 EINPROGRESS,winds是 WSAEWOULDBLOCK
#define ECONNECTED           WSAEWOULDBLOCK

typedef int socklen_t;
typedef SOCKET socket_t;

static inline void _socket_start(void) {
    WSACleanup();
}

#endif

// 目前通用的tcp udp v4地址
typedef struct sockaddr_in sockaddr_t;

//
// socket_start    - 单例启动socket库的初始化方法
// socket_addr    - 通过ip, port 得到 ipv4 地址信息
// 
inline void socket_start(void) {
#ifdef _MSC_VER
#    pragma comment(lib, "ws2_32.lib")
    WSADATA wsad;
    WSAStartup(WINSOCK_VERSION, &wsad);
    atexit(_socket_start);
#endif
    IGNORE_SIGPIPE();
}

这时候再度装进一些,  简化操作. 

这再次包一些,  简化操作. 

inline socket_t socket_stream(void) {
    return socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
}

inline int socket_close(socket_t s) {
#ifdef _MSC_VER
    return closesocket(s);
#else
    return close(s);
#endif
}

inline int socket_set_block(socket_t s) {
#ifdef _MSC_VER
    u_long mode = 0;
    return ioctlsocket(s, FIONBIO, &mode);
#else
    int mode = fcntl(s, F_GETFL, 0);
    if (mode == SOCKET_ERROR)
        return SOCKET_ERROR;
    if (mode & O_NONBLOCK)
        return fcntl(s, F_SETFL, mode & ~O_NONBLOCK);
    return 0;
#endif    
}

inline int socket_set_nonblock(socket_t s) {
#ifdef _MSC_VER
    u_long mode = 1;
    return ioctlsocket(s, FIONBIO, &mode);
#else
    int mode = fcntl(s, F_GETFL, 0);
    if (mode == SOCKET_ERROR)
        return SOCKET_ERROR;
    if (mode & O_NONBLOCK)
        return 0;
    return fcntl(s, F_SETFL, mode | O_NONBLOCK);
#endif    
}

inline int socket_connect(socket_t s, const sockaddr_t * addr) {
    return connect(s, (const struct sockaddr *)addr, sizeof(*addr));
}
inline socket_t socket_stream(void) {
    return socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
}

inline int socket_close(socket_t s) {
#ifdef _MSC_VER
    return closesocket(s);
#else
    return close(s);
#endif
}

inline int socket_set_block(socket_t s) {
#ifdef _MSC_VER
    u_long mode = 0;
    return ioctlsocket(s, FIONBIO, &mode);
#else
    int mode = fcntl(s, F_GETFL, 0);
    if (mode == SOCKET_ERROR)
        return SOCKET_ERROR;
    if (mode & O_NONBLOCK)
        return fcntl(s, F_SETFL, mode & ~O_NONBLOCK);
    return 0;
#endif    
}

inline int socket_set_nonblock(socket_t s) {
#ifdef _MSC_VER
    u_long mode = 1;
    return ioctlsocket(s, FIONBIO, &mode);
#else
    int mode = fcntl(s, F_GETFL, 0);
    if (mode == SOCKET_ERROR)
        return SOCKET_ERROR;
    if (mode & O_NONBLOCK)
        return 0;
    return fcntl(s, F_SETFL, mode | O_NONBLOCK);
#endif    
}

inline int socket_connect(socket_t s, const sockaddr_t * addr) {
    return connect(s, (const struct sockaddr *)addr, sizeof(*addr));
}

 

 

大局的测试中心main 函数有之类

全局的测试中心main 函数片之类

extern int socket_addr(const char * ip, uint16_t port, sockaddr_t * addr);
extern int socket_connecto(socket_t s, const sockaddr_t * addr, int ms);
extern socket_t socket_connectos(const char * host, uint16_t port, int ms);


//
// gcc -g -O2 -Wall -o main.exe main.c
//
int main(int argc, char * argv[]) {
    socket_start();

    socket_t s = socket_connectos("127.0.0.1", 80, 10000);
    if (s == INVALID_SOCKET) {
        fprintf(stderr, "socket_connectos is error!!\n");
        exit(EXIT_FAILURE);
    }
    puts("socket_connectos is success!");

    return EXIT_SUCCESS;
}

int 
socket_addr(const char * ip, uint16_t port, sockaddr_t * addr) {
    if (!ip || !*ip || !addr) {
        fprintf(stderr, "check empty ip = %s, port = %hu, addr = %p.\n", ip, port, addr);
        return -1;
    }

    addr->sin_family = AF_INET;
    addr->sin_port = htons(port);
    addr->sin_addr.s_addr = inet_addr(ip);
    if (addr->sin_addr.s_addr == INADDR_NONE) {
        struct hostent * host = gethostbyname(ip);
        if (!host || !host->h_addr) {
            fprintf(stderr, "check ip is error = %s.\n", ip);
            return -1;
        }
        // 尝试一种, 默认ipv4
        memcpy(&addr->sin_addr, host->h_addr, host->h_length);
    }
    memset(addr->sin_zero, 0, sizeof addr->sin_zero);

    return 0;
}
extern int socket_addr(const char * ip, uint16_t port, sockaddr_t * addr);
extern int socket_connecto(socket_t s, const sockaddr_t * addr, int ms);
extern socket_t socket_connectos(const char * host, uint16_t port, int ms);


//
// gcc -g -O2 -Wall -o main.exe main.c
//
int main(int argc, char * argv[]) {
 socket_start();

 socket_t s = socket_connectos("127.0.0.1", 80, 10000);
 if (s == INVALID_SOCKET) {
  fprintf(stderr, "socket_connectos is error!!\n");
  exit(EXIT_FAILURE);
 }
 puts("socket_connectos is success!");

 return EXIT_SUCCESS;
}

int 
socket_addr(const char * ip, uint16_t port, sockaddr_t * addr) {
    if (!ip || !*ip || !addr) {
        fprintf(stderr, "check empty ip = %s, port = %hu, addr = %p.\n", ip, port, addr);
        return -1;
    }

    addr->sin_family = AF_INET;
    addr->sin_port = htons(port);
    addr->sin_addr.s_addr = inet_addr(ip);
    if (addr->sin_addr.s_addr == INADDR_NONE) {
        struct hostent * host = gethostbyname(ip);
        if (!host || !host->h_addr) {
            fprintf(stderr, "check ip is error = %s.\n", ip);
            return -1;
        }
        // 尝试一种, 默认ipv4
        memcpy(&addr->sin_addr, host->h_addr, host->h_length);
    }
    memset(addr->sin_zero, 0, sizeof addr->sin_zero);

    return 0;
}

 

 

此地才是若一旦的所有, 当真的跨平台的客户端非阻塞 connect.

此才是您要是之成套, 确的跨平台的客户端非阻塞 connect.

int
socket_connecto(socket_t s, const sockaddr_t * addr, int ms) {
    int n, r;
    struct timeval to;
    fd_set rset, wset, eset;

    // 还是阻塞的connect
    if (ms < 0) return socket_connect(s, addr);

    // 非阻塞登录, 先设置非阻塞模式
    r = socket_set_nonblock(s);
    if (r < 0) {
        fprintf(stderr, "socket_set_nonblock error!\n");
        return r;
    }

    // 尝试连接一下, 非阻塞connect 返回 -1 并且 errno == EINPROGRESS 表示正在建立链接
    r = socket_connect(s, addr);
    if (r >= 0) goto __return;

    // 链接不再进行中直接返回, linux是 EINPROGRESS,winds是 WASEWOULDBLOCK
    if (errno != ECONNECTED) {
        fprintf(stderr, "socket_connect error r = %d!\n", r);
        goto __return;
    }

    // 超时 timeout, 直接返回结果 ErrBase = -1 错误
    r = -1;
    if (ms == 0) goto __return;

    FD_ZERO(&rset); FD_SET(s, &rset);
    FD_ZERO(&wset); FD_SET(s, &wset);
    FD_ZERO(&eset); FD_SET(s, &eset);
    to.tv_sec = ms / 1000;
    to.tv_usec = (ms % 1000) * 1000;
    n = select((int)s + 1, &rset, &wset, &eset, &to);
    // 超时直接滚 or linux '异常'直接返回 0
    if (n <= 0) goto __return;

    // 当连接成功时候,描述符会变成可写
    if (n == 1 && FD_ISSET(s, &wset)) {
        r = 0;
        goto __return;
    }

    // 当连接建立遇到错误时候, winds 抛出异常, linux 描述符变为即可读又可写
    if (FD_ISSET(s, &eset) || n == 2) {
        socklen_t len = sizeof n;
        // 只要最后没有 error那就 链接成功
        if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char *)&n, &len) && !n)
            r = 0;
    }

__return:
    socket_set_block(s);
    return r;
}

socket_t
socket_connectos(const char * host, uint16_t port, int ms) {
    int r;
    sockaddr_t addr;
    socket_t s = socket_stream();
    if (s == INVALID_SOCKET) {
        fprintf(stderr, "socket_stream is error!\n");
        return INVALID_SOCKET;
    }

    // 构建ip地址
    r = socket_addr(host, port, &addr);
    if (r < 0)
        return r;

    r = socket_connecto(s, &addr, ms);
    if (r < 0) {
        socket_close(s);
        fprintf(stderr, "socket_connecto host port ms = %s, %u, %d!\n", host, port, ms);
        return INVALID_SOCKET;
    }

    return s;
}
int
socket_connecto(socket_t s, const sockaddr_t * addr, int ms) {
 int n, r;
 struct timeval to;
 fd_set rset, wset, eset;

 // 还是阻塞的connect
 if (ms < 0) return socket_connect(s, addr);

 // 非阻塞登录, 先设置非阻塞模式
 r = socket_set_nonblock(s);
 if (r < 0) {
  fprintf(stderr, "socket_set_nonblock error!\n");
  return r;
 }

 // 尝试连接一下, 非阻塞connect 返回 -1 并且 errno == EINPROGRESS 表示正在建立链接
 r = socket_connect(s, addr);
 if (r >= 0) goto __return;

 // 链接不再进行中直接返回, linux是 EINPROGRESS,winds是 WASEWOULDBLOCK
 if (errno != ECONNECTED) {
  fprintf(stderr, "socket_connect error r = %d!\n", r);
  goto __return;
 }

 // 超时 timeout, 直接返回结果 ErrBase = -1 错误
 r = -1;
 if (ms == 0) goto __return;

 FD_ZERO(&rset); FD_SET(s, &rset);
 FD_ZERO(&wset); FD_SET(s, &wset);
 FD_ZERO(&eset); FD_SET(s, &eset);
 to.tv_sec = ms / 1000;
 to.tv_usec = (ms % 1000) * 1000;
 n = select((int)s + 1, &rset, &wset, &eset, &to);
 // 超时直接滚 or linux '异常'直接返回 0
 if (n <= 0) goto __return;

 // 当连接成功时候,描述符会变成可写
 if (n == 1 && FD_ISSET(s, &wset)) {
  r = 0;
  goto __return;
 }

 // 当连接建立遇到错误时候, winds 抛出异常, linux 描述符变为即可读又可写
 if (FD_ISSET(s, &eset) || n == 2) {
  socklen_t len = sizeof n;
  // 只要最后没有 error那就 链接成功
  if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char *)&n, &len) && !n)
   r = 0;
 }

__return:
 socket_set_block(s);
 return r;
}

socket_t
socket_connectos(const char * host, uint16_t port, int ms) {
 int r;
 sockaddr_t addr;
 socket_t s = socket_stream();
 if (s == INVALID_SOCKET) {
  fprintf(stderr, "socket_stream is error!\n");
  return INVALID_SOCKET;
 }

 // 构建ip地址
 r = socket_addr(host, port, &addr);
 if (r < 0)
  return r;

 r = socket_connecto(s, &addr, ms);
 if (r < 0) {
  socket_close(s);
  fprintf(stderr, "socket_connecto host port ms = %s, %u, %d!\n", host, port, ms);
  return INVALID_SOCKET;
 }

 return s;
}

各级一样不良突破还来之不易. 如果急需以工程中贯彻平等份 nonblocking select
connect. 可以直接用者思路.

诸一样蹩脚突破还来之不易. 如果用以工程被落实均等卖 nonblocking select
connect. 可以一直用者思路.

中心就是不同平台的select api 的利用罢了. 你知道了也许就是掉和点坑,
多无可奈何些~

基本就是不同平台的select api 的行使罢了. 你掌握了说不定就少水点坑,
多无可奈何些~

 

 

后记 – 感悟

后记 – 感悟

  代码还是丢失点注释好, 那些老人说之代码即注释好像有点道理

  代码还是遗失点注释好, 那些老人说之代码即注释好像有点道理

 

 

相关文章