3-IM聊天系统-Mediator

3-IM聊天系统-Mediator

准备工作

创建筛选器

梳理:

  1. 父类
    • 网络类
    • 实现功能
      1. 打开网络-初始化网络(initNet)
      2. 关闭网络-关闭(close)

INetMediator.h

#pragma once
 class INetMediator {
 public:
     INetMediator(){}
     virtual ~INetMediator(){}
 
     //打开网络
     //和初始化一样,是调用initNet函数
     virtual bool openNet() = 0;
 
     //关闭网络
     //调用unInitNet函数
     virtual void closeNet() = 0;
 
     //发送数据
     //data:发送数据的内容,len:发送的数据长度,to:发送的地址,发给谁,UDP是ip,TCP是socket
     virtual bool sendData(char* data, int len, unsigned long to) = 0;
 
     //转发数据(把Net层接收到的数据传给核心处理类)
     //1.什么数据
     //2.数据从哪儿来 UDP-ip TCP-socket
     virtual void transmitData(char* data, int len, unsigned long from) = 0;
 
 };

框架实现类似INet类。测试代码详见3-01笔记。

实现

主函数

通过中介者初始化网络。

UdpMediator

  1. openNet()函数:

通过对象调用Udp中initNet函数。

  • 创建Udp对象-在INetMediator.h头文件中添加父类指针成员

(在Tcp中也需要使用该对象)

加入头文件

使用相对路径-"./"返回上一级寻找。(默认从当前文件夹开始寻找)

指针初始化

  • 子类的构造函数中创建对象
//打开网络
 bool UdpMediator::openNet() {
     //构造函数创建对象,实现调用
     m_pNet = new Udp(this);
     return true;
 }

创建Udp的对象,调用Udp的函数。

添加Udp的文件

  • 通过指针调用函数
//关闭网络
 void UdpMediator::closeNet() {
     //回收资源
     m_pNet->unInitNet();
 }

相比原本直接调用,使用中介者类,多一层调用更安全。

  • 析构函数回收指针
//关闭网络
 void UdpMediator::closeNet() {
     //回收资源
     if (m_pNet) {
         m_pNet->unInitNet();
         delete m_pNet;
         m_pNet = nullptr;
     }
 }
  • 传输数据

//转发数据(把net层的数据传给核心处理类)//net层的数据由Udp使用transmit函数传给Mediator

- <font style="color:black;">Udp 使用指针调用Mediator的transmit函数把接收的的数据传给Mediator。</font>
- <font style="color:black;">同理,不止UDP,Tcp也需要使用父类指针创建对象</font>

在INet类中添加指针

加入头文件

创建指针

初始化

- <font style="color:black;"> INet() :m_isRunning(true) , m_pMediator(nullptr) {}</font>
  • 问题:

INetMediator.h包含INet.h文件

INet.h文件包含INetMediator.h文件

头文件相互包含。编译在预处理的阶段会导致重复拷贝。

解决:

申明先有INet类,可以直接使用,先不编译INet类。

但是INet类需要在整个编译过程中被使用。

  • 问题:
  • 栈溢出

Udp::Udp(INetMediator* p) :m_sock(INVALID_SOCKET),m_handle(nullptr),m_isRunning(true) {
     m_pMediator = p;
 }
bool UdpMediator::openNet() {
     //构造函数创建对象,实现调用
     m_pNet = new Udp(this);
     return m_pNet->initNet();
 }

原因:

主函数中创建了udpMediator对象,udpMediator对象走析构函数到udpMediator,创建udp对象。udp走析构函数,创建udpMediator对象。走udpMediator析构函数创建udp对象。相互调用死循环,stack溢出。

解决:

将主函数创建的对象传入udpMediator构造中创建的udp对象。

加入有参构造。把对象传入。

在udpMediator析构,创建Udp对象的时候传入参数(this-当前的udpMediater)。

代码

主函数测试

