C++实现TCP服务器端非阻塞方式同时和多个客户端通信

本文介绍了C++实现的非阻塞方式网络通信,通过服务器端不断循环调用accept()函数接收客户端请求,利用list存储连接套接字。服务器与客户端可以同时进行数据交互,当一方发送'end'时终止通信,服务器可继续接受其他客户端连接。示例展示了非阻塞模式下服务器与多个客户端通信的流程。
摘要由CSDN通过智能技术生成

非阻塞方式网络通讯

在之前的文章中,无论是点对点的还是一对多的服务器客户端通信均采用了阻塞模式:
C++实现服务器端和客户端交互发送消息
C++实现TCP服务器端同时和多个客户端通信(多线程)

socket编程中,阻塞与非阻塞的区别有哪些?
阻塞:一般的I/O操作可以在新建的流中运用.在服务器回应前它等待客户端发送一个空白的行.当会话结束时,服务器关闭流和客户端socket.如果在队列中没有请示,那么这个方法将会等待一个请求的到来.这个行为叫阻塞.accept()方法将会阻塞服务器线程直到一个呼叫到来.当5个连接处理完闭之后,服务器退出.任何的在队列中的呼叫将会被取消.
非阻塞:非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。例如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。

在编写服务器端程序时,非阻塞方式数据立即返回,所以我们需要不断循环调用accpet()函数接收客户端的请求。
因为需要对连接的socket表进行大量的增删操作,所以使用c++容器list进行存储。

服务器端

#include<iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include<vector>
#include<List>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define PORT  65432
#define buff_len 500
int main()
{
	//初始化winsock2.DLL
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载winsock.dll失败!" << endl;
		return 0;
	}
	//创建套接字
	SOCKET  sock_server;
	if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
		WSACleanup();
		return 0;
	}
	//设置为非阻塞方式
	u_long ul = 1;
	if (ioctlsocket(sock_server, FIONBIO, &ul) == SOCKET_ERROR)
	{
		cout << "ioctlsocket failure, error:" << GetLastError() << endl;
		WSACleanup();
		return 0;
	}
	//绑定端口和Ip
	sockaddr_in addr;
	int addr_len = sizeof(struct sockaddr_in);
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);//绑定本机的环回地址
	if (SOCKET_ERROR == bind(sock_server, (SOCKADDR*)&addr, sizeof(sockaddr_in)))
	{
		cout << "地址绑定失败!错误代码:" << WSAGetLastError() << endl;
		closesocket(sock_server);
		WSACleanup();
		return 0;
	}
	//将套接字设为监听状态
	int size=listen(sock_server, 0);
	if (size != 0)
	{
		cout << "listen函数调用失败!\n";
		closesocket(sock_server);
		WSACleanup();
		return 0;
	}
	list<SOCKET> liSock;//将连接的套接字存储在list中
	char msg[buff_len];//发送数据缓冲区
	char msgbuffer[buff_len];//接收数据缓冲区
	int n = 0; //标记已经连接的套接字数量
	//实现交互
	while (1)
	{
		sockaddr_in  client_addr;
		auto sc = accept(sock_server, (struct sockaddr*)&client_addr, &addr_len);
		if (sc == INVALID_SOCKET)
		{
			if (GetLastError() == WSAEWOULDBLOCK)
			{
				cout << "本次 accept函数没有客户端建立连接!" << endl;
				Sleep(100);//设置令其间隔一段时间
			}
			else
			{
				cout << "accept函数调用失败!网络异常程序退出" << endl;
				closesocket(sock_server);
				WSACleanup();
				return 0;
			}
		}
		else
		{
			cout << "服务器成功和:" << sc << "建立连接!" << endl;
			liSock.push_back(sc);//将建立连接的套接字压入list
		}
		auto it = liSock.begin();
		while (it != liSock.end())
		{
			auto itTemp = it++;
			cout << "请输入服务器端向" << *itTemp << "发送数据" << endl;
			cin.getline(msg, sizeof(msg));
			int size = send(*itTemp, msg, sizeof(msg), 0);
			if (strcmp(msg, "end\0") == 0)
			{
				cout << "关闭和" << *itTemp << "的连接!" << endl;
				liSock.erase(itTemp);//移除已关闭连接的套接字
				system("pause");
			}
			if (size <= 0)
			{
				if (GetLastError() == WSAEWOULDBLOCK)
				{
					cout << "Send data failure!\n";
				}
				else
				{
					cout << *itTemp << " is closed" << endl;
					liSock.erase(itTemp);
					system("pause");
				}
			}
		}
		it = liSock.begin();
		while (it != liSock.end())
		{
			auto itTemp = it++;
			size = recv(*itTemp, msgbuffer, sizeof(msgbuffer), 0);
			if (size <= 0)
			{
				if (GetLastError() == WSAEWOULDBLOCK)
				{
					cout << "recv data failure!\n";
				}
				else
				{
					cout << *itTemp << " is closed" << endl;
					liSock.erase(itTemp);
					system("pause");
				}
			}
			else
			{
				cout << *itTemp << "	说:	" << msgbuffer << endl;
				system("pause");
			}
		}
	}
	return 0;
}

