C++高并发网络架构与实现——第三篇

目录

一,实现简单结构化传输信息,但是不能分辨是否是结构体。

二,实现通过网络数据报文的格式进行定义传输。

三,将多次发送包文数据升级为一次收发,主要将结构体进行整合,避免出错。(需要进行数据偏移)

四,添加一个接收缓冲区。

五,将服务器端改为select模型,从而实现可以处理多客户端的目标。

六,服务器升级为select处理多客户端模型,并且可以在某个客户端加入时,提醒已经连接的客户端。


今天实现任务:服务器升级为select处理多客户端模型,并且可以在某个客户端加入时,提醒已经连接的客户端。

  1. 实现简单结构化传输信息,但是不能分辨是否是结构体。
  2. 实现通过网络数据报文的格式进行定义传输。
  3. 将多次发送包文数据升级为一次收发,主要将结构体进行整合,避免出错。(需要进行数据偏移)
  4. 添加一个接收缓冲区。
  5. 将服务器端改为select模型,从而实现可以处理多客户端的目标。
  6. 服务器升级为select处理多客户端模型,并且可以在某个客户端加入时,提醒已经连接的客户端。

一,实现简单结构化传输信息,但是不能分辨是否是结构体。

将信息设置为结构体模式,并进行传输。

出现问题,因为客户端接收服务器回发的消息时,都是结构体模式,所以如果返回数据不是结构体模式,则会出错。

struct DataPackage
{
    int age;
    char name[32];
};

程序结果:

服务器:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

struct DataPackage
{
	int age;
	char name[32];
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,bind 绑定用于接受客户端连接的网络接口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "错误,绑定网络端口失败" << endl;
	}
	else
	{
		cout << "绑定网络端口成功" << endl;
	}

	//3,listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "错误,监听网络端口失败" << endl;
	}
	else
	{
		cout << "监听网络端口成功" << endl;
	}

	//4,accept 等待客户端连接
	sockaddr_in clientAddr;
	int nAddrlen = sizeof(clientAddr);
	SOCKET _cSock = INVALID_SOCKET;

	_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
	if (INVALID_SOCKET == _cSock)
	{
		cout << "错误,接受到无效的客户端连接" << endl;
	}

	cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;

	char _recvBuf[128] = {};
	while (true)
	{
		int nLen = recv(_cSock, _recvBuf, 128, 0);
		//5,接受客户端的请求数据
		if (nLen <= 0)
		{
			cout << "客户端已经退出,任务结束" << endl;
			break;
		}
		cout << "收到消息" << _recvBuf << endl;
		//6,处理请求
		if (0 == strcmp(_recvBuf, "getInfo"))
		{
			DataPackage dp = { 20, "小明" };
			//7.1,send 向客户端发送一条数据
			send(_cSock, (const char*)&dp, sizeof(DataPackage), 0);
			memset(_recvBuf, '\n',128);
		}
		else
		{
			char msgBuf[] = "???";
			//7.3,send 向客户端发送一条数据
			send(_cSock, msgBuf, strlen(msgBuf) + 1, 0);
		}
	}

	//8,关闭套接字closesocket
	closesocket(_sock);

	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

客户端:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

struct DataPackage
{
	int age;
	char name[32];
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,用Socket API建立建立TCP客户端
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,连接服务器 connect
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
	if (SOCKET_ERROR == ret)
	{
		cout << "错误,建立Socket失败" << endl;
	}
	else
	{
		cout << "建立Socket成功" << endl;
	}

	while (true)
	{
		//3,输入请求
		char cmdBuf[128] = {};
		cin >> cmdBuf;
		//4,处理请求
		if (0 == strcmp(cmdBuf, "exit"))
		{
			break;
		}
		else
		{
			//5,向服务器发送请求
			send(_sock, cmdBuf, strlen(cmdBuf) + 1, 0);
		}
		//6,接受服务器消息
		char recvBuf[128] = {};
		int nlen = recv(_sock, recvBuf, 256, 0);
		if (nlen > 0)
		{
			DataPackage *dp = (DataPackage *)recvBuf;
			cout << "接收到数据: " << "年纪:" << dp->age << "  姓名:" << dp->name << endl;
		}
	}
	//7,关闭套接字closesocket
	closesocket(_sock);
	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

二,实现通过网络数据报文的格式进行定义传输。

从第一个可以得出,如果仅仅使用结构体传输,则会产生错误,所以需要使用网络数据报文格式

报文有两个部分,包头和包体,是网络消息的基本单元
包头:描述本次消息报的大小,描述数据的作用
包体:数据

所以此时定义一个联合体,来描述数据的作用
enum CMD
{
    CMD_LOGIN,       //登入
    CMD_LOGOUT,      //登出
    CMD_ERROR,       //错误
};

运行截图:

服务器端代码:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGOUT,      //登出
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login
{
	char useName[32];
	char PassWord[32];
};

struct LoginResult
{
	int result;
};

struct Logout
{
	char userName[32];
};

struct LogoutResult
{
	int result;
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,bind 绑定用于接受客户端连接的网络接口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "错误,绑定网络端口失败" << endl;
	}
	else
	{
		cout << "绑定网络端口成功" << endl;
	}

