C++ Socket网络编程 1.5版本 使用C++面向对象编程对客户端进行改进

当客户端连接多个服务端,或者服务端连接其它服务端,或者是交叉的应用时,使用C语言的方式编写代码是不合适的。
C++面向对象编程为解决这类问题提供的方案。通过对客户端的业务逻辑进行封装,使得一个程序中建立不同的客户端对象来进行复杂的业务,并且使用类去封装服务端和客户端功能,更加简单易用。

本节对客户端进行封装,一个客户端程序能够支持连接多个服务器。(在实际的开发中,这种情况是必然存在的。有可能一个客户端需要连接登陆数据库、业务处理服务器等);

新建一个EasyTcpClient.hpp文件,对类的定义和实现都写在同一个文件中。

//保证此文件的代码只被编译一次
#ifndef EasyTcpClient_hpp_
#define EasyTcpClient_hpp_
#endif
#ifndef EasyTcpClient_hpp_
#define EasyTcpClient_hpp_
#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#define _WINSOCK_DEPRECATED_NO_WARNINGS
	#define _CRT_SECURE_NO_WARNINGS
	#include<Windows.h>
	#include<WinSock2.h>
	#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库
#else
	#include<unistd.h>
	#include<arpa/inet.h>
	#include<string.h>
	#define SOCKET int
	#define INVALID_SOCKET (SOCKET)(~0)
	#define SOCKET_ERROR		   (-1)