#include<Windows.h>
 #include<iostream>
 #include"Mediator/TcpClientMediator.h"
 #include"Mediator/TcpServerMediator.h"
 #include"Mediator/UdpMediator.h"
 
 using namespace std;
 
 int main() {
     INetMediator* p1 = new UdpMediator;
     //初始化UDP网络
     if (!p1->openNet()) {
         cout << "打开UDP网络失败" << endl;
         delete p1;
         return 1;
     }
     //使用直接广播发送测试消息
     char s[] = "this is a test";
     //p1->sendData(s, sizeof(s), inet_addr("192.168.134.1"));
 
     //打开服务端网络
     INetMediator* p3 = new TcpServerMediator;
     if (!p3->openNet()) {
         cout << "打开tcp服务端网络失败" << endl;
         delete p3;
         return 1;
     }
     //打开客户端网络
     INetMediator* p2 = new TcpClientMediator;
     if (!p2->openNet()) {
         cout << "打开tcp网络失败" << endl;
         delete p2;
         return 1;
     }
     //客户端给服务端发一个消息
     
 
     for (int i = 0; i < 20; i++) {
         p2->sendData(s, sizeof(s), 0);
     }
     while (true) {
         Sleep(50000);
         cout << "imserver is running" << endl;
     }
 
     delete p1;
     delete p2;
     delete p3;
     
     //INetMediator* p11 = new TcpClientMediator;
     //INetMediator* p12 = new TcpServerMediator;
     //INetMediator* p13 = new UdpMediator;
     //delete p11;
     //delete p12;
     //delete p13;
     
     
     return 0;
 }
 
 
 //IM--聊天系统
 //功能
 /*1.注册
 * 2.登陆账号
 * 3.添加好友
 * 4.聊天窗口界面
 * 5.主界面
 * 6.下线 -由服务端维护socket通信状态
 * 7.上线
 */
 
 /*面向对象编程,分析系统框架
 * 
 * 客户端QT
 * 核心处理类(处理收到的数据,组织要发送的数据)-注册信息,聊天信息,在线状态
 * UI界面
 * 网络类(收发数据,初始化网络,关闭网络)
 
 
 *服务端
 * 数据库(好友关系,账号数据,信息) 
 * 核心处理类(处理收到的数据,组织要发送的数据)
 * 中介者类
 //不做数据处理,只做转发
 //代码量不大的时候不使用中介者类也可以,添加是为了方便以后的功能扩展
 * 网络类(收发数据,初始化网络,关闭网络) UDP-TCP协议
 */
 
 //网络类
 //封装继承多态
 //父类:定义接口--纯虚函数
 //子类:实现函数:UDP子类,TCPClient子类,TCPServer子类
 
 //中介者:架构和网络类一样
 //父类:
 //子类:
 
 
 //数据结构STL:
 //vector:数组,数据连续存储,知道下标查找快,不知道下标查找慢,下标是连续的。增加和删除数据不方便
 //一串连续的数字
 //list:数据不连续存储,查找效率低,插入删除方便,
 //经常插入删除
 
 //stack:数据先进后出
 //queue:数据先进先出
 //对数据顺序有要求
 
 //map:key-value,自动排序,查找快
 //一一对应的无序数据
 //set:key和value相等,查找快
 //没有对应关系的无序数据

测试结果:

标准结果:

Udp

UdpMediator.h
#pragma once
#include"INetMediator.h"
class Udp;
class UdpMediator :public INetMediator {
public:
	UdpMediator();
	~UdpMediator();

	//打开网络
	bool openNet();

	//关闭网络
	void closeNet();

	//发送数据
	bool sendData(char* data, int len, unsigned long to);

	//接收数据
	void transmitData(char* data, int len, unsigned long from);

};
UdpMediator.cpp
#include"INetMediator.h"
#include"UdpMediator.h"
#include"../Net/Udp.h"
UdpMediator::UdpMediator() {}
UdpMediator::~UdpMediator() {}

//打开网络
bool UdpMediator::openNet() {
	//构造函数创建对象,实现调用
	m_pNet = new Udp(this);
	return m_pNet->initNet();
}

//关闭网络
void UdpMediator::closeNet() {
	//回收资源
	if (m_pNet) {
		m_pNet->unInitNet();
		delete m_pNet;
		m_pNet = nullptr;
	}
}

//发送数据
bool UdpMediator::sendData(char* data, int len, unsigned long to) {
	return m_pNet->sendData(data,len,to);
}

//转发数据(把net层的数据传给核心处理类)
//net层的数据由Udp使用transmit函数传给Mediator
void UdpMediator::transmitData(char* data, int len, unsigned long from) {
	//TODO:传给核心处理类
	
	//测试代码:打印接收到的数据
	cout <<__func__<< ":" << data<<endl;

	
}
Udp.h
#pragma once
#include "INet.h"
class Udp: public INet {
public:
	Udp(INetMediator* p);
	~Udp();
	//初始化网络
	 bool initNet();

	//接收数据
	 void recvData();

	//发送数据
	 bool sendData(char* data, int len, unsigned long to) ;

	//关闭网络
	 void unInitNet();

	 //接收数据的线程函数
	// static unsigned __stdcall recvThread(void* lpvoid);
private:
	HANDLE m_handle;//初始化类型void*可以指向任何地址
};
Udp.cpp
#include "Udp.h"
#include"../mediator/UdpMediator.h"

using namespace std;
Udp::Udp(INetMediator* p) :m_handle(nullptr) {
	m_pMediator =  p;
}
Udp::~Udp(){

}

