【Linux】Socket阻塞和非阻塞、同步与异步

创作不易,本篇文章如果帮助到了你,还请点赞 关注支持一下♡>𖥦<)!!

主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步!

🔥Linux系列专栏:Linux基础 🔥

给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ


目录

阻塞

阻塞模式下,send函数返回时,只代表需要发送的数据已经被拷贝到内核的发送缓冲区

并且如果发送缓冲区有足够的空间,数据可能已经开始从发送缓冲区发送到网络上。但是并不代表数据已经成功地被接收方接收

当发送缓冲区空间不够大的时候,等到发送缓冲区空间足够大再发送

调用recv函数并且套接字没有数据可读时,recv函数会阻塞当前线程,直到有数据可读或者出现错误。

如果 recv 函数返回,返回值代表内核的接收缓冲区中的数据已经被拷贝到用户空间

cpp 复制代码
/* Windows下UDP阻塞socket*/

#include <iostream>
#include <WinSock2.h>
using namespace std;

#pragma comment(lib,"Ws2_32.lib")

int main()
{
	//1.加载库,相当于将dll拷贝到exe路径下
	WORD wVersion = MAKEWORD(2, 2);
	WSADATA wsadata;
	int errcode = 0;
	errcode = WSAStartup(wVersion, &wsadata);
		//1.1判断WSAStartup是否成功
		if (errcode != 0)
		{
			cout << "WSAStartup error:" << errcode << endl;
			return -1;
		}
		//1.2判断版本是否正确
		if (LOBYTE(wsadata.wVersion)!= 2 || HIBYTE(wsadata.wVersion) != 2)
		{
			cout << "WSAStartup wVersion error" << endl;
			WSACleanup();			
				return -1;	
		}
		else //库和版本都加载成功
		{
			cout << "WSAStartup success!" << endl;
		}
	//2.创建套接字
	SOCKET sock;
	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
		//2.1判断套接字是否创建成功
		if (sock == INVALID_SOCKET)		//INVALID无效的
		{
			cout << "set socket error:" << WSAGetLastError() << endl;
			WSACleanup();
			return -1;
		}
		else   //套接字创建成功
		{
			cout << "set socket success!" << endl;
		}
	//3.绑定ip和端口号
	struct sockaddr_in addrServer;
		addrServer.sin_family = AF_INET;
		addrServer.sin_addr.S_un.S_addr = INADDR_ANY;
		addrServer.sin_port = htons(8080);	//htons将端口号转换为大端模式
	errcode = bind(sock, (sockaddr*)&addrServer, sizeof(addrServer));
		//3.1判断绑定是否成功
		if (errcode == SOCKET_ERROR)
		{
			cout << "bind error:" << WSAGetLastError() << endl;
			//关闭套接字
			closesocket(sock);
			//卸载库
			WSACleanup();
			return -1;
		}
		else
		{
			cout << "bind success!" << endl;
		}
	//4.循环接收发送消息
	int recvNum = 0;
	int sendNum = 0;
	char recvbuf[1024] = "";
	char sendbuf[1024] = "";
	struct sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);
	while (true)
	{
		//接收消息
		recvNum = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&addrClient, &addrClientSize);
			//判断接收成功
			if (recvNum > 0)
			{
				//打印收到的数据
				//192.168.1.1------十进制四等分字符串类型ip地址:inet_addr
				//ulong类型ip地址:inet_ntoa
				cout << "来自ip:" << inet_ntoa(addrClient.sin_addr) << " 的消息: " << recvbuf << endl;
			}
			else if (recvNum == 0)		//连接关闭
			{
				cout << "connection closed" << endl;
				break;
			}
			else                        //接收错误
			{
				cout << "recvfrom error:" << WSAGetLastError() << endl;
				break;
			}
		//发送消息
			gets_s(sendbuf);
			sendNum = sendto(sock, sendbuf, sizeof(sendbuf), 0, (sockaddr*)&addrClient, addrClientSize);
			//判断发送成功
			if (sendNum != 0)
			{
				cout << "send success!" << endl;
			}
			else
			{
				cout << "sendto error:" << WSAGetLastError() << endl;
			}
	}
	//5.关闭套接字
	closesocket(sock);
	//6.卸载库
	WSACleanup();
	return 0;
}

阻塞 IO 开发简单,但不适合多个客户端高并发


非阻塞

在非阻塞模式下,send函数将数据放入内核的发送缓冲区,并立即返回

但是,如果发送缓冲区没有足够的空间来容纳全部数据,send函数会返回已经成功拷贝到发送缓冲区的字节数,有多少空间就往里拷贝多少内容,而不是全部要发送的数据

当发送缓冲区空间不够大的时候,有多少空间就往里拷贝多少内容,剩下的数据应用程序自己处理

当调用recv函数并且套接字没有数据可读 时,recv函数会立即返回一个错误 (EWOULDBLOCK或EAGAIN),而不是阻塞当前线程。

cpp 复制代码
/* Windows下UDP非阻塞socket*/
#include <iostream>
#include <WinSock2.h>
using namespace std;

#pragma comment(lib,"Ws2_32.lib")