#endif
#include<iostream>
#include"MessageHeadr.hpp"
using namespace std;
class EasyTcpClient		//服务端的类,对其进行封装,使它的初始化、连接服务器、查询select请求、收发数据封装位类的成员函数。
{
public:
	SOCKET _sock;
public:
	EasyTcpClient()
	{
		_sock = INVALID_SOCKET;
	}
	//虚析构函数 直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。
	//具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。
	//假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,
	//因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。
	//所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
	virtual ~EasyTcpClient()  
	{
		closeSocket();
	}
	//初始化socket
	int initSocket()
	{
#ifdef _WIN32
		//启动Windows sock 2.x环境
		WORD versionCode = MAKEWORD(2, 2);	//创建一个版本号 
		WSADATA data;
		WSAStartup(versionCode, &data);  //启动Socket网络API的函数
#endif
		//1. 建立一个Socket
		if (_sock != INVALID_SOCKET)
		{
			cout << "<socket=>"<<_sock<<" 关闭了已有连接并重新建立连接" << endl;
			closeSocket();
		}
		_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //用于网络链接的ipv4的socket TCP连接
		if (INVALID_SOCKET == _sock)	//SIocket 每一步都可以判断是否成功建立
		{
			cout << "ERROR: SOCKET 建立失败" << endl;
		}
		else
		{
			cout << "SUCCESS: SOCKET 建立成功" << endl;
		}
		return 0;
	}
	//连接服务器
	int ConnectServer(char* ip,unsigned short port)
	{
		if (_sock == INVALID_SOCKET)
		{
			initSocket();
		}
		//2. 连接服务器 connect
		sockaddr_in _sin = {};
		_sin.sin_family = AF_INET;
		_sin.sin_port = htons(port); //host to net short
		_sin.sin_addr.S_un.S_addr = inet_addr(ip);
		if (SOCKET_ERROR == connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in)))	//SIocket 每一步都可以判断是否成功建立
		{
			cout << "ERROR: SOCKET 连接失败" << endl;
			getchar();
			return SOCKET_ERROR;
		}
		else
		{
			cout << "SUCCESS: SOCKET 连接成功" << endl;
			return 0;
		}
	}
	//关闭socket
	void closeSocket()
	{
		if (_sock == INVALID_SOCKET)
			return;
		//7. 关闭 socket
#ifdef _WIN32
		//清理win sock环境
		closesocket(_sock);
		// 清除Windows socket环境
		WSACleanup();
#else
		close(_sock);
#endif
		cout << "任务结束" << endl;
		getchar();
		_sock = INVALID_SOCKET;
	}

	//查询select网络消息
	bool onRun()
	{
		if (isRun())
		{
			fd_set fdReads;
			FD_ZERO(&fdReads);
			FD_SET(_sock, &fdReads);
			timeval t = { 1,0 };
			int ret = select(_sock + 1, &fdReads, NULL, NULL, &t);
			if (ret < 0)
			{
				cout << "<socket = " << _sock << ">" << " select任务结束" << endl;
				return false;
			}
			if (FD_ISSET(_sock, &fdReads))	//如果_sock在fdRead里面,表明有接收数据等待处理
			{
				FD_CLR(_sock, &fdReads);

				if (RecvData() == -1)
				{
					cout << "<socket = " << _sock << ">" << " select任务结束2" << endl;
					return false;
				}
			}
			return true;
		}
		return false;
	}
	bool isRun()
	{
		return _sock != INVALID_SOCKET;
	}
	//接收数据  处理粘包、拆分包
	int RecvData()
	{
		char *szRecv = new char[1024];
		//5 首先接收数据包头
		int nlen = recv(_sock, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
		if (nlen <= 0)
		{
			//客户端退出
			cout << "客户端:Socket = " << _sock << " 与服务器断开连接,任务结束" << endl;
			return -1;
		}
		DataHeader* header = (DataHeader*)szRecv;
		recv(_sock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		OnNetMsg(header);
		return 0;
	}
	//响应网络消息
	void OnNetMsg(DataHeader* header)
	{
		switch (header->cmd)
		{
			case CMD_NEWUSERJOIN:
			{
				NewUserJoin* _userJoin = (NewUserJoin*)header;
				cout << "收到服务器消息: CMD_NEWUSERJOIN: SocketId = " << _userJoin->sockId << " 数据长度:" << header->dataLength << endl;
			}break;
			case CMD_LOGIN_RESULT:
			{
				LoginResult* _lgRes = (LoginResult*)header;
				cout << "收到服务器消息: CMD_LOGIN_RESULT: 登陆状态" << _lgRes->result << endl;
			}break;
			case CMD_LOGOUT_RESULT:
			{
				LogoutResult* _lgRes = (LogoutResult*)header;
				cout << "收到服务器消息: CMD_LOGIN_RESULT: 登出状态" << _lgRes->result << endl;
			}break;
			default:
			{
				header->cmd = CMD_ERROR;
				header->dataLength = 0;
				send(_sock, (char*)&header, sizeof(DataHeader), 0);
			}
			break;
		}
	}
	int SendData(DataHeader* header)
	{
		if (isRun() && header)
		{
			//cout << "发送数据给服务端" << endl;
			int ret = send(_sock, (const char*)header, header->dataLength, 0);
			return ret;
		}
		return SOCKET_ERROR;
	}
private:

};
#endif

主函数中,可以用client类创建多个对象,传入不同的ip地址和端口号连接多个服务器处理不同的业务逻辑。

//线程函数,输入命令
void cmdThread(EasyTcpClient* client)
{
	while (true)
	{
		// 3 输入请求命令
		char cmdBuf[128] = {};
		cout << "输入命令: ";
		cin >> cmdBuf;
		// 4 处理请求
		if (strcmp(cmdBuf, "exit") == 0)
		{
			cout << "退出cmdThread线程" << endl;
			client->closeSocket();
			return;
		}
		else if (0 == strcmp(cmdBuf, "login"))
		{
			Login _login;
			strcpy(_login.userName, "Evila");
			strcpy(_login.Password, "Evila_Password");
			// 5 向服务器发送请求命令
			int ret = client->SendData(&_login);
			//send(client->_sock, (const char*)&header, header->dataLength, 0);
		}
		else if (0 == strcmp(cmdBuf, "logout"))
		{
			Logout _logout;
			strcpy(_logout.userName, "Evila");
			//5 向服务器发送请求命令
			client->SendData(&_logout);
		}
		else
		{
			cout << "不受支持的命令" << endl;
		}
	}
	return;
}
int main()
{
	EasyTcpClient client;
	client.initSocket();
	client.ConnectServer("127.0.0.1", 4567);
	//启动UI线程
	std::thread t1(cmdThread, &client);
	t1.detach();
	while (client.isRun())
	{
		client.onRun();
	}
	client.closeSocket();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/La745739773/article/details/89110207