//不是类成员变量的写法
unsigned __stdcall recvThread(void* lpvoid)
{
	//当前的对象 类型是void* 使用this调用,因为是UDP的对象,需要强转
	// UDP函数创建的时候就有一个对象
	//如果新建一个对象,该对象不是UDP类的对象,它没有初始化
	//需要在初始化的时候就将对象传入
	Udp* pThis = (Udp*)lpvoid;
	pThis->recvData();
	return 1;
}
//初始化网络
 bool Udp::initNet(){
	 //加载库
	 //魔鬼数字:对理解代码造成困难的数字
	 //解决:定义成宏
	 //利于理解,多处使用便于修改
	 //遇见数字的地方定义成宏 新建一个文件定义
	 WORD version = MAKEWORD(DEF_VERSION_HIGH, DEF_VERSION_LOW);
	 WSADATA data;
	 int err=WSAStartup(version,&data);
	 if (0!=err) {
		 cout << "WSAStartup error" << endl;
		 return false;//bool类型返回false
	 }
	 else {
		 cout << "WSAStartup success" << endl;
	 }
	 if (HIBYTE(data.wVersion)!=DEF_VERSION_HIGH||LOBYTE(data.wVersion)!=DEF_VERSION_LOW) {
		 cout<< "version error" << endl;
		 return false;
	 }
	 else {
		 cout << "version right" << endl;
	 }
	 
	 //创建套接字
	 //--接收数据,发送数据都需要使用,需要将该变量定义为类的成员变量
	 //在类的任何成员里都可以使用
	  m_sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
	 if (INVALID_SOCKET == m_sock) {
		 cout << "socket error" << WSAGetLastError() << endl;
		// WSACleanup(); 有卸载库的函数,不需要卸载库
		 return false;
	 }
	 else {
		 cout<< "socket success" <<endl;
	 }
	 //绑定网卡和端口号
	 sockaddr_in addr;
	 addr.sin_addr.S_un.S_addr = INADDR_ANY;
	 addr.sin_family = AF_INET;
	 addr.sin_port = htons(DEF_UDP_PORT);
	 err = bind(m_sock,(sockaddr*)&addr,sizeof(addr));
	 if (SOCKET_ERROR == err) {
		 cout << "bind error" << WSAGetLastError()<<endl;
		/* closesocket(m_sock);
		 WSACleanup();*/
		 return false;
	 }
	 else {
		 cout << "bind success" << endl;
	 }

	 //创建接收数据线程
	 //createThread和ExistThread是一对,如果使用createThread,线程结束时操作系统会自动回收结束线程。
	 //如果线程中使用了c++运行时库的函数(strcpy),这些函数会申请空间但是不释放
	 //ExistTread函数调用时也不释放空间,造成内存泄露
	 //_beginthreadex和_endthreadex是一对,_endthreadex会在线程退出的时候先回收空间,在调用ExistThread
	 //返回值 HANDLE
	 m_handle=(HANDLE)_beginthreadex(nullptr/*安全级别,使用默认安全级别*/,
		0 /*堆栈大小,使用默认堆栈大小1M,决定一个服务端可以创建多少个线程*/,
		 &recvThread/*线程函数的起始地址*/,
		 this/*线程函数的参数列表*/,
		0 /*初始化标志位,0-创建既运行,还有一个挂起状态*/,
		 nullptr/*操作系统给线程分配的id,输出参数,不需要就填空*/);
	 return true;
 }

 //接收数据的线程函数
 // 线程需要做的事情:调用接收数据函数
 //不是当前类的成员函数,加入!
 
 //unsigned __stdcall Udp::recvThread(void* lpvoid)
 //{
	// //当前的对象 类型是void* 使用this调用,因为是UDP的对象,需要强转
	// // UDP函数创建的时候就有一个对象
	// //如果新建一个对象,该对象不是UDP类的对象,它没有初始化
	// //需要在初始化的时候就将对象传入
	// Udp* pThis = (Udp*)lpvoid;
	// pThis->recvData();
	// return 1;
 //}



//接收数据(放在线程里,一直循环等待接收)
 //接收数据是阻塞的,如果需要让接收数据的函数运行-创建线程
 //什么时候开始接收数据? :当程序运行的时候就开始接收数据
 //绑定完网卡和端口号就需要创建线程接收数据
 //需要一直循环等待接收数据,是一个死循环
 //有两个线程,主函数中调用initNet创建了一个线程,recvTread中创建了了一个线程,在recvData中传入数据
 //数据处理需要一段时间,如果在处理数据的过程中收到新的数据
 //新的数据会将旧的数据覆盖掉
 //解决:将recvBuf作为一个缓冲区,将需要处理的数据拷贝到一个新的空间中,如果旧的数据处理完成,将新创建的空间释放
 // 旧的空间用于接收最新的数据
 //根据接收的数据的大小申请一个新的空间里,然后把接受到的数据传入中介者类
