前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[C&C++]用WinSock封装自己的UDP类2

[C&C++]用WinSock封装自己的UDP类2

作者头像
祥知道
发布2020-03-10 15:06:15
5570
发布2020-03-10 15:06:15
举报
文章被收录于专栏:祥的专栏祥的专栏

原创文章,欢迎转载。转载请注明:转载自 祥的博客

原文链接:http://blog.csdn.net/humanking7/article/details/50731385


    • 使用共用体union的好处
    • 线程的创建和用法
      • 创建线程
      • 线程函数
    • 互斥量的用法

接上文,“用WinSock封装自己的UDP类1”,现在主要是要介绍我写的类的一些想法和总结一些技巧。

主要是下面几个内容:

  1. 使用共用体union的好处
  2. 线程的创建和用法
  3. 互斥量的用法


使用共用体union的好处

代码语言:javascript
复制
//Receive Data Pack
//用于存放接收数据,使用union是便于接收和获取对应的数据
union DataReceive               
{
    char Buf[128];
    struct DataDetail
    {       
        double Test0_double;
        UINT8  Test1_u8;
        UINT8  Test2_u8[32];
        double Test3_double;
    }data;
};

这样的好处是不言而喻的,我们传送数据的时候(不管是UDP还是Serial),都是一个字节一个字节(char, unsigned char, UINT8)的传送,这样我们想要传送double,float或其他类型的数据时候就很难搞。用union进行接收和发送的时候就非常方便,用unionBuf来接收和发送,用结构体data里面对应的数据进行解包。

示例代码:

代码语言:javascript
复制
DataSend mySend;//要发送的数据
DataSend myRece;//  接收的数据

······

//--------------------发送打包--------------------
    memset(mySend.Buf,0,sizeof(mySend.Buf));
    cout<<"Input发送数据1[double类型]:";
    cin>>mySend.data.Test0_double;
    cout<<"Input发送数据2[char类型]:";
    cin>>mySend.data.Test1_u8;
    cout<<"Input发送数据3[字符串类型]:";
    cin>>mySend.data.Test2_u8;
    cout<<"Input发送数据4[double类型]:";
    cin>>mySend.data.Test3_double;

    g_UDPClass.UDP_Send(    g_RemoteIPPort.IP, //远端IP
                            g_RemoteIPPort.Port,      //远端Port
                            mySend.Buf,         //发送数据
                            sizeof(mySend.Buf));//数据大小

//--------------------接收解包--------------------
    memset(myRece.Buf,0,sizeof(myRece.Buf));

    rece_num = g_UDPClass.UDP_Rece( myRece.Buf, sizeof(myRece.Buf) );

    cout<<"From "<<g_UDPClass.m_FromIP<<":"<<g_UDPClass.m_FromPort<<" ";
    cout<<"Receive: "<< rece_num <<" char, say: "<<endl;
    cout<<"Test0 "<<sizeof(myRece.data.Test0_double)<<" char:"<<myRece.data.Test0_double<<endl;
    cout<<"Test1 "<<sizeof(myRece.data.Test1_u8)<<" char:"<<myRece.data.Test1_u8<<endl;
    cout<<"Test2 "<<sizeof(myRece.data.Test2_u8)<<" char:"<<myRece.data.Test2_u8<<endl;
    cout<<"Test3 "<<sizeof(myRece.data.Test3_double)<<" char:"<<myRece.data.Test3_double<<endl;


线程的创建和用法

UDP的接收我是开了一个线程进行接收的,并不是基于消息响应的。这样的话就得要开一个线程,一直去调用接收函数。

创建线程

代码语言:javascript
复制
    ······

    //创建线程
    HANDLE handle1 = CreateThread(NULL,0,UDP_Rece_ThreadProc,NULL,0,NULL);
    CloseHandle(handle1); 

    ······

CreateThread() 创建一个线程,其中 UDP_Rece_ThreadProc 是线程函数对应的指针。

但是在调用 CreateThread() 之后,为什么要调用 CloseHandle() 呢? 这是意欲何为?

