简易TCP服务端升级为Select网络模型处理多客户端

本文介绍如何将简易TCP服务端从1对1阻塞模式升级到Select模型非阻塞模式,实现多客户端同时处理。文章详细展示了服务端代码修改过程,包括使用fd_set集合进行读写监听,通过select函数轮询所有文件描述符,以及处理客户端连接请求和数据收发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简易TCP服务端升级为Select网络模型

这篇文章简单介绍了如何建立服务端与客户端1对1的阻塞模式网络程序,链接如下
服务端与客户端1对1网络程序
今天,我们来把这个TCP服务端升级为select模型非阻塞模式来处理多客户端,客户端不变。
具体的流程可以看下面这张图
select模型服务端

服务端程序代码:

// Server.cpp : Defines the entry point for the console application.
//


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

#pragma comment(lib, "WS2_32.lib")
//表示链接WS2_32.lib这个库。
#define  PORT 1024

vector<SOCKET> g_clients;

//发送和接收都封装成函数
int dealWrite(SOCKET s)
{
	char msg[] = { "Hello client" };
	int size = send(s, msg, sizeof(msg), 0);
	if (size == SOCKET_ERROR)
	{
		cout << "发送信息失败! 错误代码:" << WSAGetLastError() << endl;
		return -1;
	}
	else if (size == 0)
	{
		cout << "对方已关闭连接" << endl;
		return -1;
	}
	else
	{
		cout << "信息发送成功" << endl;
	}

	return 0;
}

int dealRead(SOCKET s)
{
	char recvMsg[128] = {};

	int size = recv(s, recvMsg, sizeof(recvMsg), 0);
	if (size == SOCKET_ERROR)
	{
		cout << "接收信息失败! 错误代码:" << WSAGetLastError() << endl;;
		return -1;
	}
	else if (size == 0)
	{
		cout << "对方已关闭连接" << endl;
		return -1;
	}
	else
	{
		cout << "The message from Client:" << recvMsg << endl;
	}

	return 0;
}
int main()
{
	SOCKET sock_server, newsock;
	struct sockaddr_in server_addr, client_addr;
	
	// 初始化 winsock2.dll[12/27/2017 MagicScaring]
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);        //生成版本号
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载 winsock.dll失败" << endl;
		return 0;
	}
	// 创建套接字 [12/27/2017 MagicScaring]
	if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败! 错误代码:" << WSAGetLastError() << endl;
		WSACleanup();                   //注销WinSock动态链接库
		return 0;
	}
	// 填写需要绑定的本地地址 [12/27/2017 MagicScaring]
	int addr_len = sizeof(struct sockaddr_in);
	memset((void*)&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sock_server, (struct sockaddr*)&server_addr, addr_len) != 0)
	{
		cout << "绑定失败!错误代码:" << WSAGetLastError() << endl;
		closesocket(sock_server);               //关闭已连接套接字
		WSACleanup();                           //注销WinSock动态链接库
		return 0;
	}

	// 开始监听 [12/27/2017 MagicScaring]
	if (listen(sock_server, 0) != 0)
	{
		cout << "listen调用失败!错误代码:" << WSAGetLastError() << endl;
		closesocket(sock_server);
		WSACleanup();
		return 0;
	}
	else
	{
		cout << "listening...." << endl;
	}

	// 循环:接收连接请求并收发数据 [12/27/2017 MagicScaring]
	while (true)
	{
		fd_set fdRead;
		fd_set fdWrite;
		fd_set fdExp;

		//FD_ZERO:清空一个文件描述符集合
		FD_ZERO(&fdRead);
		FD_ZERO(&fdWrite);
		FD_ZERO(&fdExp);

		//FD_SET:将监听的文件描述符,添加到监听集合中
		FD_SET(sock_server, &fdRead);		
		FD_SET(sock_server, &fdWrite);
		FD_SET(sock_server, &fdExp);

		for (size_t i = 0; i < g_clients.size(); ++i)
		{
			FD_SET(g_clients[i], &fdRead);
		}
		
		/*
		select函数原型:
		int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
		
		参数列表:
		nfds:		监听的所有文件描述符的最大描述符 + 1(内核采取轮询的方式)
		readfds:	读文件描述监听集合
		writefds:	写文件描述符集合
		exceptfds:	异常文件描述符集合
		timeout:	大于0:设置监听超时时长;NULL:阻塞监听;0:非阻塞监听

		返回值:
		大于0:所欲监听集合中,满足对应时间的总数
		0:没有满足的
		-1:出错error
		*/

		timeval t = { 0,500 };
		int ret = select(sock_server + 1, &fdRead, &fdWrite, &fdExp, NULL);
		if (ret < 0)
		{
			cout << "select is over" << endl;
			break;
		}

		//FD_ISSET:判断一个文件描述符是否在一个集合中,返回值:在1,不在0
		if (FD_ISSET(sock_server, &fdRead))
		{
			//FD_CLR:将一个文件描述符从集合中移除
			FD_CLR(sock_server, &fdRead);

			if ((newsock = accept(sock_server, (struct sockaddr*)&client_addr, &addr_len)) == INVALID_SOCKET)
			{
				cout << "accept 函数调用失败! 错误代码:" << WSAGetLastError() << endl;
				break;
			}

			cout << "成功接收到一个连接请求!" << endl;
			cout << "新客户端加入, socket:" << newsock << ",IP:" << inet_ntoa(client_addr.sin_addr)<< endl;

			
			if (!dealWrite(newsock))
			{
				g_clients.push_back(newsock);
			}
		}

		for (size_t i = 0; i < fdRead.fd_count; ++i)
		{
			if (-1 == dealRead(fdRead.fd_array[i]))
			{
				auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[i]);
				if (iter != g_clients.end())
				{
					g_clients.erase(iter);
				}
			}
		}
	}

	//关闭套接字
	for (size_t i = 0; i < g_clients.size(); ++i)
	{
		closesocket(g_clients[i]);
	}

	//清除winsock环境
	WSACleanup();

	return 0;
}