void Udp::recvData(){
	cout << "Udp::" << __func__ << endl;
	char recvBuf[9999] = "";
	int recvBufSize = sizeof(recvBuf);
	sockaddr_in addr;
	int addrSize = sizeof(addr);
	int nRecvNum = 0;
	while (true) {
		 nRecvNum = recvfrom(m_sock, recvBuf, recvBufSize, 0, (sockaddr*)&addr, &addrSize);
		if (nRecvNum>0) {
			//接收一个数据包成功
			//根据接收到的数据大小创建一个新空间
			char* newRecvBuf = new char[nRecvNum];

			//把接收到的数据拷贝到新的空间里
			char* pPack = new char[nRecvNum];
			//不能使用strcpy拷贝,它只拷贝字符串/0结束,但是发送的数据不一定是字符串
			// 可能是一个结构体 如果数据中间有0 ,数据拷贝结束,导致数据拷贝出错
			//使用memcpy 按照指定的数据长度拷贝,应用范围比strcpy更广
			memcpy(pPack,recvBuf,nRecvNum);

			//TODO:把接收到的数据传给中介者类
			 
			m_pMediator->transmitData(pPack, sizeof(pPack), addr.sin_addr.S_un.S_addr);
			//测试代码:打印接收到的数据
			//cout << "ip:" << inet_ntoa(addr.sin_addr)<<pPack << endl;

		}
		else {
			cout << "recv error" << WSAGetLastError() << endl;
			break;
		}
	}
}

//发送数据(udp:ip ulong类型,决定发给谁;TCP:socket uint,决定发给谁)
bool Udp::sendData(char* data, int len, unsigned long to) {
	cout << "Udp::" << __func__ << endl;
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(DEF_UDP_PORT);
	addr.sin_addr.S_un.S_addr = to;
	int err = sendto(m_sock,data,len,0,(sockaddr*)&addr,sizeof(addr));
	if (SOCKET_ERROR == err) {
		cout << "send error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		cout << "send success" << endl;
	}
	return true;
}

//关闭网络:回收资源
//回收线程资源,让线程结束工作,关闭套接字,卸载库
void Udp::unInitNet() {
	//1.回收线程资源
	//创建线程时,操作系统给每个线程分配三个资源 1.句柄 2.线程ID 3.内核对象(应用程序如果需要和系统交互,需要通过内核对象)
	//每调用一个资源,实际上使用的是计数器,给每个资源计数,为0时全部自动减一
	//需要让引用计数器变为0
	//1.1结束线程工作-调用recvData函数 让函数退出循环
	m_isRunning = false;
	if (m_handle) {
		//WaitForSingleObject的返回值如果等于WAIT_TIMEOUT,就说明等待的线程在等待时间结束后,还在继续运行
		if (WAIT_TIMEOUT == WaitForSingleObject(m_handle/*等待哪个线程,就填哪个线程句柄*/, 1000/*等待的时间,*/))
		{
			//如果线程还继续运行,强制杀死线程
			TerminateThread(/*要杀死的线程*/m_handle,/*退出码*/-1);
		}
		//关闭句柄
		CloseHandle(m_handle);
		m_handle = nullptr;
	}
	//定义变量
	//但是函数也不能立刻结束,需要等待函数下一次调用的时候才能退出循环
	//等一个单个的线程 等句柄-就是等对应的线程
	//等一定时间,如果超时就说明线程在等待时间结束后还在运行
	
	//2.关闭套接字
	//如果套接字存在且不是无效的套接字就关闭
	if (!m_sock && INVALID_SOCKET != m_sock) {
		closesocket(m_sock);
	}
	//3.卸载库
	WSACleanup();
	//函数里自带保护机制,如果加载库成功就加1,如果卸载库就减一。
	//不需要判断
}

TcpServer

TcpServerMediator.h
#pragma once
#include"INetMediator.h"

class TcpServerMediator :public INetMediator {
public:
	TcpServerMediator();
	~TcpServerMediator();

	//打开网络
	bool openNet();

	//关闭网络
	void closeNet();

	//发送数据
	bool sendData(char* data, int len, unsigned long to);

	//接收数据
	void transmitData(char* data, int len, unsigned long from);

};
TcpServerMidiator.cpp
#include"INetMediator.h"
#include"TcpServerMediator.h"
#include"../net/Tcpserver.h"
TcpServerMediator::TcpServerMediator() {}
TcpServerMediator::~TcpServerMediator() {}

//打开网络
bool TcpServerMediator::openNet() {
	m_pNet = new TcpServer(this);
	return m_pNet->initNet();
}

//关闭网络
void TcpServerMediator::closeNet() {
	if (m_pNet) {
		m_pNet->unInitNet();
		delete m_pNet;
		m_pNet = nullptr;
	}
}

//发送数据
bool TcpServerMediator::sendData(char* data, int len, unsigned long to) {
	return m_pNet->sendData(data,len,to);
}

