C++网络编程快速入门(三):阻塞与非阻塞式调用网络通信函数

目录

  • 阻塞与非阻塞定义
  • send与recv
  • connect
  • 一些问题
    • 为什么要将监听socket设置为非阻塞

阻塞与非阻塞定义

阻塞模式指的是当前某个函数执行效果未达预期,该函数会阻塞当前的执行线程,程序执行流在超时时间到达或者执行成功后恢复原有流程。非阻塞模式相反,即使某个函数执行结果未达预期,该函数也不会阻塞当前执行线程,而是立即返回。
网络socket编程中,常见的connectacceptsendrecv函数均具有阻塞与非阻塞两种调用方式。
阻塞与非阻塞socket具有各自适用的场景
非阻塞模式一般用于需要支持高并发QPS的场景,但是该模式会让程序执行流和控制逻辑变复杂。
阻塞模式逻辑简单,结构简单。

send与recv

send函数本质不是向网络发送数据,而是将应用层发送缓冲区的数据拷贝到内核缓冲区,至于数据什么时候从网卡缓冲区中真正的发到网络中,要根据TCP/IP协议栈的行为来确定。
如果禁用nagel算法,存放到内核缓冲区的数据就会被立即发送出去。
否则如果一次放入缓冲区的数据包太小,系统会在多个小的数据包凑成一个足够大的数据包后再发送。
反之,recv函数的本质则是将内核缓冲区的数据拷贝到应用缓冲区
而两个程序进行网络通信时,发送的一方会将内核缓冲区的数据通过网络传输给接收方的内核缓冲区。这里的内核缓冲区也可以被称为TCP窗口
在这里插入图片描述
如果一端一直发送数据,对端应用一直不收取数据的话,则两端的内核缓冲区很快会被填满,导致调用send函数被阻塞(如果是阻塞模式下的话),从而影响当前线程的流程。如果是阻塞模式下德华,对端和本端的TCP窗口已满,数据发送不出去,send函数会立即返回-1,并且得到EWOULDBLOCK的错误码。
下面是非阻塞模式下send和recv函数的返回值总结

返回值返回值含义
大于0成功发送或者接受n个字节
0对端关闭连接
小于0出错、信号被中断、对端窗口太小导致数据发送不出去、当前网卡缓冲区无数据可接收

此时需要判断返回值是否是我们期望的发送or接收的字节数。
如果对端的TCP窗口可能因为接收了部分数据就满了,此时n的值就是(0,buf_length]了。
所以一般在循环中调用send函数,如果数据一次性发送不出去,则记录偏移量,下一次从偏移量处接着发送,直到全部发送完为止:

bool sendData(int socketfd, const char* buf, int bufLength)
{// 已经发送的字节数int sentBytes = 0;int ret = 0;while (true) {ret = send(socketfd, buf + sentBytes, bufLength - sentBytes, 0);if (ret == -1) {if (errno == EWOULDBLOCK) {// 缓存尚未发送出去的数据,这里不具体写// ... 缓存未发送出去的数据break;} else if (errno == EINTR) {continue;} else {return false;}} else if (ret == 0) {return false;}// 否则发送成功sentBytes += ret;if (sentBytes == bufLength)break;}return true;
}

当返回值为-1的时候我们需要根据不同的错误码来进行对应处理:

错误码send函数recv函数
EWOULDBLOCK 或者 EAGAINTCP窗口太小,数据暂时发送不出去当前内核缓冲区中无可读数据
EINTR被信号中断,需要重试被信号中断,需要重试
不是以上两种出错出错

connect

