socket的阻塞模式和非阻塞模式
文章目录
- socket的阻塞模式和非阻塞模式
- 如何将socket设置为非阻塞模式
- send和recv函数在阻塞和非阻塞模式下的表现
- 非阻塞模式下send和recv函数的返回值总结
- 阻塞与非阻塞socket的各自使用场景
socket的阻塞模式和非阻塞模式
如何将socket设置为非阻塞模式
无论是Windows还是Linux,默认创建的socket
都是阻塞模式的。
在linux上,我们可以使用fcntl
函数或者ioctl
函数给创建的socket
增加O_NONBLOCK
标志来将socket
设置为非阻塞模式。
int oldSocketFlag = fcntl(sockfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
fcntl(sockfd, F_SETFL, newSocketFlag);
Linux上的socket
函数也可以直接在创建时将socket
设置为非阻塞式,socket
函数签名如下:
int socket(int domain, int type, int protocol);
给type
参数增加一个SOCK_NONBLOCK
标志即可,例如:
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
不仅如此,在Linux上利用accept
函数返回的代表与客户端通信的socket
也提供了一个扩展函数accept4
,直接将accept
函数返回的socket
设置为非阻塞的;
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen, int flags);
只要将accept4
函数的最后一个参数flags
设置为SOCK_NONBLOCK
即可。
以下代码等价。
socklen_t addrlen = sizeof(clientaddr);
int clientfd = accept4(listenfd, &clientaddr, &addrlen, SOCK_NONBLOCK);socklen_t addrlen = sizeof(clientaddr);
int clientfd = accept(listenfd,&clientaddr,&addrlen);
if(clientfd != -1)
{int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;fcntl(clientfd, F_SETFL, newSocketFlag);
}
send和recv函数在阻塞和非阻塞模式下的表现
send
函数在本质上并不是向网络上发送数据,而是将应用层发送缓冲区的数据拷贝到内核缓冲区中,至于数据什么时候会从网卡缓冲区中真正地发到网络中,要根据TCP/IP协议栈的行为来确定。如果socket
设置了TCP_NODELAY
选项(即禁用nagel算法)。存放到内核缓冲区的数据就会被立即发送出去,反之,一次放入内核缓冲区的数据包如果太小,则系统会在多个小的数据包凑成一个足够大的数据包之后才会将数据发送出去。
recv
函数在本质上并不是从网络上收取数据,而是将内核缓冲区中的数据拷贝到应用程序的缓冲区中。在拷贝完成后会将内核缓冲区中的该部分数据移除。
通过上图可以知道,不同的程序在进行网络通信时,发送的一方会将内核缓冲区的数据通过网络传输给接收方的内核缓冲区。在应用程序A与应用程序B建立连接之后,假设应用程序A不断调用send
函数,则数据会不断拷贝至对应的内核缓冲区中,如果应用程序B一直不调用recv
函数,那么在应用程序B的内核缓冲区被填满之后,应用程序A的内核缓冲区也会被填满,此时应用程序A继续调用send
函数会发生什么结果?具体的结果取决于socket
是否是阻塞模式,这里先给出结论。
- 当
socket
是阻塞模式时,继续调用send/recv
函数,程序会阻塞在send/recv
调用处; - 当
socket
是非阻塞模式时,继续调用send/recv
函数,send/recv
不会阻塞程序执行流,而是立即出错返回,我们会得到一个相关的错误码,在Linux上该错误码为EWOULDBLOCK
或EAGAIN
。
非阻塞模式下send和recv函数的返回值总结
返回值n | 返回值的含义 |
---|---|
大于0 |
成功发送send 或接收recv n字节
|
0 | 对端关闭连接 |
小于0 | 出错,被信号中断,对端TCP窗口太小导致数据发送不出去或者当前网卡缓冲区已无数据可接收 |
这三种情况:
- 返回值大于0。当
send/recv
函数的返回值大于0时,表示发送或接收多少字节。需要注意的是,在这种情况下,我们一定要判断send
函数的返回值是不是我们期望发送的字节数,而不是简单判断其返回值大于0。举个例子
int n = send(socket, buf, buf_length, 0);
if(n > 0)
{printf("send data successful");
}
虽然返回值n
大于0,但在实际情况中,由于对端的TCP窗口可能因为缺少一部分字节就满了,所以n
的值可能为(0,buf_length](0, buf\_length](0,buf_length]。当0<n<buf_length0 < n < buf\_length0<n<buf_length时,虽然此时send
函数调用成功,但在业务上并不算正确,因为有部分数据并没有被发送出去。我们可能在一次测试中测不出n
小于buf_length
的情况,但不代表实际上不存在。所以,建议要么在返回值n
等于buf_length
时才认为正确,要么在一个循环中调用send
函数,如果数据一次性发送不完,则记录偏移量,下一次从偏移量处接着发送,直到全部发送完为止。
//不推荐的方式
int n = send(socket, buf, buf_length, 0);
if(n == buf_length)
{printf("send data successfully\n");
}//推荐的方式
bool SendData(const char* buf, int buf_length)
{//已经发送的字节数int sent_bytes = 0;int ret = 0;while(true){ret = send(m_hSocket, buf + sent_bytes, buf_length - sent_bytes, 0);if(ret == -1){if(errno == EWOULDBLOCK){//严谨的做法:如果发送不出去,则应该缓存尚未发送出去的数据break;}else if(errno == EINTR)continue;elsereturn false;}else if(ret == 0)return false;sent_bytes += ret;if(sent_bytes == buf_length)break;}return true;
}
- 返回值等于0。在通常情况下,如果
send
或recv
函数返回0,我们就认为对端关闭了连接,我们这端也关闭连接即可。send
函数主动发送0字节时也会返回0,这是一种特例。 - 返回值小于0。对于
send
或recv
函数返回值小于0的情况(即返回-1),此时并不表示send
或者recv
函数一定调用出错。
下表表示的是非阻塞模式下,socket
的send
和recv
返回值,对于阻塞模式下的socket
,如果返回值为-1,则一定表示出错。
返回值和错误码 | send函数 | recv函数 |
---|---|---|
返回-1,错误码是EWOULDBLOCK或EAGAIN | TCP窗口太小,数据暂时发送不出去 | 在当前缓冲区中无可读数据 |
返回-1,错误码是EINTR | 被信号中断,需要重试 | 被信号中断,需要重试 |
返回-1,错误码不是以上3种 | 出错 | 出错 |
阻塞与非阻塞socket的各自使用场景
阻塞的socket
函数在调用send
,recv
,connect
,accept
等函数时,如果特定的条件不满足,就会阻塞其调用线程直至超时,非阻塞的socket
恰恰相反。
非阻塞模式一般用于需要支持高并发多QPS的场景(如服务器程序),但是正如前文所述,这种模式让程序的执行流和控制逻辑变得复杂;相反,阻塞模式逻辑简单,程序结构简单明了,常用于一些特殊场景中。
应用场景一:某程序需要临时发送一个文件,文件分段发送,每发送一段,对端都会给予一个响应,该程序可以单独开一个任务线程,在这个任务线程函数里面,使用先send
后recv
再send
再recv
的模式,每次send
和recv
都是阻塞模式的。
应用场景二:A端与B端之间的通信只有问答模式,即A端每发送给B端一个请求,B端比定会给A端一个响应,除此之外,B端不会向A端推送任何数据,此时A端就可以采用阻塞模式,在每次send
完请求后,都可以直接使用阻塞式的recv
函数接收应答包。
发送给B端一个请求,B端比定会给A端一个响应,除此之外,B端不会向A端推送任何数据,此时A端就可以采用阻塞模式,在每次send
完请求后,都可以直接使用阻塞式的recv
函数接收应答包。
socket的阻塞模式和非阻塞模式相关推荐
- socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)
socket的阻塞模式和非阻塞模式 无论是Windows还是Linux,默认创建socket都是阻塞模式的 在Linux中,可以再创建socket是直接将它设置为非阻塞模式 int socket (i ...
- Linux下同步模式、异步模式、阻塞调用、非阻塞调用总结
同步和异步:与消息的通知机制有关. 本质区别 现实例子 同步模式 由处理消息者自己去等待消息是否被触发 我去银行办理业务,选择排队等,排到头了就办理. 异步模式 由触发机制来通知处理消息者 我去银行办 ...
- 【gev】 Golang 实现轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库
gev 轻量.快速的 Golang 网络库 https://github.com/Allenxuxu/gev gev 是一个轻量.快速的基于 Reactor 模式的非阻塞 TCP 网络库,底层并不使用 ...
- golang mysql 非阻塞_Golang 实现轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库...
gev 轻量.快速的 Golang 网络库 gev 是一个轻量.快速的基于 Reactor 模式的非阻塞 TCP 网络库,底层并不使用 golang net 库,而是使用 epoll 和 kqueue ...
- 阻塞io阻塞io_Redis:RESP协议,阻塞IO 与非阻塞IO,Redis的线程模型
1.Redis 阻塞IO 与非阻塞IO Java在JDK1.4 中引入了NIO ,但是也有很多人在使用阻塞IO,这两种IO有什么区别? 在阻塞模式下,如果你从数据流读取不到指定大小的数据量,IO就会阻 ...
- 关于阻塞I/O 非阻塞I/O 同步I/O 异步I/O epoll select的学习
之前参考了这篇文章,下面写一写笔记 阻塞IO 非阻塞IO 1.blocking IO linux中默认的socket都是阻塞的 用户进程发出系统调用后,数据未准备好,进程阻塞..之后内核中有两个阶段: ...
- 【多线程】0.理解一下5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO
5种IO模型.阻塞IO和非阻塞IO.同步IO和异步IO 看了一些文章,发现有很多不同的理解,可能是因为大家入切的角度.环境不一样.所以,我们先说明基本的IO操作及环境. 本文是在<UNIX网络编 ...
- 5种网络IO模型:阻塞IO、非阻塞IO、异步IO、多路复用IO、信号驱动IO
目录 前言 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) 模型间的区别 ...
- java之阻塞队列和非阻塞队列
目录 一.什么是阻塞队列 二.阻塞队列种类分析 三.非阻塞队列 Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子 ...
最新文章
- 关于Unity中的光照(六)
- QT-第一个程序 Hello QT , 以及QT creator介绍
- Wdcp在安装memcached出现错误的解决办法
- where does default 20 come from SAP UI5 growingThreshold
- Swift3.0中如何完成不同View Controller之间的切换
- MySQL数据库基础(数据表的SELECT操作)
- 阿里推出“阿里云网盘”App;Linux 发布 29 周年​| 极客头条
- android4以上版本读写外置sd卡报错的解决办法
- Android实现浮层的上下滑动(支持内部加入View)
- mysql split 分割文件名_【原】mysql5.6 split函数_字符串的分割
- NodeJs安装教程:看教程一步步学会安装NodeJs
- 彼得·林奇的25条黄金规则
- 运营商线路细分_电信运营商如何进行客户细分
- amazon alexa_如何建立您的第一个Amazon Alexa技能
- 我的第一款(ban)产(cheng)品(pin)——铛铛打卡
- 华为mate50参数配置 华为mate50是5g吗
- Hotkeycontrol录制宏
- MySQL 更新某个字段的值加1 是有前提条件的(非auto_increament)
- 【边做项目边学Android】手机安全卫士07-手机防盗之进入限制
- 范济安:我所知道的云计算