socket non-blocking mode connect

对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET),在读写数据之前必须建立连接,connect()函数用于完成面向连接的socket的建链过程,对于TCP,也就是三次握手过程。

connect()函数

头文件:

#include

#include

声明:

int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);

功能:

使用套接字sockfd建立到指定网络地址serv_addr的socket连接,参数addrlen为serv_addr指向的内存空间大小,即sizeof(struct sockaddr_in)。

返回值:

1)成功返回0,表示连接建立成功(如服务器和客户端是同一台机器上的两个进程时,会发生这种情况)

2)失败返回SOCKET_ERROR,相应的设置errno,通过errno获取错误信息。常见的错误有对方主机不可达或者超时错误,也可能是对方主机没有进程监听对应的端口。

非阻塞connect(non-block mode connect)

套接字执行I/O操作有阻塞和非阻塞两种模式。在阻塞模式下,在I/O操作完成前,执行操作的函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。

客户端调用connect()发起对服务端的socket连接,如果客户端的socket描述符为阻塞模式,则connect()会阻塞到连接建立成功或连接建立超时(linux内核中对connect的超时时间限制是75s, Soliris 9是几分钟,因此通常认为是75s到几分钟不等)。如果为非阻塞模式,则调用connect()后函数立即返回,如果连接不能马上建立成功(返回-1),则errno设置为EINPROGRESS,此时TCP三次握手仍在继续。此时可以调用select()检测非阻塞connect是否完成。select指定的超时时间可以比connect的超时时间短,因此可以防止连接线程长时间阻塞在connect处。

select判断规则:

1)如果select()返回0,表示在select()超时,超时时间内未能成功建立连接,也可以再次执行select()进行检测,如若多次超时,需返回超时错误给用户。

2)如果select()返回大于0的值,则说明检测到可读或可写的套接字描述符。源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:

A) 当连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写)

B) 当连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写)

因此,当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将B)和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写。

□对于Unix环境,可通过调用getsockopt来检测描述符集合是连接成功还是出错(此为《Unix Network Programming》一书中提供的方法,该方法在Linux环境上测试,发现是无效的):

A)如果连接建立是成功的,则通过getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 获取的error 值将是0

B)如果建立连接时遇到错误,则errno 的值是连接错误所对应的errno值,比如ECONNREFUSED,ETIMEDOUT 等

□一种更有效的判断方法,经测试验证,在Linux环境下是有效的:

再次调用connect,相应返回失败,如果错误errno是EISCONN,表示socket连接已经建立,否则认为连接失败。

综上所述,这里总结一下非阻塞connect的实现过程。

非阻塞connect的实现过程

1. 创建套接字sockfd

/* 1. obtain a socket */

int sock_fd;

sock_fd = socket(AF_INET, SOCK_STREAM, 0);

2. 设置套接字为非阻塞模式

/* 2. set non-blocking mode no socket */

#if 1

int flags = fcntl(sock_fd, F_GETFL, 0);

fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);

#else

int imode = 1;

ioctl(sock_fd, FIONBIO, &imode);

3. 调用connect进行连接

struct sockaddr_in addr;

addr.sin_family = AF_INET;

addr.sin_port = htons(PEER_PORT);

addr.sin_addr.s_addr = inet_addr(PEER_IP);

int ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));

if (0 == res)

{

printf("socket connect succeed immediately.\n");

ret = 0;

}

else

{

printf("get the connect result by select().\n");

if (errno == EINPROGRESS)

{

....

}

}

connect会立即返回,可能返回成功,也可能返回失败。如果连接的服务器在同一台主机上,那么在调用connect 建立连接时,连接通常会立即建立成功(我们必须处理这种情况)。

4.调用select(),通过FD_ISSET()检查套接口是否可写,确定连接请求是否完成

fd_set rfds, wfds;

struct timeval tv;

FD_ZERO(&rfds);FD_ZERO(&wfds);

FD_SET(sock_fd, &rfds);

FD_SET(sock_fd, &wfds);

/* set select() time out */

tv.tv_sec = 10;

tv.tv_usec = 0;

int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);

switch (selres)