使用非阻塞的connect的步骤如下:
1、创建socket,将socket设置为非阻塞模式
2、调用connect函数,无论connect函数是否连接成功都立即返回;
3、调用select函数,在指定时间内判断该socket是否可写,若可写,则说明连接成功,反之认为连接失败。不过在linux系统上有些特殊:
connect之后,不仅要调用select检测是否可写,还要调用getsockpt检测此时socket是否出错,通过错误码来检测是否连接上,错误码为0表示连接上。
在上一讲中我们在服务端使用了select函数来监听三种事件的发生,在客户端也是可以用的。在这个问答中:select()可以用于客户端,而不仅仅是服务器吗?有这样一个回答:
在客户端套接字上使用select()的另一个好理由是跟踪传出的TCP连接进度。例如,这允许设置连接超时。 将客户端套接字设置为非阻塞。 调用connect()。可能它会返回EINPROGRESS错误集(连接正在进行中,因为套接字是非阻塞的,所以不会被阻止)。 现在select()配置FD_SET以跟踪客户端套接字为’write-ready’。你也可以设置超时。 分析select()结果。 分析上次客户端套接字操作是否失败或成功。 最有用的是你可以在不同状态的几个套接字上使用它。因此,您可以真正无阻塞地处理多个套接字(客户端,服务器,传出,侦听,接受…)。所有这一切只有一个线程。

代码如下:

#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 3000
#define SEND_DATA "helloworld"using namespace std;int main() {// 创建一个socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1) {cout << " create client socket error " << endl;return -1;}// 将clientfd设置为非阻塞模式int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1) {close(clientfd);cout << "set socket to noblock error" << endl;return -1;}// 连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);// 此处与之前的阻塞式connect就不一样了,需要用for循环,来轮询状态while (true) {int ret = connect(clientfd, (struct sockaddr *)& serveraddr, sizeof(serveraddr));if (ret == 0) {cout << "connect to server sucessfully" << endl;close(clientfd);return 0;} else if (ret == -1) {if (errno == EINTR) {// connect 被信号中断了,重试connectcout << "connect interruptted by signal, try again" << endl;continue;} else if (errno == EINPROGRESS) {// 连接尝试中break;} else {// 真的出错了close(clientfd);return -1;}}}fd_set writeset;FD_ZERO(&writeset);FD_SET(clientfd, &writeset);struct timeval time;time.tv_sec = 3;time.tv_usec = 0;// 调用select判断socket是否可写if (select(clientfd + 1, NULL, &writeset, NULL, &time) != 1) {cout << "select connect to server error" << endl;close(clientfd);return -1;}int err;socklen_t len = static_cast<socklen_t>(sizeof err);// 调用getsockopt检测此时socket是否出错if (::getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {close(clientfd);return -1;}if (err == 0) {cout << "connect to server successfully" <<endl;} else {cout << "connect to server error" << endl; }close(clientfd);return 0;
}

一些问题

为什么要将监听socket设置为非阻塞

在第二讲中我们谈到select模型,常见的网络通信模型都会使用IO多路复用技术如select、poll、epoll等。当有新的连接请求到来时,监听套接字变为可读,然后调用accept()接收新连接、返回一个连接套接字。
如果监听套接字是阻塞的,问题可能出在什么地方?
根据TCP三次握手的示意图:
在这里插入图片描述
从图中可知,connect()会先于accep()函数返回。
当一个连接到来的时候,监听套接字可读,此时,我们稍微等一段时间之后再调用accept()。就在这段时间内,客户端设置linger选项(l_onoff = 1, l_linger = 0),然后调用了close(),那么客户端将不经过四次挥手过程,通过发送RST报文断开连接。服务端接收到RST报文,系统会将排队的这个未完成连接直接删除,此时就相当于没有任何的连接请求到来, 而接着调用的accept()将会被阻塞,直到另外的新连接到来时才会返回。这是与IO多路复用的思想相违背的(系统不阻塞在某个具体的IO操作上,而是阻塞在select、poll、epoll这些IO复用上的)。
上述这种情况下,如果监听套接字为非阻塞的,accept()不会阻塞住,立即返回-1,同时errno = EWOULDBLOCK

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376812.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

socket 端口和地址复用