	//3,listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "错误,监听网络端口失败" << endl;
	}
	else
	{
		cout << "监听网络端口成功" << endl;
	}

	//4,accept 等待客户端连接
	sockaddr_in clientAddr;
	int nAddrlen = sizeof(clientAddr);
	SOCKET _cSock = INVALID_SOCKET;

	_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
	if (INVALID_SOCKET == _cSock)
	{
		cout << "错误,接受到无效的客户端连接" << endl;
	}

	cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;

	while (true)
	{
		DataHeader header = {};
		int nLen = recv(_cSock, (char*)&header, sizeof(DataHeader), 0);
		//5,接受客户端的请求数据
		if (nLen <= 0)
		{
			cout << "客户端已经退出,任务结束" << endl;
			break;
		}
		cout << "收到命令:" << header.cmd << "  数据长度:" << header.dataLength << endl;

		switch (header.cmd)
		{
		case CMD_LOGIN:
		{
						  Login login = {};
						  recv(_cSock, (char*)&login, sizeof(Login), 0);
						  //忽略判断用户名密码是否正确的过程
						  LoginResult ret = { 1 };
						  DataHeader hd = { CMD_LOGIN };
						  send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
						  send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
		}
			break;
		case CMD_LOGOUT:
		{
						   Logout logout = {};
						   recv(_cSock, (char*)&logout, sizeof(Logout), 0);
						   //忽略判断用户名密码是否正确的过程
						   LogoutResult ret = { 1 };
						   send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
						   send(_cSock, (const char*)&ret, sizeof(Logout), 0);
		}
			break;
		default:
			header.cmd = CMD_ERROR;
			header.dataLength = 0;
			send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
			break;
		}
	}

	//8,关闭套接字closesocket
	closesocket(_sock);

	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

客户端代码:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGOUT,      //登出
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login
{
	char useName[32];
	char PassWord[32];
};

struct LoginResult
{
	int result;
};

struct Logout
{
	char userName[32];
};

struct LogoutResult
{
	int result;
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,用Socket API建立建立TCP客户端
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,连接服务器 connect
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
	if (SOCKET_ERROR == ret)
	{
		cout << "错误,建立Socket失败" << endl;
	}
	else
	{
		cout << "建立Socket成功" << endl;
	}

