socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

socket的阻塞模式和非阻塞模式

无论是Windows还是Linux,默认创建socket都是阻塞模式的

在Linux中,可以再创建socket是直接将它设置为非阻塞模式

int socket (int __domain, int __type, int __protocol)

__type增加SOCK_NOBLOCK

不仅如此,在Linux上直接利用accept函数返回的代表与客户端通信的socket也提供了一个拓展函数accept4,直接将accept4返回的socket设置为非阻塞的

sendrecv函数在阻塞和非阻塞模式下的表现

sendrecv函数并不是直接向网络上发送数据和接收数据

send函数是将应用层发送缓冲区的数据拷贝到内核缓冲区中

recv函数是将内核缓冲区的数据拷贝到应用缓冲区

可以用下面这张图来描述:

image-20210706212341938

通过上图我们可以知道,不同的程序进行网络通信时,发送的一方会将内核缓冲区的数据通过网络传输给接收方的内核缓冲区。

在应用程序A与应用程序B建立TCP连接后,假设A不断调用send函数,会将数据不断拷贝到对应的内核缓冲区,如果应用程序不调用recv函数,那么在应用程序B的内核缓冲区被填满后,A的缓冲区也随后被填满,此时如果A继续调用send函数会有什么后果呢?

  • socket处于阻塞模式时,继续调用send/recv函数,程序会阻塞在send/recv调用处
  • socket处于非阻塞模式时,继续调用send/recv函数,会返回错误码
  1. socket阻塞模式下send函数的表现

    代码来自《C++服务器开发精髓》

    服务端代码:

    #include <sys/types.h> 
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <iostream>
    #include <string.h>int main(int argc, char* argv[])
    {//1.创建一个侦听socketint listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1){std::cout << "create listen socket error." << std::endl;return -1;}//2.初始化服务器地址struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1){std::cout << "bind listen socket error." << std::endl;close(listenfd);return -1;}//3.启动侦听if (listen(listenfd, SOMAXCONN) == -1){std::cout << "listen error." << std::endl;close(listenfd);return -1;}while (true){struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);//4. 接受客户端连接int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);if (clientfd != -1){         	//只接受连接,不调用recv收取任何数据std:: cout << "accept a client connection." << std::endl;}}//7.关闭侦听socketclose(listenfd);return 0;
    }

    客户端代码:

    /*** 验证阻塞模式下send函数的行为,client端* zhangyl 2018.12.17*/
    #include <sys/types.h> 
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <iostream>
    #include <string.h>#define SERVER_ADDRESS "127.0.0.1"
    #define SERVER_PORT     3000
    #define SEND_DATA       "helloworld"int main(int argc, char* argv[])
    {//1.创建一个socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1){std::cout << "create client socket error." << std::endl;return -1;}//2.连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1){std::cout << "connect socket error." << std::endl;close(clientfd);return -1;}//3. 不断向服务器发送数据,或者出错退出int count = 0;while (true){int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);if (ret != strlen(SEND_DATA)){std::cout << "send data error." << std::endl;break;} else{count ++;std::cout << "send data successfully, count = " << count << std::endl;}}//5. 关闭socketclose(clientfd);return 0;
    }

    先启动server在启动client,客户端会不断向服务端发送helloworld,每次发送成功后会打印计数器,运行一顿时间后,停止打印,计数器不再增加

20210706214738

当程序不再有输出,说明阻塞在某个函数gdb看一看

(gdb) bt
#0  0x00007ffff7d03690 in __libc_send (fd=3, buf=0x555555556045, len=10, flags=0) at ../sysdeps/unix/sysv/linux/send.c:28
#1  0x00005555555553bb in main (argc=1, argv=0x7fffffffdf28) at client.cpp:42
(gdb) 

果然是send函数

上面这个例子证明了如果一端一直发送数据,另一端不接收数据,内核缓冲区很快就会被填满,发生阻塞. 其实这里所说的内核缓冲区就是TCP窗口

我们现在利用tcpdump工具查看一下这种情况下TCP窗口的大小

22:01:57.543364 IP 127.0.0.1.53382 > 127.0.0.1.3000: Flags [S], seq 1832090129, win 65495, options [mss 65495,sackOK,TS val 451488646 ecr 0,nop,wscale 7], length 0
22:01:57.543379 IP 127.0.0.1.3000 > 127.0.0.1.53382: Flags [S.], seq 1797517498, ack 1832090130, win 65483, options [mss 65495,sackOK,TS val 451488646 ecr 451488646,nop,wscale 7], length 0
22:01:57.543386 IP 127.0.0.1.53382 > 127.0.0.1.3000: Flags [.], ack 1797517499, win 512, options [nop,nop,TS val 451488646 ecr 451488646], length 0
...
22:02:11.342670 IP 127.0.0.1.3000 > 127.0.0.1.53382: Flags [.], ack 1832177322, win 0, options [nop,nop,TS val 451502445 ecr 451488936], length 0

win就是TCP窗口的大小可以看出,逐渐减小最后变为零

  1. socket非阻塞模式下send函数的表现

    就是返回一个错误码,不阻塞了,略…

  2. socket阻塞模式下recv函数的表现

    阻塞了就…

  3. socket非阻塞模式下recv函数的表现

    recv在没有数据可读的情况下,会立即返回,返回值为-1

非阻塞模式下sendrecv函数返回值总结

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