https://blog.csdn.net/weibo1230123/article/details/79978745 https://blog.csdn.net/weixin_42157432/article/details/115560824 在linux socket网络编程中&#xff0c;大规模并发TCP或UDP连接时&#xff0c;经常会用到端口复用&#xff1a; int opt 1; if (setsockopt…

MyEclipse老是弹出problem occurred窗口

有的时候是因为jsp页面中的java脚本有误&#xff0c;比如说<% String name"";>就会出现错误&#xff0c;因为结束标签少了一个百分号&#xff05;。转载于:https://www.cnblogs.com/passer1991/archive/2013/03/15/2961624.html

Mysql中代替like模糊查询的一种方法

使用Mysql的函数instr,可代替传统的like方式查询,并且速度更快。 instr函数&#xff0c;第一个参数是字段&#xff0c;第二个参数是要查询的串&#xff0c;返回串的位置&#xff0c;第一个是1&#xff0c;如果没找到就是0. 例如&#xff1a; select username from prefix_user …

Linux网络故障排查命令(ifconfig、ping、telnet、netstat、lsof、nc、curl、tcpdump)

目录ifconfig-s&#xff0c;显示网卡信息的精简列表-a、up、down将IP地址绑定到某个网卡&#xff0c;以及解绑操作pingtelnetnetstatlsofnc模拟一个服务器程序和客户端程序进行通信发送文件curltcpdump参数连接一个正常的监听端口ifconfig 该命令用来查看当前系统的网卡和IP地…

My Oracle Support Metalink站点最近将放弃flash界面转而使用ADF HTML

根据oracle官方博客的报道《The New My Oracle Support User Interface (HTML-based) 》&#xff0c; MY ORACLE SUPPORT开发team会在最近将support.oracle.com站点从原来的flash界面迁移到基于ADF HTML的用户界面上。 实际上在2012年的 January 27&#xff0c; MOS开发team就…

心跳检测以及应用层心跳包机制设计

博主联系方式&#xff1a; QQ:1540984562 微信&#xff1a;wxid_nz49532kbh9u22 QQ交流群&#xff1a;750313950&#xff08;嵌入式方向&#xff09; QQ交流群&#xff1a;856398158&#xff08;后端方向&#xff09; 目录心跳检测应用场景死连接情况保活传递有效业务数据心跳包…

一个DBA的工作写照

一个DBA的工作写照&#xff0c; 一个DBA的内心 Know the DBA Mind! DBA也是 IT民工啊&#xff0c; 民工何苦为难民工&#xff01; 转载于:https://www.cnblogs.com/macleanoracle/archive/2013/03/19/2968227.html

UVALive 6257 Chemist's vows --一道题的三种解法(模拟,DFS,DP)

题意&#xff1a;给一个元素周期表的元素符号&#xff08;114种&#xff09;&#xff0c;再给一个串&#xff0c;问这个串能否有这些元素符号组成&#xff08;全为小写&#xff09;。 解法1&#xff1a;动态规划 定义&#xff1a;dp[i]表示到 i 这个字符为止&#xff0c;能否有…

hdu 1025(最长非递减子序列的n*log(n)求法)

题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1025 经典题。。。最长非递减序列的n*log(n)求法。。。orz... View Code 1 #include<iostream>2 const int N500007;3 using namespace std;4 int city[N];5 int dp[N];//dp[i]保存的是长度为i的最长不降…

消息队列重要机制讲解以及MQ设计思路(kafka、rabbitmq、rocketmq)

目录《Kafka篇》简述kafka的架构设计原理&#xff08;入口点&#xff09;消息队列有哪些作用&#xff08;简单&#xff09;消息队列的优缺点&#xff0c;使用场景&#xff08;基础&#xff09;消息队列如何保证消息可靠传输死信队列是什么&#xff1f;延时队列是什么&#xff1…

数据库归档模式

