一个真正的客户端非阻塞的 connect
前言 - 一个简短开场白
winds 的 select 和 linux 的 select 是两个完全不同的东西. 然而凡人喜欢把它们揉在一起.
非阻塞的connect业务是个自带超时机制的 connect. 实现机制无外乎利用select(也有 epoll的).
本文是个源码软文, 专注解决客户端的跨平台的connect问题. 服务器的connect 要比客户端多考虑一丁点.
有机会再扯. 对于 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();
}
此刻再封装一些, 简化操作.
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 = ;
return ioctlsocket(s, FIONBIO, &mode);
#else
int mode = fcntl(s, F_GETFL, );
if (mode == SOCKET_ERROR)
return SOCKET_ERROR;
if (mode & O_NONBLOCK)
return fcntl(s, F_SETFL, mode & ~O_NONBLOCK);
return ;
#endif
}
inline int socket_set_nonblock(socket_t s) {
#ifdef _MSC_VER
u_long mode = ;
return ioctlsocket(s, FIONBIO, &mode);
#else
int mode = fcntl(s, F_GETFL, );
if (mode == SOCKET_ERROR)
return SOCKET_ERROR;
if (mode & O_NONBLOCK)
return ;
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 函数部分如下
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 -;
} 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 -;
}
// 尝试一种, 默认ipv4
memcpy(&addr->sin_addr, host->h_addr, host->h_length);
}
memset(addr->sin_zero, , sizeof addr->sin_zero); return ;
}
这里才是你要的一切, 真正的跨平台的客户端非阻塞 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 的使用罢了. 你知道了也许就少趟点坑, 多无可奈何些~
后记 - 感悟
代码还是少点注释好, 那些老人说的代码即注释好像有些道理
一个真正的客户端非阻塞的 connect的更多相关文章
- 非阻塞方式connect编程
参考博客: ①setsockopt()函数使用详解:http://blog.csdn.net/tody_guo/article/details/5972588 ②setsockopt :SO_LING ...
- c/c++ llinux epoll系列4 利用epoll_wait实现非阻塞的connect
llinux epoll系列4 利用epoll_wait实现非阻塞的connect connect函数是阻塞的,而且不能设置connect函数的timeout时间,所以一旦阻塞太长时间,影响用户的体验 ...
- UNP学习 非阻塞I/O
缺省状态下,套接口时阻塞方式的.这意味着当一个套接口调用不能立即完成时,进程进入睡眠状态,等待操作完成.我们将可能阻塞的套接口调用分成四种. 1.输入操作:read.readv.recv.recvfr ...
- linux 客户端 Socket 非阻塞connect编程
开发测试环境:虚拟机CentOS,windows网络调试助手 非阻塞模式有3种用途 1.三次握手同时做其他的处理.connect要花一个往返时间完成,从几毫秒的局域网到几百 ...
- 非阻塞socket调用connect, epoll和select检查连接情况示例
转自http://www.cnblogs.com/yuxingfirst/archive/2013/03/08/2950281.html 我们知道,linux下socket编程有常见的几个系统调用: ...
- TCP非阻塞accept和非阻塞connect
http://blog.chinaunix.net/uid-20751538-id-238260.html 非阻塞accept 当一个已完成的连接准备好被accept的时候,select会把监 ...
- 面向连接的socket数据处理过程以及非阻塞connect问题
对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET)在读写数据之前必须建立连接,首先服务器端socket必须在一个客户端知道的地址进行监听,也就是创建socket之后 ...
- 网络编程之非阻塞connect编写
一.connect非阻塞编写 TCP连接的建立涉及到一个三次握手的过程,且socket中connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回, 这意味着每 个connect函数总 ...
- linux-socket connect阻塞和非阻塞模式 示例
~/cpp$ ./connect 192.168.1.234 1234 kkkk block mode: ubuntu 14.04 : time used:21.0.001053s connect ...
随机推荐
- Python re(正则表达式)模块
python正则表达式 正则表达式是一个特殊的字符序列,它能帮助我们方便的检查一个字符串是否与某种模式匹配.Python自1.5版本起增加了re模块,它提供Perl风格的正则表达式模式.re模块使Py ...
- 【SPOJ】Highways(矩阵树定理)
[SPOJ]Highways(矩阵树定理) 题面 Vjudge 洛谷 题解 矩阵树定理模板题 无向图的矩阵树定理: 对于一条边\((u,v)\),给邻接矩阵上\(G[u][v],G[v][u]\)加一 ...
- BZOJ2693:JZPTAP——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=2693 Description Input 一个正整数T表示数据组数 接下来T行 每行两个正整数 ...
- Mysql 语句优化技巧
前言 有人反馈之前几篇文章过于理论缺少实际操作细节,这篇文章就多一些可操作性的内容吧. 注:这篇文章是以 MySQL 为背景,很多内容同时适用于其他关系型数据库,需要有一些索引知识为基础. 优化目标 ...
- Django内置auth模块中login_required装饰器用于类视图的优雅方式
使用多继承 以及类似java中的静态代理模式 原理:OrderView.as_view()根据广度优先,调用的是LoginRequiredMixin中的as_view(cls, *args, **kw ...
- BZOJ2434: [NOI2011]阿狸的打字机(AC自动机+dfs序+树状数组)
[NOI2011]阿狸的打字机 题目链接:https://www.luogu.org/problemnew/show/P2414 题目背景 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机. ...
- GYM 101875 2018 USP-ICMC
3月自训 (1):10/12 A: 题意:每个人可以连边,最远连到第(i+k)%n个人,边权为这个人与另一个人连边距离,求生成一颗最大生成树的权值和是多少 题解:可以证明的是,我们每一个人都向接下来的 ...
- Qt ------- QByteArray操作注意
使用QByteArray方法把数据存入QByteArray需要是char型数据,如果需要存入无符号8位数据,如下: QByteArray data; data[0] = 0xFF; 即使通过data[ ...
- SSH客户端,FinalShell服务器管理,远程桌面加速软件,支持Windows,Mac OS X,Linux,版本2.6.3.1
FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工具,充分满足开发,运维需求. 用户QQ群 342045988 Windows版下载地址:http:/ ...
- [C#] 小记 new 和 override 关键字
C#要想实现函数的override,要求和C++一样,父类的函数必须用virtual关键字注明,随后在子类中用override关键字表明重写的函数. 子类同名函数定义时,如果什么都不写,或者使用new ...