{

case -1:

printf("select error\n");

ret = -1;

break;

case 0:

printf("select time out\n");

ret = -1;

break;

default:

if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))

{

.....

}

}

对于无连接的socket类型(SOCK_DGRAM),客户端也可以调用connect进行连接,此连接实际上并不建立类似SOCK_STREAM的连接,而仅仅是在本地保存了对端的地址,这样后续的读写操作可以默认以连接的对端为操作对象。

Linux下常见的socket错误码:

EACCES, EPERM:用户试图在套接字广播标志没有设置的情况下连接广播地址或由于防火墙策略导致连接失败。

EADDRINUSE 98:Address already in use(本地地址处于使用状态)

EAFNOSUPPORT 97:Address family not supported by protocol(参数serv_add中的地址非合法地址)

EAGAIN:没有足够空闲的本地端口。

EALREADY 114:Operation already in progress(套接字为非阻塞套接字,并且原来的连接请求还未完成)

EBADF 77:File descriptor in bad state(非法的文件描述符)

ECONNREFUSED 111:Connection refused(远程地址并没有处于监听状态)

EFAULT:指向套接字结构体的地址非法。

EINPROGRESS 115:Operation now in progress(套接字为非阻塞套接字,且连接请求没有立即完成)

EINTR:系统调用的执行由于捕获中断而中止。

EISCONN 106:Transport endpoint is already connected(已经连接到该套接字)

ENETUNREACH 101:Network is unreachable(网络不可到达)

ENOTSOCK 88:Socket operation on non-socket(文件描述符不与套接字相关)

ETIMEDOUT 110:Connection timed out(连接超时)

测试代码:

#include

#include

#include

#include

#include

#include

#include

#include

#include

//inet_addr()

#include

#include

#include

#define PEER_IP "192.254.1.1"

#define PEER_PORT 7008

int main(int argc, char **argv)

{

int ret = 0;

int sock_fd;

int flags;

struct sockaddr_in addr;

/* obtain a socket */

sock_fd = socket(AF_INET, SOCK_STREAM, 0);

/* set non-blocking mode on socket*/

#if 1

flags = fcntl(sock_fd, F_GETFL, 0);

fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);

#else

int imode = 1;

ioctl(sock_fd, FIONBIO, &imode);

#endif

/* connect to server */

addr.sin_family = AF_INET;

addr.sin_port = htons(PEER_PORT);

addr.sin_addr.s_addr = inet_addr(PEER_IP);

int res = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

if (0 == res)

{

printf("socket connect succeed immediately.\n");

ret = 0;

}

else

{

printf("get the connect result by select().\n");

if (errno == EINPROGRESS)

{

int times = 0;

while (times++ < 5)

{

fd_set rfds, wfds;

struct timeval tv;

printf("errno = %d\n", errno);

FD_ZERO(&rfds);

FD_ZERO(&wfds);

FD_SET(sock_fd, &rfds);

FD_SET(sock_fd, &wfds);

/* set select() time out */

tv.tv_sec = 10;

tv.tv_usec = 0;

int selres = select(sock_fd + 1, &rfds, &wfds, NULL, &tv);

switch (selres)

{

case -1:

printf("select error\n");

ret = -1;

break;

case 0:

printf("select time out\n");

ret = -1;

break;

default:

if (FD_ISSET(sock_fd, &rfds) || FD_ISSET(sock_fd, &wfds))

{

#if 0 // not useable in linux environment, suggested in <>

int errinfo, errlen;

if (-1 == getsockopt(sock_fd, SOL_SOCKET, SO_ERROR, &errinfo, &errlen))

{

printf("getsockopt return -1.\n");

ret = -1;

break;

}

else if (0 != errinfo)

{

printf("getsockopt return errinfo = %d.\n", errinfo);

ret = -1;

break;

}

ret = 0;

printf("connect ok?\n");

#else

#if 1

connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));

int err = errno;

if (err == EISCONN)

{

printf("connect finished 111.\n");

ret = 0;

}

else

{

printf("connect failed. errno = %d\n", errno);

printf("FD_ISSET(sock_fd, &rfds): %d\n FD_ISSET(sock_fd, &wfds): %d\n", FD_ISSET(sock_fd, &rfds) , FD_ISSET(sock_fd, &wfds));

ret = errno;

}

#else

char buff[2];

if (read(sock_fd, buff, 0) < 0)

{

printf("connect failed. errno = %d\n", errno);

ret = errno;

}

else

{

printf("connect finished.\n");

ret = 0;

}

#endif

#endif

}

else

{

printf("haha\n");

}

}

if (-1 != selres && (ret != 0))

{

printf("check connect result again... %d\n", times);

continue;

}

else

{

break;

}

}

}