int main()
{
	//1.加载库,相当于将dll拷贝到exe路径下
	WORD wVersion = MAKEWORD(2, 2);
	WSADATA wsadata;
	int errcode = 0;
	errcode = WSAStartup(wVersion, &wsadata);
		//1.1判断WSAStartup是否成功
		if (errcode != 0)
		{
			cout << "WSAStartup error:" << errcode << endl;
			return -1;
		}
		//1.2判断版本是否正确
		if (LOBYTE(wsadata.wVersion)!= 2 || HIBYTE(wsadata.wVersion) != 2)
		{
			cout << "WSAStartup wVersion error" << endl;
			WSACleanup();			
				return -1;	
		}
		else //库和版本都加载成功
		{
			cout << "WSAStartup success!" << endl;
		}
	//2.创建套接字
	SOCKET sock;
	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

	//Windows下设置socket为非阻塞
	u_long val = 1;
	ioctlsocket(sock, FIONBIO, &val);

	//Linux下:
	//int flags = fcntl(sock, F_GETFL, 0);
	//fcntl(sock, F_SETFL, flags | O_NONBLOCK);

		//2.1判断套接字是否创建成功
		if (sock == INVALID_SOCKET)		//INVALID无效的
		{
			cout << "set socket error:" << WSAGetLastError() << endl;
			WSACleanup();
			return -1;
		}
		else   //套接字创建成功
		{
			cout << "set socket success!" << endl;
		}
	//3.绑定ip和端口号
	struct sockaddr_in addrServer;
		addrServer.sin_family = AF_INET;
		addrServer.sin_addr.S_un.S_addr = INADDR_ANY;
		addrServer.sin_port = htons(8080);	//htons将端口号转换为大端模式
	errcode = bind(sock, (sockaddr*)&addrServer, sizeof(addrServer));
		//3.1判断绑定是否成功
		if (errcode == SOCKET_ERROR)
		{
			cout << "bind error:" << WSAGetLastError() << endl;
			//关闭套接字
			closesocket(sock);
			//卸载库
			WSACleanup();
			return -1;
		}
		else
		{
			cout << "bind success!" << endl;
		}
	//4.循环接收发送消息
	int recvNum = 0;
	int sendNum = 0;
	char recvbuf[1024] = "";
	char sendbuf[1024] = "";
	struct sockaddr_in addrClient;
	int addrClientSize = sizeof(addrClient);
	while (true)
	{
		//接收消息
		recvNum = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (sockaddr*)&addrClient, &addrClientSize);
			//判断接收成功
			if (recvNum > 0)
			{
				//打印收到的数据
				//192.168.1.1------十进制四等分字符串类型ip地址:inet_addr
				//ulong类型ip地址:inet_ntoa
				cout << "来自ip:" << inet_ntoa(addrClient.sin_addr) << " 的消息: " << recvbuf << endl;
			}
			else if (recvNum == 0)		//连接关闭
			{
				cout << "connection closed" << endl;
				break;
			}
			else if (WSAGetLastError() != 10035) {		//非阻塞
			{
				cout << "recvfrom error:" << WSAGetLastError() << endl;
				break;
			}

	}
	//5.关闭套接字
	closesocket(sock);
	//6.卸载库
	WSACleanup();
	return 0;
}

同步和异步

  • 同步

同步模型:一个操作必须等待前一个操作完成后才能进行。比如说,当客户端向服务器发送一个请求并期望得到响应时,它会处于阻塞状态,直到服务器返回响应

read(), write(), recv(), send()


  • 异步

异步模型则打破了这种顺序执行的限制。在异步 I/O 中,客户端发送请求后不会被阻塞,而是可以继续执行其他操作。当服务器的响应准备好时,通过回调函数、事件机制或者消息队列等方式通知客户端

异步模型的优点在于能够极大地提高系统的并发性和响应性。它能够充分利用系统资源,避免了因等待而造成的资源浪费

同步和异步的区分:是否是用户自己写代码实现的将数据从内核缓冲区拷贝到用户缓冲区,如果不是自己写代码实现的,通过回调函数、事件通知或其他机制告知用户 就是异步


|--------------------------------------|
| 大家的点赞、收藏、关注将是我更新的最大动力! 欢迎留言或私信建议或问题。 |

|---------------------------------------------------------------|
| 大家的支持和反馈对我来说意义重大,我会继续不断努力提供有价值的内容!如果本文哪里有错误的地方还请大家多多指出(●'◡'●) |

相关推荐
Web3探索者18 小时前
可视化服务器管理和传统命令行区别是什么?新手教程:Linux 运维到底该用图形界面还是 SSH 命令行?
linux·ssh
zylyehuo20 小时前
Linux系统中网线与USB网络共享冲突
linux
荣--1 天前
一键部署不是为了省时间 —— 它是把"买来的 PaaS"变成"自己的平台"的拐点
运维·zabbix·工程化·一键部署·平台化·边界设计
江华森1 天前
动手实战学 Docker — 从零到集群编排完全指南
运维
Avan_菜菜2 天前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
博客18002 天前
酷宝的使用方法,超好用的免费界面库,C++、MFC可用
c++·mfc·界面库·库来帮·酷宝
郝学胜_神的一滴2 天前
CMake 026:属性体系精讲、四大作用域全解 & 实战代码落地
c++·cmake
Sokach10152 天前
Linux Shell 脚本从零到能用:一个新手的一天学习总结
linux
SelectDB3 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
AlfredZhao3 天前
Docker 容器时区不对,`timedatectl` 不存在怎么办?
linux·timezone