1、在sys身份下登陆oracle&#xff0c;执行命令archive log list; SQL> archive log list; Database log mode Archive Mode Automatic archival Enabled Archive destination USE_DB_RECOVERY_FILE_DEST Oldest online log sequence …

转载|网络编程中阻塞式函数的底层逻辑

逛知乎看到的&#xff0c;觉得写的挺透彻的&#xff0c;转载一下&#xff0c;原文链接&#xff1a;Unix网络编程里的阻塞是在操作系统的内核态创建一个线程来死循环吗&#xff1f; 原文以阻塞式的recv函数作为讲解&#xff0c;但是所有阻塞式的api底层逻辑基本相通。 下面是正文…

树的存储结构2 - 数据结构和算法42

树的存储结构 让编程改变世界 Change the world by program 孩子表示法 我们这次换个角度来考虑&#xff0c;由于树中每个结点可能有多棵子树&#xff0c;可以考虑用多重链表来实现。 就像我们虽然有计划生育&#xff0c;但我们还是无法确保每个家庭只养育一个孩子的冲动&a…

Sharepoint 2013 发布功能(Publishing features)

一、默认情况下&#xff0c;在创建网站集时&#xff0c;只有选择的模板为‘ Publishing Portal&#xff08;发布门户&#xff09;’与‘ Enterprise Wiki&#xff08;企业 Wiki&#xff09;’时才默认启用发布功能&#xff0c;如下图所示&#xff1a; 二、发布功能包含两块&…

【草稿】windows + vscode 远程开发

主要分为三个步骤&#xff1a; 1、开启openssh服务 2、通过ssh命令连接到远程服务器 3、通过vscode连接远程服务器进行开发调试 ssh概念 SSH是较可靠&#xff0c;专为远程登陆会话和其他网络服务提供安全性得协议&#xff0c;利用ssh协议可以有效防止远程管理过程中得信息…

POJ3185(简单BFS,主要做测试使用)

没事做水了一道POJ的简单BFS的题目 这道题的数据范围是20,所以状态总数就是&#xff08;1<<20&#xff09; 第一次提交使用STL的queue&#xff0c;并且是在队首判断是否达到终点&#xff0c;达到终点就退出&#xff0c;超时&#xff1a;&#xff08;其实这里我是很不明白…

sql server根据表中数据生成insert语句

几个收藏的根据数据库生成Insert语句的存储过程[修正版]----根据表中数据生成insert语句的存储过程--建立存储过程&#xff0c;执行spGenInsertSQL 表名--感谢playyuer----感谢szyicol--CREATEproc[dbo].[spGenInsertSQL](tablenamevarchar(256))asbegindeclaresqlvarchar(8000…

杂感无题|

今天中午和组里面的人吃饭&#xff0c;聊起了科兴跳楼的事情。这事其实前几天我华为的mentor就转给我了&#xff0c;当时也没太在意&#xff0c;在脉脉上看了看&#xff0c;也不知晓是谁&#xff0c;想着可能又是抑郁症吧。 饭后依旧绕着食堂散步&#xff0c;ly说那个人好像还是…

uva1366_Martian Mining_简单DP

题目不难&#xff0c;却想了好长时间&#xff0c;目测自己DP还是很水。。。囧 思路&#xff1a;舍f[i][j]为前i行j列的最大矿总量不难推出状态转移方程为f[i][j]max(f[i-1][j]line[i][j],f[i][j-1]row[j][i]) 其中line[i][j]为第i行前j个A矿的和&#xff08;a[i][1]a[i][2]...a…

数学图形之Boy surface

这是一个姓Boy的人发现的,所以取名为Boy surface.该图形与罗马图形有点相似,都是三分的图形.它甚至可以说是由罗马曲面变化而成的. 本文将展示几种Boy曲面的生成算法和切图,使用自己定义语法的脚本代码生成数学图形.相关软件参见:数学图形可视化工具,该软件免费开源.QQ交流群: …