//接收数据
void TcpServerMediator::transmitData(char* data, int len, unsigned long from) {
	//TODO:把数据传给核心处理类
	//测试
	cout << "client say" << ":" << data << "   len:" << len << endl;
	char s[] = "sgjhgddvgfygaja1db";
	sendData(s, sizeof(s), from);
}
TcpServer.h
#pragma once
#include "INet.h"

class TcpServer : public INet {
public:
	TcpServer(INetMediator* p);
	~TcpServer();
	//初始化网络
	bool initNet();

	//接收数据
	void recvData();

	//发送数据
	bool sendData(char* data, int len, unsigned long to);

	//关闭网络
	void unInitNet();

	//接收数据线程
	static unsigned __stdcall recvThread(void* lpvoid);

	//接受连接函数
	static unsigned __stdcall acceptThread(void* lpvoid);
private:
	SOCKET m_sock;
	list<HANDLE> m_listHandle;
	map<unsigned int, SOCKET> m_mapThreadToSocket;
};
TcpServer.cpp
#include "TcpServer.h"
#include"../mediator/TcpServerMediator.h"
TcpServer::TcpServer(INetMediator* p) :m_sock(INVALID_SOCKET) {
	m_pMediator = p;
}
TcpServer::~TcpServer() {}

unsigned __stdcall TcpServer::recvThread(void* lpvoid) {
	TcpServer* lpthis = (TcpServer*)lpvoid;
	lpthis->recvData();
	return 1;
}

unsigned __stdcall TcpServer::acceptThread(void* lpvoid) {
	TcpServer* lpthis = (TcpServer*)lpvoid;
	sockaddr_in addrto = {};
	int addtoSize = sizeof(addrto);
	SOCKET sockTalk=INVALID_SOCKET;//初始化为无效
	HANDLE handle = nullptr;
	unsigned int threadId = 0;
	//循环接受连接
	while (lpthis->m_isRunning) {
		//接受连接
		//接收连接成功的时候创建的socket用于收发数据,需要在接收发送数据中使用
		//会有多个socket,且socket不同,需要定义为成员变量
		 sockTalk = accept(lpthis->m_sock, (sockaddr*)&addrto, &addtoSize);
		if (INVALID_SOCKET == sockTalk) {
			//如果接收连接失败,打印错误日志,等待下次连接
			cout << "accept errror" << WSAGetLastError() << endl;
			break;
		}
		else {
			//成功打印客户ip
			cout << "client ip:" << inet_ntoa(addrto.sin_addr) << endl;
		
			//创建接收客户端数据的线程

			 handle = (HANDLE)_beginthreadex(nullptr, 0, &recvThread, lpthis, 0, &threadId);
			if (handle) {
				lpthis->m_listHandle.push_back(handle);
			}
			//保存当前连接产生的socket
			lpthis->m_mapThreadToSocket[threadId] = sockTalk;
		}
		
	}
	return 1;
}
//初始化网络
bool TcpServer::initNet() {
	//加载库
	WORD version = MAKEWORD(DEF_VERSION_HIGH, DEF_VERSION_LOW);
	WSADATA data;
	int err=WSAStartup(version,&data);
	if (err != 0) {
		cout << "WSAstartup error" << endl;
		return false;
	}
	else {
		cout << "WSAStarup success " << endl;
	}
	if (DEF_VERSION_HIGH != HIBYTE(data.wVersion) || DEF_VERSION_LOW != LOBYTE(data.wVersion)) {
		cout << "version error" << endl;
		return false;
	}
	else {
		cout << "version right" << endl;
	}
	//创建套接字
	 m_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (INVALID_SOCKET== m_sock) {
		cout << "socket error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		cout << "socket success" << endl;
	}
	//绑定IP和端口号
	sockaddr_in addr;
	addr.sin_addr.S_un.S_addr = ADDR_ANY;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(DEF_TCP_PORT);
	err = bind(m_sock,(sockaddr*)&addr,sizeof(addr));
	if (SOCKET_ERROR == err) {
		cout << "bind error"<<WSAGetLastError() << endl;
		return false;
	}
	else {
		cout << "bind success" << endl;
	}
	//监听
	
	err=listen(m_sock, SOMAXCONN);
	if (SOCKET_ERROR==err) {
		cout << "listen error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		cout << "listen success" << endl;
	}
	//创建接受连接的线程
	HANDLE handle = (HANDLE)_beginthreadex(nullptr, 0, &acceptThread, this, 0, nullptr);
	//保存线程句柄
	if (handle) {
		m_listHandle.push_back(handle);
		cout << "acceptThread:" << GetCurrentThreadId << endl;
	}
	return true;
}