客户端

#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
using  namespace std;
#pragma comment(lib, "ws2_32.lib")
#define PORT 65432
int  main() 
{
	//初始化winsock2.DLL
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载winsock.dll失败!" << endl;
		return 0;
	}
	//创建套接字
	SOCKET  sock_client; 
	if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
		WSACleanup();
		return 0;
	}
	//连接服务器
	sockaddr_in   addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);//绑定本机的环回地址
	int len = sizeof(sockaddr_in);
	if (connect(sock_client, (SOCKADDR*)&addr, len) == SOCKET_ERROR) {
		cout << "连接失败!错误代码:" << WSAGetLastError() << endl;
		return 0;
	}
	//实现交互部分,客户端先接收后发送数据
	while (1)
	{
		//接收服务端的消息
		char msgbuffer[1000] = { 0 };
		int size = recv(sock_client, msgbuffer, sizeof(msgbuffer), 0);
		if (strcmp(msgbuffer, "end\0") == 0)
		{
			cout << "服务器端已经关闭连接!" << endl;
			break;
		}
		if (size < 0)
		{
			cout << "接收信息失败!错误代码:" << WSAGetLastError() << endl;
			break;
		}
		else if (size == 0)
		{
			cout << "对方已经关闭连接" << endl;
			break;
		}
		else cout << "The message from Server:" << msgbuffer << endl;

		//从键盘输入一行文字发送给服务器
		msgbuffer[999] =  0 ;
		cout << "从键盘输入发给服务器的信息:" << endl;
		cin.getline(msgbuffer, sizeof(msgbuffer));
		if (strcmp(msgbuffer, "end\0") == 0)
		{
			cout << "关闭连接!" << endl;
			break;
		}
		int ret = send(sock_client, msgbuffer, sizeof(msgbuffer), 0);
		if (ret == SOCKET_ERROR || ret == 0)
		{
			cout << "发送信息失败!错误代码:" << WSAGetLastError() << endl;
			break;
		}
		else cout << "信息发送成功!" << endl;
	}
	closesocket(sock_client);
	WSACleanup();
	return 0;
}

我们用建立连接时服务器端接收的客户端套接字来唯一标识该客户端。
服务器端可以随时接收客户端的连接并与其进行交互。

运行实例

通讯建立后首先由服务器端发送消息,客户端接收消息;接着客户端发送消息,服务器端接收消息,实现交互发送消息。
服务器同时可以和多个客户端建立连接,进行交互;
在某次交互中,服务器端或某客户端有一方发送"end"即终止服务器与其的通信;服务器还可以继续接收其他客户端的请求,与其他客户端通信。

实例展示了非阻塞模式下服务器与客户端通信的运行情况:在这里插入图片描述
在这里插入图片描述
为了便于展示,在程序中调用了Sleep(100)以及system("pause")等函数,可以看到在服务器与客户端连接后还不断调用accpet函数接收连接请求,在服务器发送数据后,暂时未收到客户端发送的数据,服务器recv函数立即返回,输出recv data failure

  • 6
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新西兰做的饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值