Closing a thread handle does not terminate the associated thread. To remove a thread object, you must terminate the thread, then close all handles to the thread.

以下是引用网友的解释

1,线程线程句柄(Handle) 不是一个东西,线程是在CPU上运行的…..(说不清楚了),线程句柄是一个内核对象。我们可以通过句柄来操作线程,但是线程的生命周期和线程句柄的生命周期不一样的。线程的生命周期就是线程函数从开始执行到return; 线程句柄的生命周期是从 CreateThread返回到你CloseHandle()。 2,所有的内核对象(包括线程Handle)都是系统资源,用了要还的,也就是说用完后一定要closehandle关闭之,如果不这么做,你系统的句柄资源很快就用光了。 3,如果你CreateThread以后需要对这个线程做一些操作,比如改变优先级,被其他线程等待,强制TermateThread等,就要保存这个句柄,使用完了在CloseHandle。如果你开了一个线程,而不需要对它进行如何干预,CreateThread 后直接 CloseHandle 就行了。 所以 CloseHandel(ThreadHandle ); 只是关闭了一个线程句柄对象,表示我不再使用该句柄,即不对这个句柄对应的线程做任何干预了。并没有结束线程。 如果你觉得多了一个变量,也可以写为: CloseHandel(CreateThread(NULL,0,…..));


《windows核心编程》上说调用 CloseHandle(HANDLE) 表示创建者放弃对该内核对象的操作。如果该对象的引用对象记数为 0 就撤消该对象。 在线程创建后马上调用 CloseHandle() 是个良好的做法,这里不会影响线程的执行,就是因为即使你close了这个handle,它的内部记数也不为零. 但如果你不关,在线程结束后,那个线程对象将滞留于内存中,也就是说你有handle leak. 返回这个handle给你,是让你有机会对这个线程实施外部动作,诸如 waitforsingleobject 之类. CloseHandle的功能是关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄,而ExitThread的功能是终止一个线程,它所接受的参数是一个线程的退出码。 通过调用CloseHandle可以告知系统,已经完成了对某一内核对象的操作,该函数首先检查调用进程的句柄表,来确认进程是否对该句柄所指向的对象有访问权,如果句柄无效则返回FALSE,如果有效,系统将得到该内核对象的数据结构的地址,把结构中的使用计数成员减1,如果计数变为0,则将从内核中释放该内核对象。 如果计数还未到0,就意味着还有其他的进程在使用这个内核对象,那么它就不会被释放。 ExitThread是推荐使用的结束一个线程的方法,当调用该函数时,当前线程的栈被释放,然后线程终止,相对于TerminateThread函数来说,这样做能够更好地完成附加在该线程上的DLL的清除工作。 如果需要进一步的信息,您可以参看有关的MSDN信息。 线程作为一种资源创建后不只被创建线程引用,我想系统自身为了管理线程也会有一个引用,所以用户线程释放线程句柄后,引用计数也不会是零。 引用计数是资源自我管理的一种机制,资源本身以引用计数为零来得知别人不再需要自己,从而把自己kill掉。


CreateThread 后那个线程的引用计数不是1,调用 CloseHandle 只是说自己对这个线程没有兴趣了,线程还是正常运行的 CreateThread后那个线程的引用计数不是1,而是2。 creating a new process causes the system to create a process kernel object and a thread kernel object. At creation time, the system gives each object an initial usage count of 1. Then, just before CreateProcess returns, the function opens the process object and the thread object and places the process-relative handles for each in the hProcess and hThread members of the PROCESS_INFORMATION structure. When CreateProcess opens these objects internally, the usage count for each becomes 2. 创建新的进程后,记数初始化为1,而函数需要返回进程内核对象的句柄,相当于打开一次新创建的类核对象,记数再加1 另外:CreateThread启动了一个线程,同时产生一个句柄让你好操纵这个线程,如果你不要用这个句柄了就CloseHandle关掉它。 调用这个CloseHandle并不意味着结束线程,而是表示不关心此句柄的状态了,也就无法控制子进程的线程了。如果需要关心,可以在子进程结束后再CloseHandle,但一定得CloseHandle。 操作系统内核管理内核对象的生命期,应用程序通过CloseHandle操作内核对象的引用计数,当引用计数由1降为0时,内核负责销毁相应的内核对象。进程和线程都有一个内核对象与它们对应,操作系统通过内核对象管理进程和线程。当你在程序中,不需要再操作创建的线程时,就CloseHandle掉,即便是那个线程目前计数为1,等你调用 CloseHandle 后该计数降为 0 ,但已经创建的线程并没有被马上撤消,而是等线程函数执行完毕后才撤消,或者是在线程函数执行完毕前整个进程结束,那么该线程也被撤消。