	while (true)
	{
		//3,输入请求
		char cmdBuf[128] = {};
		cin >> cmdBuf;
		//4,处理请求
		if (0 == strcmp(cmdBuf, "exit")){
			break;
		}
		else if (0 == strcmp(cmdBuf, "login")){
			Login login = { "lyd", "lydmm" };
			DataHeader dh = { sizeof(Login), CMD_LOGIN };
			//5,向服务器发送请求
			send(_sock, (const char *)&dh, sizeof(DataHeader), 0);
			send(_sock, (const char *)&login, sizeof(Login), 0);
			//接收服务器返回数据
			DataHeader retHeader = {};
			LoginResult loginRet = {};
			recv(_sock, (char *)&retHeader, sizeof(retHeader), 0);
			recv(_sock, (char *)&loginRet, sizeof(LoginResult), 0);
			cout << "LoginResult:" << loginRet.result << endl;
		}
		else if (0 == strcmp(cmdBuf, "logout")){
			Logout logout = { "lyb" };
			DataHeader dh = { sizeof(Login), CMD_LOGOUT };
			//5,向服务器发送请求
			send(_sock, (const char *)&dh, sizeof(DataHeader), 0);
			send(_sock, (const char *)&logout, sizeof(Logout), 0);
			//接收服务器返回数据
			DataHeader retHeader = {};
			LogoutResult logoutRet = {};
			recv(_sock, (char *)&retHeader, sizeof(retHeader), 0);
			recv(_sock, (char *)&logoutRet, sizeof(LogoutResult), 0);
			cout << "LogoutResult:" << logoutRet.result << endl;
		}
		else{
			cout << "不支持的命令,请重新输入" << endl;
		}
	}
	//7,关闭套接字closesocket
	closesocket(_sock);
	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

三,将多次发送包文数据升级为一次收发,主要将结构体进行整合,避免出错。(需要进行数据偏移)

上面的方式中,传递的结构体和描述数据的作用(包头和包体)是分开的,容易出错,所以此时将两者结合起来

运行截图:

服务器代码:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,bind 绑定用于接受客户端连接的网络接口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "错误,绑定网络端口失败" << endl;
	}
	else
	{
		cout << "绑定网络端口成功" << endl;
	}

	//3,listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "错误,监听网络端口失败" << endl;
	}
	else
	{
		cout << "监听网络端口成功" << endl;
	}

	//4,accept 等待客户端连接
	sockaddr_in clientAddr;
	int nAddrlen = sizeof(clientAddr);
	SOCKET _cSock = INVALID_SOCKET;

	_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
	if (INVALID_SOCKET == _cSock)
	{
		cout << "错误,接受到无效的客户端连接" << endl;
	}

	cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;

	while (true)
	{
		DataHeader header = {};
		int nLen = recv(_cSock, (char*)&header, sizeof(DataHeader), 0);
		//5,接受客户端的请求数据
		if (nLen <= 0)
		{
			cout << "客户端已经退出,任务结束" << endl;
			break;
		}

		switch (header.cmd)
		{
		case CMD_LOGIN:
		{
						  Login login = {};
						  //做数据偏移
						  recv(_cSock, (char*)&login+sizeof(DataHeader), sizeof(Login)-sizeof(DataHeader), 0);
						  cout << "收到命令:CMD_LOGIN,  数据长度:" << login.dataLength;
						  cout << "  UserName:" << login.userName<<"  PassWord:"<<login.PassWord<< endl;
						  //忽略判断用户名密码是否正确的过程
						  LoginResult ret;
						  send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
		}
			break;
		case CMD_LOGOUT:
		{
						   Logout logout = {};
						   recv(_cSock, (char*)&logout + sizeof(DataHeader), sizeof(Logout)-sizeof(DataHeader), 0);
						   cout << "收到命令:CMD_LOGIN,  数据长度:" << logout.dataLength;
						   cout << "  UserName:" << logout.userName <<endl;
						   //忽略判断用户名密码是否正确的过程
						   LogoutResult ret;
						   send(_cSock, (const char*)&ret, sizeof(Logout), 0);
		}
			break;
		default:
			header.cmd = CMD_ERROR;
			header.dataLength = 0;
			send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
			break;
		}
	}

	//8,关闭套接字closesocket
	closesocket(_sock);

	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

客户端代码:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,用Socket API建立建立TCP客户端
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,连接服务器 connect
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
	if (SOCKET_ERROR == ret)
	{
		cout << "错误,建立Socket失败" << endl;
	}
	else
	{
		cout << "建立Socket成功" << endl;
	}

	while (true)
	{
		//3,输入请求
		char cmdBuf[128] = {};
		cin >> cmdBuf;
		//4,处理请求
		if (0 == strcmp(cmdBuf, "exit")){
			break;
		}
		else if (0 == strcmp(cmdBuf, "login")){
			Login login;
			strcpy(login.userName, "lyd");
			strcpy(login.PassWord, "lydmima");
			
			//5,向服务器发送请求
			send(_sock, (const char *)&login, sizeof(Login), 0);

			//接收服务器返回数据
			LoginResult loginRet = {};
			recv(_sock, (char *)&loginRet, sizeof(LoginResult), 0);
			cout << "LoginResult:" << loginRet.result << endl;
		}
		else if (0 == strcmp(cmdBuf, "logout")){
			Logout logout;
			strcpy(logout.userName, "lyb");

			//5,向服务器发送请求
			send(_sock, (const char *)&logout, sizeof(Logout), 0);

			//接收服务器返回数据
			LogoutResult logoutRet = {};
			recv(_sock, (char *)&logoutRet, sizeof(LogoutResult), 0);
			cout << "LogoutResult:" << logoutRet.result << endl;
		}
		else{
			cout << "不支持的命令,请重新输入" << endl;
		}
	}
	//7,关闭套接字closesocket
	closesocket(_sock);
	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

四,添加一个接收缓冲区。