else

{

printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);

ret = errno;

}

}

if (0 == ret)

{

send(sock_fd, "12345", sizeof("12345"), 0);

}

else

{

printf("connect to host %s:%d failed.\n", PEER_IP, PEER_PORT);

}

close(sock_fd);

return ret;

}

问题总结:

1. 非阻塞socket可调用fcntl或ioctl设置

2.connect的返回值

非阻塞connect返回-1,并不一定是连接失败,可能是连接过程未完成,此时errno为EINPROGRESS,可通过select检查连接何时完成

3. select的返回值

1)-1,表示select出错,可以关闭socket,重新发起连接过程

2)0,表示select超时,此时可能connect还在进行中,可再次进行select,反复数次仍超时,可认为连接失败,需返回失败错误

3)1,表示存在套接口描述字可读或可写,需根据规则进一步判断是连接成功还是出错

4. 通过socket可读/可写能否判断连接状态

源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:

A) 当连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写)

B) 当连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写)

当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将B)和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时select同样会返回非阻塞socket描述符既可读又可写。

因此,仅从socket可读或可写无法判断socket连接的状态。

5. 如何有效判断连接状态:

1)getsockopt方法在linux环境下无效

2)再次执行connect,检查errno值,该方法在Linux环境下有效。测试中发现的问题:

一次select之后,发现此时套接口描述字可读或可写,再次执行connect,此时errno始终不变,仍未EINPROGRESS,增加select的超时时间结果也一样。

之后尝试在select返回值为0,或返回值为1,且connect后errno仍为EINPROGRESS(115)时,再次执行select+connect,即再次检测连接状态。此时errno被置为EISCONN(106),connect成功。

还没有搞清楚原因。

7. socket连接成功后是否重置为阻塞模式

这要看连接需要什么样的效果。

如果连接建立成功后,重置socket为阻塞模式。在给服务器发送信息等待接收数据时,如果服务器很忙,而服务器也没设计好,每到来一个客户端就服务,那么后来的要排队,客户端多的话,会导致后来的请求长时间得不到应答,线程一直被阻塞。

然而,在非阻塞模式下,send和recv也会立即返回。测试发现,非阻塞模式下,recv常常未能接收到数据,返回错误。而在建链之后将socket重置为阻塞,recv的接收正常。

8. socket使用完成后要进行释放

close(sock_fd)

9. 具有非阻塞属性的接口

