双端联调和连接测试

客户端网络编程

客户控制端的网络编程和服务被控端的区别在于,客户端没有listen和accept,取而代之的是connect。还有就是服务端通常可以不指定固定的IP地址连接就是可以来者不拒。而客户端必须指定要连接的客户端的IP地址端口。

然后就是要明确,建立的连接是要短连接还是长连接。

因为在我实现过程中发现内存泄漏情况。即我在堆区开辟了服务端的接收缓冲区和客户端的接收缓冲区。但是我又想这个一直是全局有效的。我采用了两种方法的尝试解决,一个是智能指针管理,还有一个是交给STL的vector管理,这两个的安全性都封装的非常好。

服务客户双端联调的技巧:把每个基础阶段步骤都打出日志,先从日志宏观入手,判断哪个端出问题,以及哪个部分。然后断点深入函数寻找异常,查看对应返回值的正确性。

ClientSocket.h

#pragma once
#include <mutex>
#include<vector>
#include"Packet.h"
#define BUFFER_SIZE 4096
class CClientSocket
{
private:
	std::vector<char> m_buffer;
	SOCKET m_skt;//当前端的socket
	CPacket packet;
	bool sign =	 false;
	//static CServerSocket* m_instance;
	static std::shared_ptr<CClientSocket> m_instance; // 唯一实例化的对象且加上了智能指针的管理
	static std::mutex mtx;// 静态的互斥锁,用于确保多线程环境下的安全性 C++11后可以不需要类外初始化mutex
public:
	//初始化套接字环境(Windows编程特有)
	BOOL InitSktEnv();

	//初始化套接字
	BOOL InitSkt(const std::string& strIPAddress, short port);


	//TODO:发送数据的操作Send
	bool Send(const char* pData, int nSize);
	bool Send(CPacket& packet);
	//关闭客户端
	//void CloseClient();

	static CClientSocket* GetInstance();

	//处理命令
	int DealCommand();
	//
	CPacket GetPacket() { return packet; }
	//
	bool GetFilePath(std::string& strPath);

	~CClientSocket() {
		closesocket(m_skt);
		WSACleanup();
	}
private:
	CClientSocket() {
		//m_client = INVALID_SOCKET;// -1
		if (InitSktEnv() == FALSE) {
			//Windows API 函数用于创建一个消息对话框
			MessageBox(NULL, //无父窗口,即该消息对话框为顶层窗口
				_T("无法初始化套接字环境,请检查网路设置!"), //要显示的文本
				_T("初始化错误!"), //消息对话框的标题
				MB_OK | MB_ICONERROR //消息框的风格,表示一个包含 OK 按钮的消息框,并显示错误图标。
			); //_T 是一个宏,用于支持在编译时实现 Unicode 和 ANSI 字符串切换。使代码更具可移植性,可以在不同的编译设置下工作。
			exit(0);//程序正常退出
		}
		m_buffer.resize(BUFFER_SIZE);
		//创建套接字
		m_skt = socket(PF_INET, SOCK_STREAM, 0);
		//TODO:m_skt创建失败的日志
	}
	CClientSocket(const CClientSocket& ss) {
		m_skt = ss.m_skt;
		//m_client = ss.m_client;
	}
	CClientSocket& operator=(const CClientSocket& ss) {}
};

ClientSocket.cpp

#include "pch.h"
#include "ClientSocket.h"
#pragma warning (disable:4996)
//全局区
std::string GetErrInfo(int wsaErrCode) {
	std::string ret;
	LPVOID lpMsgBuf = NULL;
	FormatMessage(
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
		NULL,
		wsaErrCode,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		NULL
	);
	ret = (char*)lpMsgBuf;
	LocalFree(lpMsgBuf);
	return ret;
}
//

//记得类外初始化静态成员变量
std::shared_ptr<CClientSocket> CClientSocket::m_instance = nullptr;
std::mutex CClientSocket::mtx;

CClientSocket* pclient = CClientSocket::GetInstance();//这样就可以外部获取一个全局唯一对象的指针了,且总是在全局区实例化好了对象

BOOL CClientSocket::InitSktEnv()
{
	//Windows下Socket网络编程的API简称WSA
	WSADATA data;//存储Winsock的版本信息
	//WSAStartup初始化Winsock库
	if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
		TRACE(_T("Init Winsock Library Failed\r\n"));
		return FALSE;
	}
	TRACE(_T("Init Winsock Library Succeeded\r\n"));
	return TRUE;
}

