C++ High Concurrency Network Architecture and Implementation-Part Three

table of Contents

First, realize simple structured transmission of information, but cannot distinguish whether it is a structure.

Second, realize the defined transmission through the format of the network data message.

Third, upgrade the packet data sent multiple times to one send and receive, mainly to integrate the structure to avoid errors. (Data offset is required)

Fourth, add a receiving buffer.

Fifth, change the server side to the select model to achieve the goal of processing multiple clients.

Sixth, the server is upgraded to a select processing multi-client model, and it can alert connected clients when a client joins.


Today’s task is to be achieved: the server is upgraded to a select processing multi-client model, and it can alert connected clients when a client joins.

  1. Achieve simple structured transmission of information, but cannot distinguish whether it is a structure.
  2. Realize the definition transmission through the network data message format.
  3. Upgrade the multiple sending and receiving of packet data to one sending and receiving, and mainly integrate the structure to avoid errors. (Data offset is required)
  4. Add a receiving buffer.
  5. Change the server side to the select model to achieve the goal of being able to handle multiple clients.
  6. The server is upgraded to a select processing multi-client model, and can alert connected clients when a client joins.

First, realize simple structured transmission of information, but cannot distinguish whether it is a structure.

Set the information to structure mode and transfer it.

There is a problem, because the client receives the message sent back by the server in the structure mode, so if the returned data is not in the structure mode, an error will occur.

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

Procedure result:

server:

#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;
}

Client:

#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;
}

Second, realize the defined transmission through the format of the network data message.

From the first one, it can be concluded that if only the structure is used for transmission, an error will be generated, so the network data message format needs to be used

The message has two parts, the header and the body, which are the basic units of the network message. The
header: describes the size of the message and the role of the data. The
body: data

So define a union at this time to describe the role of data
enum CMD
{     CMD_LOGIN, //login     CMD_LOGOUT, //logout     CMD_ERROR, //error };



Run screenshot:

Server-side code:

#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;
}

Client code:

#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;
}

Third, upgrade the packet data sent multiple times to one send and receive, mainly to integrate the structure to avoid errors. (Data offset is required)

In the above method, the function of the transferred structure and the description data (header and body) are separate, which is prone to errors, so combine the two at this time

Run screenshot:

Server code:

#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;
}

Client code:

#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;
}

Fourth, add a receiving buffer.

The function of adding a receiving buffer here is that when there is high concurrency, if the sent data is too large, it may not be able to receive all, so at this time, use a buffer to receive

The client remains unchanged.

server:

#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;
}

Fifth, change the server side to the select model to achieve the goal of processing multiple clients.

Turn the previous model into a select model, which can achieve high concurrency and cross-platform

Run screenshot:

The client remains unchanged.

Service-Terminal:

#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;
}

Sixth, the server is upgraded to a select processing multi-client model, and it can alert connected clients when a client joins.

Add a new function, when a client connects to the client, the server sends the connected client to it, simulating the reminder function when someone goes online in the chat system.

Screenshot of program running:

If the cin transmission is still used in this place, the cin function will cause blocking, and the result is that the connected client cannot be reminded in time that a new client has joined

So in this place, the client and the server automatically send and receive messages, so they will always send messages, which can simulate the reception of new client connections.

Service-Terminal:

#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;
}

Client:

#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;
}

 

Guess you like

Origin blog.csdn.net/qq_46423166/article/details/110294956