3-IM聊天系统-Mediator
文章目录
准备工作
创建筛选器

梳理:
- 父类
- 网络类
- 实现功能
- 打开网络-初始化网络(initNet)
- 关闭网络-关闭(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
- 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
}

被折叠的 条评论
为什么被折叠?