线程函数

线程函数的写法,应用MSDN

代码语言:javascript
复制
DWORD WINAPI ThreadProc(
  LPVOID lpParameter   // thread data
);
  • Parameters :
  • lpParameter [in] Receives the thread data passed to the function using the lpParameter parameter of the CreateThread or CreateRemoteThread function.
  • Return Values :
  • The function should return a value that indicates its success or failure.

我的代码

代码语言:javascript
复制
DWORD WINAPI UDP_Rece_ThreadProc(LPVOID pParam)
{   
    int rece_num;
    memset(myRece.Buf,0,sizeof(myRece.Buf));
    while (TRUE)
    {
        WaitForSingleObject(g_Mutex,INFINITE);//等待被唤醒
        rece_num = g_UDPClass.UDP_Rece( myRece.Buf, sizeof(myRece.Buf) );
        if (rece_num>0)
        {
            cout<<"\n==================== Receive Begain ======================="<<endl;
            cout<<"From "<<g_UDPClass.m_FromIP<<":"<<g_UDPClass.m_FromPort<<" ";
            cout<<"Receive: "<< rece_num <<" char, say: "<<endl;
            cout<<"Test0 "<<sizeof(myRece.data.Test0_double)<<" char:";
            cout<<myRece.data.Test0_double<<endl;
            cout<<"Test1 "<<sizeof(myRece.data.Test1_u8)<<" char:";
            cout<<myRece.data.Test1_u8<<endl;
            cout<<"Test2 "<<sizeof(myRece.data.Test2_u8)<<" char:";
            cout<<myRece.data.Test2_u8<<endl;
            cout<<"Test3 "<<sizeof(myRece.data.Test3_double)<<" char:";
            cout<<myRece.data.Test3_double<<endl;
            cout<<"==================== Receive End =========================="<<endl;
        }
        ReleaseMutex(g_Mutex);    
    }
    return 0;
} 


互斥量的用法

往简单的说,就是三行代码:

代码语言:javascript
复制
//创建互斥量    
g_Mutex = CreateMutex(NULL,false, ("MyThread") );
//第一个参数:
//指定一个SECURITY_ATTRIBUTES结构
//或传递零值(将参数声明为ByVal As Long,并传递零值),表示使用不允许继承的默认描述符

//第二个参数:
//true为主线程拥有互斥量,false为当前没有线程拥有互斥量
//如创建进程希望立即拥有互斥体,则设为TRUE。一个互斥体同时只能由一个线程拥有

//第三个参数:
//指定互斥体对象的名字。用vbNullString创建一个未命名的互斥体对象。
//如已经存在拥有这个名字的一个事件,则打开现有的已命名互斥体。
//这个名字可能不与现有的事件、信号机、可等待计时器或文件映射相符

一般是在线程函数中用 WaitForSingleObject()ReleaseMutex() 用于保护需要代码段。

代码语言:javascript
复制
WaitForSingleObject(g_Mutex,INFINITE);//等待被唤醒
//······
//要保护的代码段
//······
ReleaseMutex(g_Mutex);    

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用共用体union的好处
  • 线程的创建和用法
    • 创建线程
      • 线程函数
      • 互斥量的用法
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档