//接收数据
void TcpServer::recvData() {
	cout << "TcpServer::" << __func__ << endl;
	//休眠一会儿:等待acceptThread把socket保存到map里。再去map中取数据
	Sleep(10000);
	//获取当前线程对应的socket,使用当前线程的socket去接收数据
	//如果从map中取值的时候有错误,要么存Id的时候错了,要么key错了
	SOCKET sock = m_mapThreadToSocket[GetCurrentThreadId()];
	if (!sock || INVALID_SOCKET == sock) {
		cout << "TcpServer::recvData error" << WSAGetLastError() << endl;
	}
	//接收数据的长度
	int nRecvNum = 0;
	//保存包的长度
	int nPackLen = 0;
	//记录一个包中累计接收到多少数据
	int noffset = 0;

	//循环接收 
	while (m_isRunning) {
		//接收数据的大小
		nRecvNum = recv(sock, (char*)&nPackLen, sizeof(int), 0);
		if (nRecvNum > 0) {
			//创建一个接收到的数据大小的空间
			char* PackBuf = new char[nPackLen];
			//循环接收一个包的数据
			while (nPackLen > 0) {
				nRecvNum = recv(sock, PackBuf + noffset, nPackLen, 0);
				if (nRecvNum > 0) {
					noffset += nRecvNum;
					nPackLen -= nRecvNum;
				}
				else {
					cout << "recv error2:" << WSAGetLastError() << endl;
					break;
				}
			}
			//TODO:把接收到的数据传给中介者类
			//调用transmit函数
			//需要创建指针,使用指针创建对象,使用指针调用
            //一个包的数据接收完成,此时nPackLen=0.noffset变成包长度。
			m_pMediator->transmitData(PackBuf, noffset,sock);
			////测试代码
			////打印接收到的内容
			//cout << "client say: " << PackBuf << endl;

			////给客户端回复一个字符串
			//char s[] = "ni hao ";
			//sendData(s, sizeof(s), sock);
			//offset清零
			noffset = 0;

		}
		else {
			cout << "server recv error1:" << WSAGetLastError() << endl;
			break;
		}
	}


}

//发送数据
bool TcpServer::sendData(char* data, int len, unsigned long to) {
	cout << "TcpServer::" << __func__ << endl;
	//1.判断参数有效性
	if (nullptr == data || len <= 0) {
		cout << "TcpServer::sendData paramater error" << endl;
	}
	//2.先发包大小
	int sendNum = send(to, (char*)&len, sizeof(int), 0);
	if (SOCKET_ERROR == sendNum) {
		cout << "send error" << WSAGetLastError() << endl;
		return false;
	}
	else { 
		//cout << "send success" << endl;
	}
	//2.发数据
	sendNum = send(to, data, len, 0);
	if (SOCKET_ERROR == sendNum) {
		cout << "send error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		// cout << "send success" << endl;
	}
	return true;
}

//关闭网络
void TcpServer::unInitNet() {
	//1.回收线程资源
	m_isRunning = false;
	HANDLE handle = nullptr;
	for (auto ite = m_listHandle.begin(); ite != m_listHandle.end();) {
		//取出当前节点的句柄
		handle = *ite;
		if (handle) {
			if (WAIT_TIMEOUT == WaitForSingleObject(handle, 1000)) {
				//没有结束强制杀死
				TerminateThread(handle, -1);
			}
			//关闭句柄
			CloseHandle(handle);
			handle = nullptr;
		}
		//从list中移除当前无效节点,返回值是下一个有效节点
		ite = m_listHandle.erase(ite);
	}
	//2.关闭套接字
	if (m_sock && INVALID_SOCKET != m_sock) {
		closesocket(m_sock);
	}
	SOCKET sock = INVALID_SOCKET;
	for (auto ite = m_mapThreadToSocket.begin(); ite != m_mapThreadToSocket.end();) {
		//取出当前节点中保存的socket
		sock = ite->second;
		//关闭taojiez
		if (sock && INVALID_SOCKET != sock) {
			closesocket(sock);
		}

		//把无效节点从中移除,返回值是下一个节点
		ite = m_mapThreadToSocket.erase(ite);
	}
	//3.卸载库
	WSACleanup();
}

TcpClient

TcpClientMediator.h
#pragma once
#include"INetMediator.h"

class TcpClientMediator :public INetMediator {
public:
	TcpClientMediator();
	~TcpClientMediator();

	//打开网络
	 bool openNet() ;

	//关闭网络
	 void closeNet() ;

	//发送数据
	 bool sendData(char* data, int len, unsigned long to) ;

	//接收数据
	 void transmitData(char* data, int len, unsigned long from) ;

};
TcpClientMediator.cpp
#include"INetMediator.h"
#include"TcpClientMediator.h"
#include"../net/TcpClient.h"
TcpClientMediator::TcpClientMediator() { }
TcpClientMediator::~TcpClientMediator() {}

