目录
1、类的成员变量
同一个类中的多个函数中都要使用的变量应设置为类的成员变量
2、魔鬼数字
解决方法:定义成宏
目的:1、好理解 2、好修改,如果需要修改,只需要修改一处
3、创建接收数据的线程
(1)CreateThread 和 ExistThread
如果线程中使用了C++运行时库的函数(比如strcpy),这些函数会申请空间,但是不释放
ExistThread退出线程时也不释放这些空间,会造成内存泄漏
(2)_beginthreadex和_endthreadex
_endthreadex会在线程退出的时候,先回收空间,再调用EXistThread
m_handle=(HANDLE)_beginthreadex(nullptr,0,&recvThread,this,0,nullptr);
第一个参数_Security:表示安全级别,nullptr为使用默认安全级别
第二个参数_StackSize:表示堆栈大小,0表示使用默认堆栈大小1M
堆栈大小决定了一个服务端能创建多少线程
第三个参数_StartAddress:表示线程函数起始地址,&recvThread
第四个参数_ArgList:表示线程函数参数列表,this
第五个参数_InitFlag:表示初始化标志位,0表示线程创建即运行,还有一个挂起状态
第六个参数_ThrdAddr:表示操作系统给线程分配的id,输出参数,nullptr表示不需要
HANDLE为重定义的类型,原类型为void*,初始化时可初始化为nullptr
4、线程函数
线程的作用取决于线程函数的写法
unsigned __stdcall Udp::recvThread(void* IpVoid)
{
Udp* pThis = (Udp*)IpVoid;
pThis->recvData();
return 1;
}
但必须要将recvThread改为静态函数
因为创建接收数据的线程时,需要使用接收数据的线程函数,在编译期使用,此时还没有该类的对象
因此,想要在没有对象时使用类的成员函数,就应把函数变为静态函数
5、防止旧接收到的数据被新数据覆盖掉
在初始化网络的函数中,创建了新线程recvThread,里面的空间recvBuf用来接收数据
但处理数据的行为在初始化网络线程中进行,但接收到的数据会再次存在recvBuf中,新的数据会把旧的数据覆盖掉
解决方法:每接收到一个数据,就new一个新的空间pack,将recvBuf中的数据拷贝到新开辟的空间中,将新的空间中的数据传给初始化网络的线程函数,这样再次接收到新的数据时,旧数据被覆盖掉也无碍
6、拷贝用的函数:memcpy_s
不能使用strcpy进行拷贝,是因为接收到的数据不一定是字符串类型,而且strcpy遇到'\0'会自动结束拷贝
使用memcpy进行拷贝,可以按照传入的数据长度进行拷贝,即使遇到'\0'也不会自动拷贝
memcpy的参数:1、目的空间的名称 2、目的空间的大小
3、原始数据 4、原始数据大小
7、回收线程资源
操作系统在创建线程时会分配3个资源:线程id(_beginthreadex的最后一个参数),
线程句柄(_beginthreadex的返回值m_handle),
内核对象(用来与内核沟通的,无形,看不见)
引用计数器:操作系统在管理资源时,使用的是计数器,这个资源有几个人在用,计数器就是几,当计数器变为0的时候,系统会将该资源回收
引用计数器是2,线程句柄和内核对象各占用1个,线程id不占用计数器
我们的卸载库等操作都是将引用计数器-1,等计数器为0时,操作系统会自动将资源回收
让引用计数器变为0:1、结束线程工作,2、关闭句柄
8、计数器-1
(1)结束线程工作
需要在unitNet的函数中结束recvData函数,需要给这两个函数设置一个共同的标志位,bool类型的m_Running,把recvData函数中的while循环的条件改为while (m_Running),在unitNet函数中将m_Running设为false。
但当m_Running被设为false时,函数不一定立刻运行到while循环的位置,因此不能第一时间结束循环,又因为接收数据的函数是一个阻塞函数,因此,需要等待函数内其他部分运行完,因此使用WaitForSingleObject函数
WaitForSingleObject函数参数:1、等待的对象:传入句柄,表示等待的是接收数据的线程
2、等待多长时间:传入1000,表示等待1000毫秒
如果函数返回值为WAIT_TIMEOUT表示等待的线程经过等待的时间后,还没结束运行,应该调用TerminateThread函数强制杀死线程
线程没有自己结束工作,再强制杀死线程,但是不要一开始就强制杀死
原因:线程之间存在竞争关系,比如操作同一个变量,修改同一个变量的值,需要加锁,如果一个线程没解锁,其他线程都不能操作,会出现死锁的问题
TerminateThread函数参数:1、杀死哪个线程
2、退出码
TerminateThread(m_handle, -1);
(2)关闭句柄
调用CloseHandle的函数即可
测试代码
INet* p1 = new Udp;
//打开网络
if (!p1->initNet())
{
cout << "错误" << endl;
return 1;
}
//发个直接广播
char s[] = "123456";
p1->sendData(s, sizeof(s), inet_addr("192.168.56.1"));
while (true)
{
Sleep(5000000);
cout << "server is running";
}
//关闭网络
p1->unitNet();
INet* p2 = new TcpServer;
INet* p3 = new TcpClient;
INetMediator* p4 = new UdpMediator;
INetMediator* p5 = new TcpServerMediator;
INetMediator* p6 = new TcpClientMediator;
delete p1;
delete p2;
delete p3;
delete p4;
delete p5;
delete p6;