BOOL CClientSocket::InitSkt(const std::string& strIPAddress,short port)
{
	if (m_skt == -1) {
		TRACE(_T("m_skt never existed\r\n"));
		return false;
	}
	sockaddr_in serv_adr;// IPv4 地址的结构体
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family = AF_INET; //地址族设置(AF_INET 表示 IPv4)
	serv_adr.sin_addr.s_addr = inet_addr(strIPAddress.c_str());//服务端可以通过任何网络接口连接,而不用担心特定的网络接口或 IP 地址
	serv_adr.sin_port = htons(port);//设置连接的端口号
	if (serv_adr.sin_addr.s_addr == INADDR_NONE) {
		AfxMessageBox("指定的IP地址不存在");
		return false;
	}
	int ret = 0;
	if (sign == false) {
		ret = connect(m_skt, (sockaddr*)&serv_adr, sizeof(serv_adr));
		sign = true;
	}
	if (ret == -1) {
		AfxMessageBox("connect failed");
		TRACE("connect failed: %d, %s\r\n",WSAGetLastError(), GetErrInfo(WSAGetLastError()).c_str());
		sign = false;
		return false;
	}
	//一切正常
	return true;
}

bool CClientSocket::Send(const char* pData, int nSize)
{
	if (m_skt == -1) return false;
	int ret = send(m_skt, pData, nSize, 0);
	if (ret > 0) return true;
	TRACE(_T("Client Send Failed"));
	return false;
}

bool CClientSocket::Send(CPacket& packet)
{
	if (m_skt == -1) return false;
	//发送一整个包过去,然后大小为 2B包头 + 4BPacketLength + PacketLength值
	return send(m_skt, packet.EntirePacket(), packet.PacketLength + 6, 0) > 0;
}


CClientSocket* CClientSocket::GetInstance()
{
	if (m_instance == nullptr) {
		std::lock_guard<std::mutex> lock(mtx);
		if (m_instance == nullptr) {
			m_instance.reset(new CClientSocket());

		}
	}
	return m_instance.get();
}
#define BUFFER_SIZE 4096
int CClientSocket::DealCommand()
{
	TRACE(_T("Processing commands\r\n"));
	if (m_skt == -1) {
		TRACE(_T("Client socket = -1 is disconnected\r\n"));
		return -1;
	}
	char* buffer = m_buffer.data();//让容器去管理内存泄漏
	if (buffer == NULL) {
		TRACE("no heap memory\r\n");
		return -2;
	}
	memset(buffer, 0, BUFFER_SIZE);
	size_t nowIndex = 0;//实际接收缓冲区数据的右边界指针表示接收缓冲区的数据大小
	while (1) {
		//***
		size_t recvLength = recv(m_skt, buffer + nowIndex, BUFFER_SIZE - nowIndex, 0);//获得接收缓冲区的增量值
		if (recvLength <= 0) {
			return -1;
		}
		nowIndex += recvLength;	//更新当前接收缓冲区大小
		recvLength = nowIndex;//此时的该变量已然没用,就充当当前接收缓冲区大小,保护nowIndex值
		packet = CPacket((BYTE*)buffer, recvLength);//解析包后更新recvLength为接收一个数据包具体接收了接收缓冲区的多少
		if (recvLength > 0) {
			memmove(buffer, buffer + recvLength, BUFFER_SIZE - recvLength);//把已经被解析成包的数据覆盖掉,后面往前推。		
			nowIndex -= recvLength;//更新右边界
			return packet.PacketCommand;
		}
	}
	return -1;
}

bool CClientSocket::GetFilePath(std::string& strPath)
{
	if (packet.PacketCommand >= 7 && packet.PacketCommand <= 9)
	{
		strPath = packet.PacketData;  return true;
	}
	return false;
}

RemoteControl_ClientDlg.cpp

void CRemoteControlClientDlg::OnBnClickedBtnTest()//基于MFC实现的测试连接按钮
{	 
	// TODO: 在此添加控件通知处理程序代码
	CClientSocket* pClient = CClientSocket::GetInstance();
	bool ret = pClient->InitSkt("127.0.0.1", 9527);
	if (!ret) {
		AfxMessageBox("Network Init Failed!"); 
	}
	CPacket pack(520, NULL, 0);
	ret = pClient->Send(pack);
	TRACE("Send ret = %d\r\n", ret);
	int cmd = pClient->DealCommand();
	TRACE("ack:%d\r\n", cmd);
}
int CTest::DealCommand()//在服务端接收到520命令则会回发1314命令的数据报来测试连接
{
	CPacket pack(1314, NULL, 0);
	CServerSocket::GetInstance()->Send(pack);
	return 520;
}

在这里插入图片描述

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值