mysql client 非阻塞_一个真正的客户端非阻塞的 connect

前言  - 一个简短开场白

winds 的 select 和 linux 的 select 是两个完全不同的东西. 然而凡人喜欢把它们揉在一起.

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

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

有机会再扯. 对于 select 网上资料太多, 几乎都有点不痛不痒. 了解真相推荐man and msdn!!!

正文 - 所有的都需要前戏

那开始吧 .  一切从丑陋的跨平台宏开始

#include #include#include#include#include#include

//

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

#include#include#include#include#include#include#include#include#include

/** 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 EINPROGRESStypedefintsocket_t;#elif _MSC_VER

#undef FD_SETSIZE

#define FD_SETSIZE (1024)#include

#undef errno

#define errno WSAGetLastError()

#define IGNORE_SIGPIPE()

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

#define ECONNECTED WSAEWOULDBLOCKtypedefintsocklen_t;

typedef SOCKET socket_t;static inline void _socket_start(void) {

WSACleanup();

}#endif

//目前通用的tcp udp v4地址

typedef structsockaddr_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);#endifIGNORE_SIGPIPE();

}

此刻再封装一些,  简化操作.

inline socket_t socket_stream(void) {returnsocket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

}

inlineintsocket_close(socket_t s) {

#ifdef _MSC_VERreturnclosesocket(s);#else

returnclose(s);#endif}

inlineintsocket_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)returnSOCKET_ERROR;if (mode &O_NONBLOCK)return fcntl(s, F_SETFL, mode & ~O_NONBLOCK);return 0;#endif }

inlineintsocket_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)returnSOCKET_ERROR;if (mode &O_NONBLOCK)return 0;return fcntl(s, F_SETFL, mode |O_NONBLOCK);#endif }

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

}

全局的测试主体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;

}

intsocket_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.

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. 可以直接用上面思路.

核心就是不同平台的select api 的使用罢了. 你知道了也许就少趟点坑, 多无可奈何些~

后记 - 感悟

代码还是少点注释好, 那些老人说的代码即注释好像有些道理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值