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,一经查实,立即删除!

相关文章

css3实现头像旋转360度

css样式: .div a img{ width: 88px; height: 88px; border-radius: 88px; transition: all 1.2s ease-out 0s;}.div a img:hover{ -webkit-transform:rotate(360deg); -moz-transform:rotate(360deg); -o-transform:rotate(360deg); -ms-transform:rotate(360deg); transform:r…

POJ 2488 深搜

POJ 2488 深搜 要求字典序的顺序。 1 #include <iostream>2 #include <stdio.h>3 #include <string.h>4 using namespace std;5 int n,m,cnt;6 bool success;7 bool sign[30][30];8 int step[30][2];9 int dir[8][2]{ 10 -2,-1,-2,1, 11 …

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

C++网络编程快速入门(四):EPOLL模型使用

目录基本使用方法step1:创建epollfdstep2:将fd绑定到epollfdstep3:调用epoll_wait检测事件epoll_wait与poll、select区别所在水平触发与边缘触发基本使用方法 step1:创建epollfd 创建一个epollfd&#xff0c;若epoll_create调用成功&#xff0c;则返回一个非负值的epollfd&am…

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

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

两种大小端判断的方式

网络通信是按照字节流进行数据交换的&#xff0c;主机根据不同的CPU型号可能是大段存储&#xff0c;也可能是小端存储。而网络字节序在TCP/IP协议中已经规定好了&#xff0c;采用大端的排序方式。 所以网络通信中一般将需要传输的整数型值转换成网络字节序。 从本机字节序转换成…

把数据库复制成脚本(包含远程以及数据库数据)

1.启动VS 2.服务器资源管理器 3.连接需要的数据库 4.右键数据库 选择publist to provider.... 5.剩下的 选择数据库 选择存放地址 下一步 这方法应该是用在把08的数据还原到05上面 明天用这个方法去盗取哈公司的数据库 看行不行转载于:https://www.cnblogs.com/Rock-Lee/a…

代理模式用来初始化的延迟下载

package 设计模式; //代理模式实现延迟加载来减小启动时间 //数据库查询接口 interface IDBQery{ public String request(); }class DBQuery implements IDBQery {//创建一个DBQery非常耗时的&#xff0c;这里面我可以在需要DBQuery的时候在创建public DBQuery(){try {Thread.s…

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就…

BF算法

BF(Brute Force)算法是普通的模式匹配算法&#xff0c;BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配&#xff0c;若相等&#xff0c;则继续比较S的第二个字符和 T的第二个字符&#xff1b;若不相等&#xff0c;则比较S的第二个字符和T的第一个字符&…

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

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

【APUE】孤儿进程与僵死进程

基本概念&#xff1a; 在unix/linux中&#xff0c;正常情况下&#xff0c;子进程是通过父进程创建的&#xff0c;子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后&#xff0c…

一个DBA的工作写照

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

使用mutex和条件变量实现信号量

c提供了互斥量&#xff1a;mutex和条件变量&#xff1a;condition_variable&#xff0c;但是并没有信号量&#xff1a;semaphore。而linux和windows系统库会提供的。下面简单介绍一下信号量的特性&#xff0c;然后给出一个简单的demo&#xff0c;使用mutex condition_variable…

2014-07-28 使用Axure RP进行手机端BBS的原型设计

今天是在吾索实习的第14天。因本公司的微信公众号需要有一个对外的技术交流平台&#xff0c;所以我们小组打算设计一个手机端的BBS以满足其要求。首先&#xff0c;我们需要做的是进行数据库设计与原型设计&#xff0c;然后提交给经理验收&#xff0c;看看是否合理&#xff0c;是…

jquery exif + lazyload实现延迟加载并显示相片exif信息

对一个摄影爱好者来说&#xff0c;从高手的作品中学习是非常有用的。而照片的光圈&#xff0c;快门&#xff0c;感光度等信息是关注的重点。 上代码&#xff1a; 1 <script src"../js/jquery.js" type"text/javascript"></script>2 <script…

undefined reference to `pthread_create‘(linux下Clion使用thread报错)

完整报错&#xff1a; [ Build | AsyncLogger | Debug ] /snap/clion/169/bin/cmake/linux/bin/cmake --build /home/dyy/CLionProjects/AsyncLogger/cmake-build-debug --target AsyncLogger -- -j 6 [ 50%] Linking CXX executable AsyncLogger /usr/bin/ld: CMakeFiles/Asy…

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

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