linux socket 阻塞服务端 非阻塞客户端,Linux socket非阻塞connect方法相关推荐

  1. 基于TCP/IP协议的Java服务端与Android客户端的Socket通信及数据交互

    基于TCP/IP协议的Java服务端与Android客户端的Socket通信及数据交互 一.前言 1.Java服务端程序代码的项目名为TcpSocketServerOfJava,包名为com.exam ...

  2. socket.io服务端是java_SpringBoot(23) 集成socket.io服务端和客户端实现通信

    @Slf4j @Service(value = "socketIOService") public class SocketIOServiceImpl implements ISo ...

  3. linux socket 阻塞服务端 非阻塞客户端,linux下异步RPC的阶段性总结-非阻塞SOCKET客户端...

    尽可能使用非阻塞socket int flags, s; flags = fcntl (fd, F_GETFL, 0); if (flags == -1){ close(fd); return -1; ...

  4. linux 进程sockfd fork,Linux下多进程服务端客户端模型一(单进程与多进程模型)...

    本文将会简单介绍Linux下如何利用C库函数与系统调用编写一个完整的.初级可用的C-S模型. 一.基本模型: 1.1   首先服务器调用socket()函数建立一个套接字,然后bind()端口,开始l ...

  5. mysql服务器是否支持tcp/ip连接,(3)MySQL客户端与服务端的TCP/IP及socket连接方式-Go语言中文社区...

    MySQL客户端与服务端的TCP/IP及socket连接方式 客户端与服务器模型 客户端与服务端模型 TCP/IP方式连接 解释说明 TCP/IP套接字方式是MySQL在任何平台下都提供的连接方式,也 ...

  6. SpringBoot(23) 集成socket.io服务端和客户端实现通信

    一.前言 websocket和socket.io区别? websocket 一种让客户端和服务器之间能进行双向实时通信的技术 使用时,虽然主流浏览器都已经支持,但仍然可能有不兼容的情况 适合用于cli ...

  7. Socket服务端向指定客户端发送消息

    Socket服务端向指定客户端发送消息 解决思想 1.项目背景 2.如何上传与下发指令 3.解决方法 4.流程 解决思想 I.指定客户端远程地址是存起来的. II.服务端直接主动发信息给客户端,问题在 ...

  8. linux c++ 线程支持 多核应用,linux C++多线程服务端开发

    linux C++多线程服务端开发 UNIX 线程安全的对象生命期管理 当析构函数遇到多线程 构造不要在构造函数中注册任何回调 不要在构造函数中把this传给跨线程的对象 即便在构造函数的最后一行也不 ...

  9. iOS开发-使用OC搭建自己的Socket 包括服务端和客服端

    iOS开发-使用OC搭建自己的Socket 包括服务端和客服端 前言 开发须知 客服端 服务端 两端测试 前言 iOS开发中需要使用到Socket通信的地方,socket分为UDP和TCP,这次分享的 ...

  10. PHP服务端、Unity客户端 双端基础源码做avalon阿瓦隆桌游面sha(类似狼人游戏)支持WebGL、小程序发布

    文章目录 PHP服务端发布(Windows下演示) Windows 安装PHP 启动服务器 Linux家族 Unity客户端发布 发布Windows客户端 发布WebGL端 演示 源码解析 联系作者 ...

最新文章

  1. python实现数据库查询_通过Python实现mysql查询数据库实例
  2. 《数学之美》第6章 信息的度量和作用
  3. xml突然变成空白_“侏罗纪中期”出现了型增转变填补食肉性恐龙体型发展当中的空白...
  4. [北航软工]第一次团队作业
  5. centos7防火墙操作
  6. Tomcat提示Null component
  7. 多行查询结果合并sys_connect_by_path
  8. c# Winform 开发分屏显示应用程序
  9. CentOS查看硬件情况
  10. Spring框架IOC容器,依赖注入,控制反转
  11. HoloLens开发手记 - Unity之语音输入
  12. 20科大考研经验分享-数学
  13. 常见的弱口令字典1000~一石三鸟
  14. C++洛谷题解(6)
  15. C语言 数组 冒泡排序法
  16. Dynamics CRM: 权限问题之SecLib::AccessCheckEx2 failed
  17. 关于网站推广 网站营销 建议
  18. 2020最新版前端学习路线图--让前端学习变得美如画
  19. linux字体渲染比不上windows,各位是怎么解决字体渲染问题的
  20. MSP430 MSP430单片机输入/输出模块 通用I/O端口GPIO LED按键

热门文章

  1. 基于FPGA板的音乐盒的设计
  2. qq互联android sdk,qq互联.Android_SDK_V2.0使用说明.doc
  3. 小米手机不断自己重启问题解决
  4. 传感器系列(一)——超声波测距传感器 HC—SR04模块
  5. 苹果怎么用测试软件,iPhone 也能测量身高教你怎么用 iOS「测距仪」App
  6. 算法题:矩阵修改为黑白矩阵
  7. 清明节黑白效果=来聊聊色彩矩阵算法
  8. {“errcode“:40125,“errmsg“:“invalid appsecret, view more at http:\/\/t.cn\/RAEkdVq rid: 60d999f2-3ad5
  9. 英文文献调研方法综述
  10. 微博登录设备有python_Python搜寻器如何登录新浪微博并获取内容?