详细介绍:

  1. 返回值大于0

    sendrecv函数返回值大于0时,表示发送或者接收多少字节.需要注意的是,在这种情况下,**判断send返回值是否等于要发送的字节数,而不是简单地判断返回值是否大于零

    int n = send(socketfd,buf,buf_length,0);if (n>0){printf("send successfully");}
    

    很多新手就会写出以上的代码(比如我…)虽然返回值大于零,但由于对端TCP窗口已满,搜易我们所期望发送的字节,并没有全部被对方接收,所以n的大小在区间(0,buf_length)内

    解决办法:

    • 在返回值等于buf_length时才认为正确
    • 在一个循环中调用send函数,如果一次性发送不完,记录偏移量,接着从偏移量处发送
  2. 返回值等于0

    • 对端关闭了连接
    • 特殊情况:send函数主动发送了0字节
  3. 小于零

    出错啦呗

TCP网络编程的基本流程

Linux与C++11多线程编程(学习笔记)

Linux select函数用法和原理

socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)

connect函数在阻塞和非阻塞模式下的行为

获取socket对应的接收缓冲区中的可读数据量

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

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

相关文章

国外优秀技术站点推荐

博客园准备在首页右下位置放一些国外优秀技术站点的链接&#xff0c;欢迎大家推荐自己喜欢的国外优秀技术站点&#xff0c;大家只要在评论中列出网址&#xff0c;并希望能写一点推荐理由。

connect函数在阻塞和非阻塞模式下的行为

connect函数在阻塞和非阻塞模式下的行为 当socket使用阻塞模式时,connect函数会阻塞到有明确结果才会返回,如果网络环境较差,可能要等一会,影响体验, 为了解决这个问题,我们使用异步connect技术 创建socket,将socket设置为非阻塞模式 调用connect函数,此时无论connect函数是…

让模块支持“导入”“导出”功能

让模块支持“导入”“导出”功能DNN模块可以支持导入导出功能&#xff0c;通过将模块内容导入到XML文件可以便于模块内容备份和转移&#xff0c;也可将模块内容事先以XML格式保存通过导入功能实现模块内容的批量录入。如图:[img]http://esshs.cnblogs.com/images/cnblogs_com/e…

获取socket对应的接收缓冲区中的可读数据量

获取socket对应的接收缓冲区中的可读数据量 本文介绍如何获取当前socket对应的接收缓冲区的可读数据量 在Linux上可以使用ioctl函数 #include <sys/ioctl.h>int ioctl (int __fd, unsigned long int __request, ...)来看一个例子: #include <sys/types.h> #in…

ASP.NET 2.0 Club Web Site Starter Kit 补丁

ASP.NET2.0 Club Web Site Starter Kit 具有一个很大的缺陷&#xff1a;不支持中文。这里给出两种解决方案供大家参考方法一&#xff1a;1&#xff09;由于大家大部分都是用SQL SERVER2005 EXPRESS开发的&#xff0c;所以在建立好Club需要的数据表后&#xff0c;将每一个表的v…

Linux epoll的用法

Linux epoll的用法 epollfd_create函数 #include <sys/epoll.h>int epoll_create (int __size)参数含义__size此参数从Linux 2.6.8后就不再使用了,但必须设置成大于零的值 返回值含义>0可用的epollfd-1调用失败 epollfd_ctl函数 有了epollfd,我们需要将要检测事件…

EVC4.0 PPC2003 Emulator 转中文版

1. 安装Windows Mobile 2003 Second Edition Emulator Images for Pocket PC - CHS.msi 2. 将C:\Program Files\Windows CE Tools\wce420\POCKET PC 2003\Emulation\emul.xml文件中的start "" "C:\Program Files\Common Files\Microsoft Shared\Windo…

第一个 Win32 窗口程序

第一个 Win32 窗口程序 程序骨架 int WinMain(){ // 设计窗口外观及交互响应&#xff0c;注册&#xff0c;申请专利RegisterClass(...) ;// 生产窗口 CreateWindow(...); // 展示窗口 ShowWindow(...); // 粉刷窗口 UpdateWindow(...);// 进入消息循环 while (GetMessage(...)…

在C#中利用DirectX实现声音播放

在c#中利用directx实现声音播放我感觉声音的播放比较简单。我们从播放声音开始。为什么我这么觉得&#xff1f;我也不知道。 这里是展示最简单的directx播放声音的例子,我尽量省略了无关的代码。最后的代码只有19行,够简单了吧&#xff1f; 准备工作&#xff1a; 1.安装了dire…

windows网络编程

windows网络编程 TCP编程 服务端 这里我们有几点需要注意: 使用WSAStartup初始化网络库,即将与socket函数相关dll文件加载到进程地址空间中退出时,使用WSACleanup()卸载相关dll文件与Linux使用close函数关闭socket不同,windows需要使用closesocket函数关闭socket WSAStart…

TCP服务器epoll的多种实现

TCP服务器epoll的多种实现 对于网络IO会涉及到两个系统对象 用户空间中进程或者线程操作系统内核 比如发生read操作时就会经历两个阶段 等待数据就绪将数据从内核缓冲区拷贝到用户缓冲区 由于各个阶段多有不同的情况,一组合么就产生了多种网络 IO 模型 阻塞IO 在Linux中…

软件的社交能力

两个人之间互不相识&#xff0c;则无法交往&#xff0c;两个系统之间相互不能识别&#xff0c;那么也就不可能相互通讯。但是人有一种社交能力&#xff0c;这种能力可以保证两个不相识的人&#xff0c;相互认识&#xff0c;开始交往。两个系统之间没有这种能力&#xff0c;所以…