此处添加一个接收缓冲区的作用就是,当出现高并发时,如果发送数据过大时,可能无法全部接收,所以此时使用一个缓冲区来进行接收

客户端不变。

服务器:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,bind 绑定用于接受客户端连接的网络接口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "错误,绑定网络端口失败" << endl;
	}
	else
	{
		cout << "绑定网络端口成功" << endl;
	}

	//3,listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "错误,监听网络端口失败" << endl;
	}
	else
	{
		cout << "监听网络端口成功" << endl;
	}

	//4,accept 等待客户端连接
	sockaddr_in clientAddr;
	int nAddrlen = sizeof(clientAddr);
	SOCKET _cSock = INVALID_SOCKET;

	_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
	if (INVALID_SOCKET == _cSock)
	{
		cout << "错误,接受到无效的客户端连接" << endl;
	}

	cout << "新的客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;

	while (true)
	{
		//缓冲区
		char szRecv[1024] = {};
		//5,接受客户端的请求数据
		int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
		DataHeader *header = (DataHeader*)szRecv;
		if (nLen <= 0)
		{
			cout << "客户端已经退出,任务结束" << endl;
			break;
		}

		switch (header->cmd)
		{
		case CMD_LOGIN:
		{
						  //做数据偏移
						  recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength-sizeof(DataHeader), 0);
						  Login *login = (Login*)szRecv;
						  cout << "收到命令:CMD_LOGIN,  数据长度:" << login->dataLength;
						  cout << "  UserName:" << login->userName<<"  PassWord:"<<login->PassWord<< endl;
						  //忽略判断用户名密码是否正确的过程
						  LoginResult ret;
						  send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
		}
			break;
		case CMD_LOGOUT:
		{
						   recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength-sizeof(DataHeader), 0);
						   Login *logout = (Login*)szRecv;
						   cout << "收到命令:CMD_LOGIN,  数据长度:" << logout->dataLength;
						   cout << "  UserName:" << logout->userName <<endl;
						   //忽略判断用户名密码是否正确的过程
						   LogoutResult ret;
						   send(_cSock, (const char*)&ret, sizeof(Logout), 0);
		}
			break;
		default:
		{
				   DataHeader header = { 0, CMD_ERROR };
				   send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
		}
			break;
		}
	}

	//8,关闭套接字closesocket
	closesocket(_sock);

	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

五,将服务器端改为select模型,从而实现可以处理多客户端的目标。

将以前的模型变为select模型,从而可以实现高并发和跨平台

运行截图:

客户端不变。

服务器端:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

#include<vector>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

vector<SOCKET> g_clients;

int processor(SOCKET _cSock)
{
	//缓冲区
	char szRecv[1024] = {};
	//5,接受客户端的请求数据
	int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
	DataHeader *header = (DataHeader*)szRecv;
	if (nLen <= 0)
	{
		cout << "客户端已经退出,任务结束" << endl;
		return -1;
	}
	switch (header->cmd)
	{
	case CMD_LOGIN:
	{
					  //做数据偏移
					  recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
					  Login *login = (Login*)szRecv;
					  cout << "收到命令:CMD_LOGIN,  数据长度:" << login->dataLength;
					  cout << "  UserName:" << login->userName << "  PassWord:" << login->PassWord << endl;
					  //忽略判断用户名密码是否正确的过程
					  LoginResult ret;
					  send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
	}
		break;
	case CMD_LOGOUT:
	{
					   recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
					   Login *logout = (Login*)szRecv;
					   cout << "收到命令:CMD_LOGIN,  数据长度:" << logout->dataLength;
					   cout << "  UserName:" << logout->userName << endl;
					   //忽略判断用户名密码是否正确的过程
					   LogoutResult ret;
					   send(_cSock, (const char*)&ret, sizeof(Logout), 0);
	}
		break;
	default:
	{
			   DataHeader header = { 0, CMD_ERROR };
			   send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
	}
		break;
	}
}

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,bind 绑定用于接受客户端连接的网络接口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "错误,绑定网络端口失败" << endl;
	}
	else
	{
		cout << "绑定网络端口成功" << endl;
	}

	//3,listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "错误,监听网络端口失败" << endl;
	}
	else
	{
		cout << "监听网络端口成功" << endl;
	}

	while (true)
	{
		//伯克利套接字
		fd_set fdRead;
		fd_set fdWrite;
		fd_set fdExp;

		FD_ZERO(&fdRead);
		FD_ZERO(&fdWrite);
		FD_ZERO(&fdExp);

		FD_SET(_sock, &fdRead);
		FD_SET(_sock, &fdWrite);
		FD_SET(_sock, &fdExp);

		for (int n = (int)g_clients.size() - 1; n >= 0; n--)
		{
			FD_SET(g_clients[n],&fdRead);
		}

		//nfds 是一个整数值,是指fd_set集合中所有描述符(socket)的范围,而不是数量
		//即是所有文件描述符最大值+1,在Windows中这个参数可以写0
		int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, NULL);
		if (ret < 0)
		{
			cout << "select任务结束" << endl;
			break;
		}

		if (FD_ISSET(_sock, &fdRead))
		{
			FD_CLR(_sock, &fdRead);

			//4,accept 等待客户端连接
			sockaddr_in clientAddr = { };
			int nAddrlen = sizeof(clientAddr);
			SOCKET _cSock = INVALID_SOCKET;

			_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
			if (INVALID_SOCKET == _cSock)
			{
				cout << "错误,接受到无效的客户端连接" << endl;
			}

			g_clients.push_back(_cSock);
			cout << "新的客户端加入:"<<(int)_cSock<<"        "<< inet_ntoa(clientAddr.sin_addr) << endl;
		}

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

	for (size_t n = g_clients.size() - 1; n >= 0; n--)
	{
		closesocket(g_clients[n]);
	}

	//8,关闭套接字closesocket
	closesocket(_sock);

	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