//打开网络
bool TcpClientMediator::openNet() {
	m_pNet = new TcpClient(this);
	return m_pNet->initNet();
}

//关闭网络
void TcpClientMediator::closeNet() {
	if (m_pNet) {
		m_pNet->unInitNet();
		delete m_pNet;
		m_pNet=nullptr;
	}
}

//发送数据
bool TcpClientMediator::sendData(char* data, int len, unsigned long to) {
	return m_pNet->sendData(data,len,to);
}

//转发数据
void TcpClientMediator::transmitData(char* data, int len, unsigned long from) {
	cout << "server say:"<<data <<"   len:"<<len << endl;
	
}
TcpClient.h
#pragma once
#include "INet.h"

class TcpClient : public INet {
public:
	TcpClient(INetMediator* p);
	~TcpClient();
	//初始化网络
	bool initNet();

	//接收数据
	void recvData();

	//发送数据
	bool sendData(char* data, int len, unsigned long to=0);

	//关闭网络
	void unInitNet();

	//接收数据线程
	static unsigned __stdcall recvThread(void* lpvoid);
private:
	SOCKET m_sock;
	HANDLE m_handle;
	
};
TcpClient.cpp
#include "TcpClient.h"
#include"../mediator/UdpMediator.h"
TcpClient::TcpClient(INetMediator* p):m_sock(INVALID_SOCKET), m_handle(nullptr)
{
	m_pMediator = p;
}
TcpClient::~TcpClient() {}

unsigned __stdcall TcpClient::recvThread(void* lpvoid) {
	TcpClient* lpclient = (TcpClient*)lpvoid;
	lpclient->recvData();
	return 1;
}

//初始化网络
bool TcpClient::initNet() {
	//加载库
	WORD version = MAKEWORD(DEF_VERSION_HIGH, DEF_VERSION_LOW);
	WSADATA data;
	int err = WSAStartup(version,&data);
	if (0 != err) {
		cout << "WSAStartup success" << endl;
		return false;
	}
	if (DEF_VERSION_HIGH != HIBYTE(data.wVersion) || DEF_VERSION_LOW != LOBYTE(data.wVersion)) {
		cout << "version error" << endl;
		return false;
	}
	//创建套接字
	m_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (INVALID_SOCKET == m_sock) {
		cout << "socket error" << endl;
		return false;
	}
	else {
		cout << "socket success" << endl;
	}
	//连接服务器
	sockaddr_in addr;
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addr.sin_family = AF_INET;
	addr.sin_port = htons(DEF_TCP_PORT);
    err=connect(m_sock, (sockaddr*)&addr,sizeof(addr) );
	if (SOCKET_ERROR == err) {
		cout << "connect error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		cout << "connect success" << endl;
	}
	//创建接收数据的线程
	m_handle = (HANDLE)_beginthreadex(nullptr, 0,&recvThread,this,0,nullptr);
	
	return true;
}

//接收数据(放在线程里)
void TcpClient::recvData() {
	cout << "TcpClient::" << __func__ << endl;
	
	//接收数据的长度
	int nRecvNum = 0;
	//保存包的长度
	int nPackLen = 0;
	//记录一个包中累计接收到多少数据
	int noffset = 0;

	//循环接收 
	while (m_isRunning) {
		//接收数据的大小
		nRecvNum = recv(m_sock, (char*)&nPackLen,sizeof(int), 0);
		if (nRecvNum > 0) {
			//接收包内容
			//创建一个接收到的数据大小的空间
			char* PackBuf = new char[nPackLen];
			//循环接收一个包的数据
			while (nPackLen > 0) {
				nRecvNum = recv(m_sock, PackBuf + noffset, nPackLen, 0);
				if (nRecvNum > 0) {
					noffset += nRecvNum;
					nPackLen -= nRecvNum;
				}
				else {
					cout << "recv error2:" << WSAGetLastError() << endl;
					break;
				}
			}
			//TODO:把接收到的数据传给中介者类
			m_pMediator->transmitData(PackBuf, noffset, m_sock);
			////打印接收到的内容
			//cout << "server say: " << PackBuf << endl;

			//offset清零
			noffset = 0;

		}
		else {
			cout << "server recv error1:" << WSAGetLastError() << endl;
		}
	}
	


}

//发送数据
bool TcpClient::sendData(char* data, int len, unsigned long to) {
	cout << "TcpClient::" << __func__  << endl;
	
	//粘包问题-用先发数据大小再发包内容
	//1.校验参数合法性
	//-根据参数类型或者意义判断
	if (nullptr == data || len <= 0) {
		cout << " paramater error" << endl;
		return false;
	}

	//2.先发包长度
	int sendNum = send(m_sock,(char*) &len, sizeof(int), 0);
	if (SOCKET_ERROR == sendNum) {
		cout << "send len error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		//cout << "send len success" << endl;
	}
	//3.再发包内容
	sendNum = send(m_sock, data, len, 0);
	if (SOCKET_ERROR == sendNum) {
		cout << "send data error" << WSAGetLastError() << endl;
		return false;
	}
	else {
		//cout << "send data success" << endl;
	}
	return true;
}