客户端程序代码:

// Client.cpp : Defines the entry point for the console application.
//

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

#pragma comment(lib, "WS2_32.lib")
//表示链接WS2_32.lib这个库。
#define  PORT 1024

int main()
{
	SOCKET sock_client;
	struct sockaddr_in server_addr;
	int addr_len = sizeof(struct sockaddr_in);
	char msgbuffer[128];
	memset(msgbuffer, 0, sizeof(msgbuffer));
	// 初始化 winsock2.dll[12/27/2017 MagicScaring]
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);		//生成版本号
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载 winsock.dll失败" << endl;
		return 0;
	}
	// 创建套接字 [12/27/2017 MagicScaring]
	if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败! 错误代码:" << WSAGetLastError() << endl;
		WSACleanup();					//注销WinSock动态链接库
		return 0;
	}
	// 填写服务器地址 [12/27/2017 MagicScaring]
	char IP[20] = { "127.0.0.1" };
	memset((void*)&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.s_addr = inet_addr(IP);

	// 与服务器建立连接 [12/27/2017 MagicScaring]
	if (connect(sock_client, (struct sockaddr*)&server_addr, addr_len) == SOCKET_ERROR)
	{
		cout << "连接失败! 错误代码:" << WSAGetLastError() << endl;
		closesocket(sock_client);
		WSACleanup();
		return 0;
	}
	while (true)
	{
		int size;
		if ((size = recv(sock_client, msgbuffer, sizeof(msgbuffer), 0)) == SOCKET_ERROR)
		{
			cout << "接收信息失败! 错误代码:" << WSAGetLastError() << endl;
			closesocket(sock_client);
			WSACleanup();
			return 0;
		}
		else if (size == 0)
		{
			cout << "对方已关闭连接" << endl;
			closesocket(sock_client);
			WSACleanup();
			return 0;
		}
		else
		{
			cout << "The message from Server:" << msgbuffer << endl;
		}
		char send_msg[128] = { "Hello server" };
		if ((size = send(sock_client, send_msg, sizeof(send_msg), 0)) == SOCKET_ERROR)
		{
			cout << "发送信息失败! 错误代码:" << WSAGetLastError() << endl;
		}
		else if (size == 0)
		{
			cout << "对方已关闭连接" << endl;
		}
		else
		{
			cout << "信息发送成功" << endl;
		}
	}
	closesocket(sock_client);
	WSACleanup();

	return 0;
}

服务器程序运行结果:
在这里插入图片描述
客户端程序1运行结果:
在这里插入图片描述
客户端程序2运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值