六,服务器升级为select处理多客户端模型,并且可以在某个客户端加入时,提醒已经连接的客户端。

添加一个新的功能,当一个客户端连接客户端时,服务器向其发送已经连接的客户端,模拟聊天系统中某个人上线时的提醒功能。

程序运行截图:

如果这个地方仍然使用cin传输的话,cin函数会造成阻塞,结果就是不能及时提醒已经连接的客户端有新的客户端加入

所以这个地方客户端和服务器是自动收发消息,所以会一直发送消息,从而能够模拟出接收新的客户端连接的消息。

服务器端:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

#include<vector>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,      //新的用户加入
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock = 0;
	}
	int sock;
};

vector<SOCKET> g_clients;

int processor(SOCKET _cSock)
{
	//缓冲区
	char szRecv[1024] = {};
	//5,接受客户端的请求数据
	int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
	DataHeader *header = (DataHeader*)szRecv;
	if (nLen <= 0)
	{
		cout << "客户端已经退出,任务结束" << endl;
		return -1;
	}
	switch (header->cmd)
	{
	case CMD_LOGIN:
	{
					  //做数据偏移
					  recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
					  Login *login = (Login*)szRecv;
					  cout << "收到命令:CMD_LOGIN,  数据长度:" << login->dataLength;
					  cout << "  UserName:" << login->userName << "  PassWord:" << login->PassWord << endl;
					  //忽略判断用户名密码是否正确的过程
					  LoginResult ret;
					  send(_cSock, (const char*)&ret, sizeof(LoginResult), 0);
	}
		break;
	case CMD_LOGOUT:
	{
					   recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
					   Login *logout = (Login*)szRecv;
					   cout << "收到命令:CMD_LOGIN,  数据长度:" << logout->dataLength;
					   cout << "  UserName:" << logout->userName << endl;
					   //忽略判断用户名密码是否正确的过程
					   LogoutResult ret;
					   send(_cSock, (const char*)&ret, sizeof(Logout), 0);
	}
		break;
	default:
	{
			   DataHeader header = { 0, CMD_ERROR };
			   send(_cSock, (const char*)&header, sizeof(DataHeader), 0);
	}
		break;
	}
}

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,建立一个socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,bind 绑定用于接受客户端连接的网络接口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		cout << "错误,绑定网络端口失败" << endl;
	}
	else
	{
		cout << "绑定网络端口成功" << endl;
	}

	//3,listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		cout << "错误,监听网络端口失败" << endl;
	}
	else
	{
		cout << "监听网络端口成功" << endl;
	}

	while (true)
	{
		//伯克利套接字
		fd_set fdRead;
		fd_set fdWrite;
		fd_set fdExp;

		FD_ZERO(&fdRead);
		FD_ZERO(&fdWrite);
		FD_ZERO(&fdExp);

		FD_SET(_sock, &fdRead);
		FD_SET(_sock, &fdWrite);
		FD_SET(_sock, &fdExp);

		for (int n = (int)g_clients.size() - 1; n >= 0; n--)
		{
			FD_SET(g_clients[n], &fdRead);
		}

		//nfds 是一个整数值,是指fd_set集合中所有描述符(socket)的范围,而不是数量
		//即是所有文件描述符最大值+1,在Windows中这个参数可以写0

		//添加非阻塞
		//timeval t = { 1, 0 };

		int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, NULL);

		if (ret < 0)
		{
			cout << "select任务结束" << endl;
			break;
		}

		if (FD_ISSET(_sock, &fdRead))
		{
			FD_CLR(_sock, &fdRead);

			//4,accept 等待客户端连接
			sockaddr_in clientAddr = {};
			int nAddrlen = sizeof(clientAddr);
			SOCKET _cSock = INVALID_SOCKET;

			_cSock = accept(_sock, (sockaddr *)&clientAddr, &nAddrlen);
			if (INVALID_SOCKET == _cSock)
			{
				cout << "错误,接受到无效的客户端连接" << endl;
			}

			for (int n = (int)g_clients.size() - 1; n >= 0; n--)
			{
				NewUserJoin userjoin;
				send(g_clients[n], (const char*)&userjoin, sizeof(NewUserJoin), 0);
			}

			g_clients.push_back(_cSock);
			cout << "新的客户端加入:" << (int)_cSock << "        " << inet_ntoa(clientAddr.sin_addr) << endl;
		}

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

	for (size_t n = g_clients.size() - 1; n >= 0; n--)
	{
		closesocket(g_clients[n]);
	}

	//8,关闭套接字closesocket
	closesocket(_sock);

	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	system("pause");
	return 0;
}

