C++实现TCP服务器端利用Select模型同时和多个客户端通信

服务器端利用Select模型通信


服务器端利用Select模型通信
Select模型解决传统C/S模型中accpet阻塞等待客户端链接的问题,但其只解决accpet阻塞的问题,不解决send()、recv()执行阻塞的问题。

服务器端

#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_server,newsock;
	if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
		WSACleanup();
		return 0;
	}
	//绑定端口和Ip
	sockaddr_in addr,client_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);//ip地址转网络字节序
	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;
	}
	fd_set fdsock;//套接字集
	fd_set fdread;//读取
	fd_set fdwrite;//写入
	char msgbuffer[500];//接收数据缓冲区
	char msg[500];//发送数据缓冲区
	FD_ZERO(&fdsock);
	FD_SET(sock_server, &fdsock);
	timeval tv;//时间计时器
	//循环接收连接请求并收发数据
	while (1) 
	{
		tv.tv_sec = 1;//select等待两秒后返回,避免被锁死以及马上返回
		tv.tv_usec = 0;
		fdread = fdsock;
		fdwrite = fdsock;//初始化
		if (select(0, &fdread, &fdwrite, NULL, &tv) == SOCKET_ERROR)
		{
			DWORD dw = GetLastError();
			if (dw == WSAENETDOWN)
			{
				printf("The network subsystem has failed\n");
				break;
			}
		}
		else
		{
			cout <<"连接的数目是:"<<fdsock.fd_count << endl;
			for (int i = 0; i < (int)fdsock.fd_count; i++)
			{
				if (FD_ISSET(fdsock.fd_array[i], &fdread))
				{
					if (fdsock.fd_array[i] == sock_server)//侦听套接字返回一个新的连接则是accpet
					{
						newsock = accept(sock_server, (struct sockaddr*)&client_addr, &addr_len);
						if (newsock == INVALID_SOCKET)
						{
							cout << "accept函数调用失败!\n";
							break;
						}
						cout << "服务器成功和:" << newsock << "建立连接!" << endl;
						inet_ntoa(client_addr.sin_addr),
							ntohs(client_addr.sin_port);
						FD_SET(newsock, &fdsock);//将新套接字加入fdsock
						continue;
					}
				}
			}
			for (int i = 0; i < (int)fdsock.fd_count; i++)//服务器发送数据
			{
				if (FD_ISSET(fdsock.fd_array[i], &fdwrite))
				{
					printf("服务器向%d发送数据:\n", fdsock.fd_array[i]);
					cin.getline(msg, sizeof(msg));
					int size = send(fdsock.fd_array[i], msg, sizeof(msg), 0);//给客户端发送一段信息
					if (strcmp(msg, "end\0") == 0)
					{
						cout << "关闭和" << fdsock.fd_array[i] << "的连接!" << endl;
						FD_CLR(fdsock.fd_array[i], &fdsock);
						continue;
					}
					if (size == SOCKET_ERROR || size == 0)
					{
						cout << "发送信息失败!错误代码:" << WSAGetLastError() << endl;
						FD_CLR(fdsock.fd_array[i], &fdsock);
					}
				}
			}
			for (int i = 0; i < (int)fdsock.fd_count; i++)//服务器接收消息
			{
				if (FD_ISSET(fdsock.fd_array[i], &fdwrite))
				{
					memset((void*)msgbuffer, 0, sizeof(msgbuffer));
					size = recv(fdsock.fd_array[i], msgbuffer, sizeof(msgbuffer), 0);//接收客户端的消息
					if (size <= 0)
					{
						if (size == 0) cout << fdsock.fd_array[i] <<"is closed" << endl;
						else
						{
							DWORD dw = GetLastError();//不同的网络错误信息
							switch (dw)
							{
							case WSAENETDOWN: printf("The network subsystem has failed\n");
								break;
							case WSAENETRESET: printf("The connection has been broken\n");
								break;
							case WSAESHUTDOWN: printf("The socket has been shut downd\n");
								break;
							case WSAETIMEDOUT: printf("The connection has been dropped\n ");
								break;
							case WSAECONNRESET: printf("The virtual circuit was reset\n");
								break;
							default: printf("program error,please check program\n");
							}
						}
						FD_CLR(fdsock.fd_array[i], &fdsock);
					}
					else cout << fdsock.fd_array[i] << "  说: " << msgbuffer << endl;
				}
			}
		}
	}
	for (int i = 0; i < fdsock.fd_count; i++)
	{
		closesocket(fdsock.fd_array[i]);
		FD_CLR(fdsock.fd_array[i], &fdsock);
	}
	WSACleanup();
	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[500] = { 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[499] =  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;
}

我们需要while循环不断轮询遍历所有接收的套接字集合进行收发数据。
我们用建立连接时服务器端接收的客户端套接字来唯一标识该客户端。

通过对比,我们可以看到Select模型的优势,在阻塞模式下Select模型综合了非阻塞的accept()函数不断监听的优势。

运行实例

服务器首先发送消息,之后等待客户端的回应。我们仍使用一方发出"end"结束会话,实例展示了服务器端或客户端交互或结束会话的示例。
在这里插入图片描述

实例展示连接数为1其实就是只有监听套接字在套接字集中,我们可以看到accpet()函数不阻塞不断监听新的链接,但send()函数和recv()仍阻塞等待客户端的消息。

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

新西兰做的饭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值