//关闭网络
void TcpClient::unInitNet() {
	//1.回收线程资源
	m_isRunning = false;
	if (m_handle) {
		//WaitForSingleObject的返回值如果等于WAIT_TIMEOUT,就说明等待的线程在等待时间结束后,还在继续运行
		if (WAIT_TIMEOUT == WaitForSingleObject(m_handle/*等待哪个线程,就填哪个线程句柄*/, 1000/*等待的时间,*/))
		{
			//如果线程还继续运行,强制杀死线程
			TerminateThread(/*要杀死的线程*/m_handle,/*退出码*/-1);
		}
		//关闭句柄
		CloseHandle(m_handle);
		m_handle = nullptr;
	}
	//2.关闭套接字
	//如果套接字存在且不是无效的套接字就关闭
	if (!m_sock && INVALID_SOCKET != m_sock) {
		closesocket(m_sock);
	}
	//3.卸载库
	WSACleanup();

}

INetMediator.h

#pragma once
//#include "./Net/INet.h"
class INet;

//路径问题-同项目的文件用相对路径寻找 ./返回上一级
class INetMediator {
public:
	INetMediator():m_pNet (nullptr){}
	virtual ~INetMediator(){}

	//打开网络
	//和初始化一样,是调用initNet函数
	virtual bool openNet() = 0;

	//关闭网络
	//调用unInitNet函数
	virtual void closeNet() = 0;

	//发送数据
	//data:发送数据的内容,len:发送的数据长度,to:发送的地址,发给谁,UDP是ip,TCP是socket
	virtual bool sendData(char* data, int len, unsigned long to) = 0;

	//转发数据(把Net层接收到的数据传给核心处理类)
	//1.什么数据
	//2.数据从哪儿来 UDP-ip TCP-socket
	virtual void transmitData(char* data, int len, unsigned long from) = 0;
protected:
	INet* m_pNet;
};

INet.h

#pragma once
#include<WinSock2.h> //系统路径
#include<Windows.h>
#include<iostream>  
#include<process.h>
#include"def.h"  //当前路径
#include<list>
#include<map>
//#include"../mediator/INetMediator.h"
#pragma comment(lib,"Ws2_32.lib")
using namespace std;
class INetMediator;
class INet {
public:
	INet() :m_isRunning(true) , m_pMediator(nullptr), m_sock(INVALID_SOCKET){}
	//();定义 (){}实现 
	//INet函数没有代码实现,直接在h文件中实现更方便
	virtual~INet() {}//父类虚构是虚析构

	//初始化网络
	//父类不知道网络具体如何实现,使用TCP还是UDP,父类步实现,纯虚
	virtual bool initNet() = 0;

	//接收数据
	//接受数据是阻塞的,不知道什么时候可以接受到数据,没有办法通过返回值知道什么时候接受到数据。
	//调用函数-开始接收数据。
	virtual void recvData() = 0;

	//发送数据
	//需要什么参数?
	//sendto(sock,buf,len,0,addr,size)
	//send(sock,buf,len,0)
	//需要发送的数据,地址-地址中端口号不会变化 只有IP地址不断变化
	//ulong ip,SOCKET-uint sock
	//没有ulong的头文件 unsigned long可以替代
	//发送数据(data:发送数据的内容,len:发送的数据长度,to:发送的地址,发给谁,UDP是ip,TCP是socket)
	virtual bool sendData(char* data,int len,unsigned long to) = 0;

	//关闭网络
	virtual void unInitNet()=0;
protected:
	BOOL m_isRunning;
	SOCKET m_sock;
	INetMediator* m_pMediator;
};

QT

创建

创建一个新项目(界面项目)

QMainWindow:类似QT这样的工具软件,有工具栏,状态栏,按钮。

QDialog:聊天窗口,类似微信。只有一些简单的按钮。

QWidget:只有一些按键,可以是一些窗口的一部分。

必须是MinGw,不然版本不对。

自动生成的文件,可以直接运行。

介绍

.pro-配置文件

比如编译器,使用语法。

QT       += core gui//编译器

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17//c++17语法

   //版本
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

   //源文件 
SOURCES += \
    main.cpp \
    loginwindow.cpp
    
//头文件
HEADERS += \
    loginwindow.h
    
//ui文件
FORMS += \
    loginwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

配置文件-类文件-界面文件:组成一个界面。

界面文件main.cpp-应用程序入口

#include "loginwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    LoginWindow w;//界面对象
    w.show();//窗口显示
    return a.exec();//运行一直执行,关闭才能return
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值