客户端:

#define WIN32_LEAN_AND_MEAN
#include<iostream>
#include<windows.h>
#include<Winsock2.h>

using namespace std;

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR,       //错误
};

struct DataHeader
{
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock = 0;
	}
	int sock;
};


int processor(SOCKET _cSock)
{
	//缓冲区
	char szRecv[1024] = {};
	//5,接受客户端的请求数据
	int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
	DataHeader *header = (DataHeader*)szRecv;
	if (nLen <= 0)
	{
		cout << "与服务器断开连接,任务结束" << endl;
		return -1;
	}
	switch (header->cmd)
	{
	case CMD_LOGIN_RESULT:
	{
							 recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
							 LoginResult *login = (LoginResult*)szRecv;
							 cout << "收到服务端消息:CMD_LOGIN_RESULT  " << _cSock << "  数据长度:" << login->dataLength << endl;
	}
		break;
	case CMD_LOGOUT_RESULT:
	{
							  recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
							  LogoutResult *logout = (LogoutResult*)szRecv;
							  cout << "收到服务端消息:CMD_LOGOUT_RESULT  " << _cSock << "  数据长度:" << logout->dataLength << endl;
	}
		break;
	case CMD_NEW_USER_JOIN:
	{
							  recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
							  NewUserJoin *userJoin = (NewUserJoin*)szRecv;
							  cout << "收到服务端消息:CMD_NEW_USER_JOIN  " << _cSock << "  数据长度:" << userJoin->dataLength << endl;
	}
		break;
	}
}

int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//---------------------------------
	//1,用Socket API建立建立TCP客户端
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	//2,连接服务器 connect
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int ret = connect(_sock, (sockaddr*)&_sin, sizeof(_sin));
	if (SOCKET_ERROR == ret)
	{
		cout << "错误,建立Socket失败" << endl;
	}
	else
	{
		cout << "建立Socket成功" << endl;
	}

	while (true)
	{
		fd_set fdReads;
		FD_ZERO(&fdReads);
		FD_SET(_sock, &fdReads);

		//添加非阻塞
		//timeval t = { 1, 0 };

		int ret = select(_sock, &fdReads, 0, 0, NULL);
		if (ret < 0)
		{
			cout << "select 任务结束1" << endl;
			break;
		}
		if (FD_ISSET(_sock, &fdReads))
		{
			FD_CLR(_sock, &fdReads);

			if (-1 == processor(_sock))
			{
				cout << "select 任务结束2" << endl;
				break;
			}
		}

		Login login;
		strcpy(login.userName, "lyd");
		strcpy(login.PassWord, "lydmima");

		//5,向服务器发送请求
		send(_sock, (const char *)&login, sizeof(Login), 0);
		Sleep(1000);
	}

	//7,关闭套接字closesocket
	closesocket(_sock);
	//-----------------------------------
	//清除Windows socket环境
	WSACleanup();
	cout << "已退出" << endl;
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46423166/article/details/110294956