Windows下多线程编程指南

Windows多线程编程总结
关键字:多线程 线程同步 线程池 内核对象

1 内核对象1 .1内核对象的概念
内核对象是内核分配的一个内存块,这种内存块是一个数据结构,表示内核对象的各种特征。并且只能由内核来访问。应用程序若需要访问内核对象,需要通过操作系统提供的函数来进行,不能直接访问内核对象(Windows从安全性方面来考虑的)。

内核对象通过Create*来创建,返回一个用于标识内核对象的句柄,这些句柄(而不是内核对象)可在创建进程范围内使用,不能够被传递到其他进程中被使用。

1 .2内核对象使用的计数
因为内核对象的所有者是内核,而不是进程,所以何时撤销内核对象由内核决定,而内核做这个决定的依据就是该内核对象是否仍然被使用。那么如何判断内核对象是否被使用呢?可以通过内核对象的“使用计数”属性,一旦这个值变成0了,内核就可以释放该对象了。

1 .3创建内核对象1 .3.1进程与句柄表
每个进程在初始化的时候,将被分配一个句柄表,该句柄表中只存储内核对象的句柄,不存储用户对象的句柄。句柄表的详细结构微软没有公布,但是大致包含三个内容:内核对象句柄,内核对象地址,访问屏蔽标志。


微软为何要将内核对象的句柄设置为进程相关的呢?理由有:

l         不同的进程对内核对象的访问权限是不同的,有必要区分对待

l         如果句柄是全局的,则一个进程可以控制另外一个进程的句柄,破坏另外一个进程的句柄。

 

1 .3.2创建内核对象及操作系统内部机制
利用CreateSomeObject的函数来创建内核对象。调用该函数的时候内核就为该对象分配一个内存块,并进行初始化,然后内核再扫描该进程的句柄表,初始化一条记录并放在句柄表中。

1 .3.3进程中使用内核对象的内部机制
假设函数F使用某个内核对象,其参数为Handle1,则该函数内部需要查找该进程的句柄表,找出参数句柄对应的记录,然后才能使用该内核对象。

1 .4关闭内核对象
无论进程怎样创建内核对象,在不使用该对象的时候都 应当通过Bool CloseHandle(HANDLE hobj)来向操作系统声明结束对该对象的访问。为什么叫声明呢?是因为此时也许还有其他进程对该对象的访问,操作系统可能并不立即释放该对象。操作系统 需要做的是:从进程的句柄表中删除该内核对象的记录,另外再考察该内核对象的使用计数以决定是否需要释放该对象。


1 .5内核对象的共享
说到共享,与之孪生的就是共享权限。Windows内核对象的共享有三种方式:

1 .5.1继承式共享(父子进程间)
只有当进程是父子关系的时候,才能使用此种方式的共享。特别要注意的是继承的是内核对象的句柄,内核对象本身是不具备继承性。要达到这种继承的效果需要做以下几件事:

l         在进程创建内核对象的时候,需要一个安全结构sa(SECURITY_ATTRIBUTES类型,以向OS声明对象的访问方式)作为参数。继承式共享需要 将结构的成员sa.bInheritHandle设置为TRUE。此时OS内部的处理式将进程的句柄表中的该对象的访问屏蔽字段设置成“可继承”。

l         在创建子进程(CreateProcess函数)时,设置创建参数bInheritHandles为TRUE。表示被创建的子进程可以继承父进程中的所有 可继承内核对象。OS内部的处理是:复制父进程句柄表中的记录到子进程的句柄表中,并使用相同的句柄值;为内核对象的使用计数器加1。

特别说明:子进程能够继承的的内核对象仅局限于父进程创建它的时候所拥有的可继承内核对象。子进程诞生后,父进程再搞出什么可继承的东西,子进程是不能用的。这就需要在子进程中使用继承的内核对象的时候需要慎重,以确定内核对象是否已被继承了。

利用SetHandleinformation方法可以随时修改内核对象句柄的一些属性,目前公开的句柄属性有两种,一种是该句柄是否能被继承,另一种是该句柄是否能被关闭。

1 .5.2同名共享
同名共享,不需要共享进程之间存在父子关系。但局限于内核对象是否支持这种共享方式。创建内核对象的Create函数中是否包含pszName是该内核对象是否支持同名共享的标志。

l         方法一:当Process1通过CreateObject(…”someName”)创建了一个名字为someName的内核对象后,Process2也 调用了CreateObject(…”someName”),此时内核的动作是:在全局中查询发现已经存在someName1的对象;为Process2 的句柄表添加一条Ojbect的记录,使用的句柄不确定;为someName这个Object的引用计数器加1。

l         方法二:Process2使用OpenObject(…”someName”)的方式来获得对名someName的Object的句柄。用这种Open方法的时候,需要提供一个参数让OS鉴权,以判定是否能够以参数指定的方式来访问内核对象。

1 .5.3复制内核对象的句柄的方式共享
跨进程边界的内核对象共享的另外一个方法是通过DuplicateHandle来复制内核对象句柄。

如果要将ProcessS中的对象拷贝到ProcessT中则调用DuplicateHandle的进程一定要有对这两个进程的访问权,即句柄表中拥有这两个进程内核对象的句柄记录。

2 线程的一般概念2 .1视图

l         进程只是线程的容器,从来不执行任何东西

l         线程总是在某个进程中被创建

l         线程在进程的地址空间中执行代码

l         线程们共享进程中的所有内核对象

3 线程的创建
HANDLE CreateThread(

         PSECURITY_ATTRIBUTES psa,

         DWORD cbStack,

         PTHREAD_START_ROUTINE pfnStartAddr,

         PVOID pvParam,

         DWORD fdwCreate,

         PDWORD pdwThreadID);

《Windows核心编程》P124介绍说应当使用编译器提供的线程创建函数,而不应当直接使用CreateThread。

3 .1CreateThread调用的内核行为
调用CreateThread后,OS进行如下几个动作:

l         生成一个线程内核对象

l         在进程空间内为线程分配堆栈空间

因为线程的环境同于其所在进程的环境,所以创建的线程可以访问进程中的所有资源,包括线程中所有的内核对象。

4 线程销亡4 .1终止线程的方式:
l         线程函数返回(最好使用这个方式,可以保证:线程种创建的C++对象正常析构;OS释放线程堆栈内存;OS将线程的退出码设置为线程函数的返回值;系统将递减该线程内核对象的的使用计数器【如果此时还有其他引用……,见下面说明】。)

l         调用ExitThread(不能释放C++对象,所以最好不要使用这个方式。另外,如果非要调用也应当调用编译器推荐的,如_endThread【Windows核心编程P127】)

l         同进程内的其他线程(包括主线程)调用TerminateThread(被撤销线程得不到通知,不能释放资源,尽量避免这种方式。另外这个函数是个异步函 数,返回时,线程不保证已经被撤销,如果要观察线程是否被撤销,应当使用WaitForSingleObject)

l         包含线程的进程终止(应当避免这种方式)

4 .2线程退出时OS的行为
l         线程内的所有用户对象被释放。

l         线程的退出码从STILL_ACTIVE改为传递给ExitThread或TerminateThread的代码

l         线程内核对象的状态改为“已通知”

l         如果线程为进程中的最后一个线程,则OS将进程当作已终止运行

l         线程内核对象的引用计数器减1(一旦线程终止了,其他引用改线程内核对象将不能够处理改线程的句柄,但是可以通过调用GetExitcodeThread来检查hThread代表的线程是否已经终止运行了。)

5 线程同步5 .1线程同步的起因以及解决之道5 .1.1共用资源型:多个线程需要访问同一个资源的时候,为了保证资源不被破坏,需要线程对资源的访问具有原子性。5 .1.2依赖型:一个线程等待另外一个线程某件事情完成后才能执行_可以通过手动事件的方式互相通知。5 .2线程同步种类细分同步起因
 同步种类
 同步方法备注
 
共用资源
 多个线程对共用变量做加减操作
 互锁函数族之:InterlockedExchangeAdd
 
共用资源
 多个线程对公共变量、指针做赋值操作
 互锁函数族之:InterlockedExchange ,InterlockedExchangepoint
 
共用资源
 多个线程需要根据对公共变量、指针的判断做操作选择
 互锁函数族之:InterlockedCompareExchange

              InterlockedCompareExchangePointer
 
共用资源
 复杂数据结构(非单值),不适合互锁函数族处理的
 用“关键代码”的方式,关键代码中要注意1、要尽量的快速处理完,以防止其他等待线程等待太长时间 2、线程等待过程中由用户模式切换到内核模式,耗费1000个CPU周期,时间比较长。3、只能对单个进程中的线程进行同步)

InitializeCriticalSection;

EnterCriticalSection;

LeaveCriticalSection;

DeleteCriticalSection;
 
处理线程同步的一种方法


 对线程同步做的一个抽象,线程的同步本质上都是依赖于某个其他事件的发生,用软件的方法来对所依赖的事件做一个抽象,将有助与程序编写的简捷


 CreateEvent

Event的重要属性有一个是“自动”or“手动”,如果是自动的,则在某个线程用Wait××成功等待到事件的“通知”状态后,则事件状态立刻变成“未通知”状态,以保证同时对资源访问的线程只有一个。


 
原则上不算线程同步范畴,而属于对wait**的一种应用方式


 一个可以作为定时器的内核对象,Waitable Timer


 CreateWaitableTimer

SetWaitableTimer,

CancelWaitableTimer


 
共用资源
 一组线程对一组同样性质的资源的争用,则这组资源需要有所表示,以告知线程们是否有空闲的给以为他们服务,以信号量机制实现
 CreateSemaphore,ReleaseSemaphore
 
共用资源
 一组线程对一个单一的资源的争用,需要有一种机制保证同一个事件只有一个线程能得到资源。以Mutex方式实现
 CreateMutex

ReleaseMutex

与关键代码的差别在于:

1、   允许不同进程的线程之间同步

2、   内核对象,用户模式和内核模式切换的时候需要更多的CPU开销
 

特别说明:WaitForSingleObject/WaitForMultipleObject是抑制线程本身的一种手法,配合以共用资源对象或所依赖的其他对象“通知状态”的原子性变化,以达到线程在争用资源、互相依赖时执行的顺序化,从而达到同步的目的。

综上:其实Windows的线程同步机制是提供了一组不同情况下的资源争用处理办 法而已。与此同时推出的Wait××却可以带来很多其他好处,甚至部分缓解C++语言没有事件机制的缺憾,部分达到了JAVA,C#中事件机制的效果,为 Oberserve模式的实现做了些贡献。


Windows多线程编程
[日期:2006-07-17] 来源:  作者: [字体:大 中 小] 

Windows多线程编程总结关键字:多线程 线程同步 线程池 内核对象1 内核对象1 .1内核对象的概念内核对象是内核分配的一个内存块,这种内存块是一个数据结构,表示内核对象的各种特征。并且只能由内核来访问。应用程序若需要访问内核 对象,需要通过操作系统提供的函数来进行,不能直接访问内核对象(Windows从安全性方面来考虑的)。内核对象通过Create*来创建,返回一个用 于标识内核对象的句柄,这些句柄(而不是内核对象)可在创建进程范围内使用,不能够被传递到其他进程中被使用。1 .2内核对象使用的计数因为内核对象的所有者是内核,而不是进程,所以何时撤销内核对象由内核决定,而内核做这个决定的依据就是该内核对象是否仍然被使 用。那么如何判断内核对象是否被使用呢?可以通过内核对象的“使用计数”属性,一旦这个值变成0了,内核就可以释放该对象了。1 .3创建内核对象1 .3.1进程与句柄表每个进程在初始化的时候,将被分配一个句柄表,该句柄表中只存储内核对象的句柄,不存储用户对象的句柄。句柄表的详细结构微软没有公 布,但是大致包含三个内容:内核对象句柄,内核对象地址,访问屏蔽标志。微软为何要将内核对象的句柄设置为进程相关的呢?理由有:l 不同的进程对内核对象的访问权限是不同的,有必要区分对待l 如果句柄是全局的,则一个进程可以控制另外一个进程的句柄,破坏另外一个进程的句柄。 1 .3.2创建内核对象及操作系统内部机制利用CreateSomeObject的函数来创建内核对象。调用该函数的时候内核就为该对象分配一个内存块,并 进行初始化,然后内核再扫描该进程的句柄表,初始化一条记录并放在句柄表中。1 .3.3进程中使用内核对象的内部机制假设函数F使用某个内核对象,其参数为Handle1,则该函数内部需要查找该进程的句柄表,找出参数句柄对应的记 录,然后才能使用该内核对象。1 .4关闭内核对象无论进程怎样创建内核对象,在不使用该对象的时候都应当通过Bool CloseHandle(HANDLE hobj)来向操作系统声明结束对该对象的访问。为什么叫声明呢?是因为此时也许还有其他进程对该对象的访问,操作系统可能并不立即释放该对象。操作系统 需要做的是:从进程的句柄表中删除该内核对象的记录,另外再考察该内核对象的使用计数以决定是否需要释放该对象。1 .5内核对象的共享说到共享,与之孪生的就是共享权限。Windows内核对象的共享有三种方式:1 .5.1继承式共享(父子进程间)只有当进程是父子关系的时候,才能使用此种方式的共享。特别要注意的是继承的是内核对象的句柄,内核对象本身是不具备继 承性。要达到这种继承的效果需要做以下几件事:l 在进程创建内核对象的时候,需要一个安全结构sa(SECURITY_ATTRIBUTES类型,以向OS声明对象的访问方式)作为参数。继承式共享需要 将结构的成员sa.bInheritHandle设置为TRUE。此时OS内部的处理式将进程的句柄表中的该对象的访问屏蔽字段设置成“可继承”。l 在创建子进程(CreateProcess函数)时,设置创建参数bInheritHandles为TRUE。表示被创建的子进程可以继承父进程中的所有 可继承内核对象。OS内部的处理是:复制父进程句柄表中的记录到子进程的句柄表中,并使用相同的句柄值;为内核对象的使用计数器加1。特别说明:子进程能 够继承的的内核对象仅局限于父进程创建它的时候所拥有的可继承内核对象。子进程诞生后,父进程再搞出什么可继承的东西,子进程是不能用的。这就需要在子进 程中使用继承的内核对象的时候需要慎重,以确定内核对象是否已被继承了。利用SetHandleinformation方法可以随时修改内核对象句柄的一 些属性,目前公开的句柄属性有两种,一种是该句柄是否能被继承,另一种是该句柄是否能被关闭。1 .5.2同名共享同名共享,不需要共享进程之间存在父子关系。但局限于内核对象是否支持这种共享方式。创建内核对象的Create函数中是否包含 pszName是该内核对象是否支持同名共享的标志。l 方法一:当Process1通过CreateObject(…”someName”)创建了一个名字为someName的内核对象后,Process2也 调用了CreateObject(…”someName”),此时内核的动作是:在全局中查询发现已经存在someName1的对象;为Process2 的句柄表添加一条Ojbect的记录,使用的句柄不确定;为someName这个Object的引用计数器加1。l 方法二:Process2使用OpenObject(…”someName”)的方式来获得对名someName的Object的句柄。用这种Open方 法的时候,需要提供一个参数让OS鉴权,以判定是否能够以参数指定的方式来访问内核对象。1 .5.3复制内核对象的句柄的方式共享跨进程边界的内核对象共享的另外一个方法是通过DuplicateHandle来复制内核对象句柄。如果要将 ProcessS中的对象拷贝到ProcessT中则调用DuplicateHandle的进程一定要有对这两个进程的访问权,即句柄表中拥有这两个进程 内核对象的句柄记录。2 线程的一般概念2 .1视图l 进程只是线程的容器,从来不执行任何东西l 线程总是在某个进程中被创建l 线程在进程的地址空间中执行代码l 线程们共享进程中的所有内核对象3 线程的创建HANDLE CreateThread( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadID);《Windows核心编程》P124介绍说应当使用编译器提供的线程创建函数,而不应当直接使用CreateThread。3 .1CreateThread调用的内核行为调用CreateThread后,OS进行如下几个动作:l 生成一个线程内核对象l 在进程空间内为线程分配堆栈空间因为线程的环境同于其所在进程的环境,所以创建的线程可以访问进程中的所有资源,包括线程中所有的内核对象。4 线程销亡4 .1终止线程的方式:l 线程函数返回(最好使用这个方式,可以保证:线程种创建的C++对象正常析构;OS释放线程堆栈内存;OS将线程的退出码设置为线程函数的返回值;系统将 递减该线程内核对象的的使用计数器【如果此时还有其他引用……,见下面说明】。)l 调用ExitThread(不能释放C++对象,所以最好不要使用这个方式。另外,如果非要调用也应当调用编译器推荐的,如_endThread 【Windows核心编程P127】)l 同进程内的其他线程(包括主线程)调用TerminateThread(被撤销线程得不到通知,不能释放资源,尽量避免这种方式。另外这个函数是个异步函 数,返回时,线程不保证已经被撤销,如果要观察线程是否被撤销,应当使用WaitForSingleObject)l 包含线程的进程终止(应当避免这种方式)4 .2线程退出时OS的行为l 线程内的所有用户对象被释放。l 线程的退出码从STILL_ACTIVE改为传递给ExitThread或TerminateThread的代码l 线程内核对象的状态改为“已通知”l 如果线程为进程中的最后一个线程,则OS将进程当作已终止运行l 线程内核对象的引用计数器减1(一旦线程终止了,其他引用改线程内核对象将不能够处理改线程的句柄,但是可以通过调用GetExitcodeThread 来检查hThread代表的线程是否已经终止运行了。)5 线程同步5 .1线程同步的起因以及解决之道5 .1.1共用资源型:多个线程需要访问同一个资源的时候,为了保证资源不被破坏,需要线程对资源的访问具有原子性。5 .1.2依赖型:一个线程等待另外一个线程某件事情完成后才能执行_可以通过手动事件的方式互相通知。5 .2线程同步种类细分同步起因同步种类同步方法备注共用资源多个线程对共用变量做加减操作互锁函数族之:interlockedexchangeadd共 用资源多个线程对公共变量、指针做赋值操作互锁函数族之:interlockedexchange ,interlockedexchangepoint共用资源多个线程需要根据对公共变量、指针的判断做操作选择互锁函数族 之:interlockedcompareexchange interlockedcompareexchangepointer共用资源复杂数据结构(非单值),不适合互锁函数族处理的用“关键代码”的方式,关 键代码中要注意1、要尽量的快速处理完,以防止其他等待线程等待太长时间 2、线程等待过程中由用户模式切换到内核模式,耗费1000个cpu周期,时间比较长。3、只能对单个进程中的线程进行同 步)initializecriticalsection;entercriticalsection;leavecriticalsection;deletecriticalsection; 处理线程同步的一种方法对线程同步做的一个抽象,线程的同步本质上都是依赖于某个其他事件的发生,用软件的方法来对所依赖的事件做一个抽象,将有助与程序 编写的简捷createeventevent的重要属性有一个是“自动”or“手动”,如果是自动的,则在某个线程用wait××成功等待到事件的“通 知”状态后,则事件状态立刻变成“未通知”状态,以保证同时对资源访问的线程只有一个。原则上不算线程同步范畴,而属于对wait**的一种应用方式一个 可以作为定时器的内核对象,waitable timercreatewaitabletimersetwaitabletimer,cancelwaitabletimer共用资源一组线程对一组同 样性质的资源的争用,则这组资源需要有所表示,以告知线程们是否有空闲的给以为他们服务,以信号量机制实现 createsemaphore,releasesemaphore共用资源一组线程对一个单一的资源的争用,需要有一种机制保证同一个事件只有一个线程 能得到资源。以mutex方式实现createmutexreleasemutex与关键代码的差别在于:1、 允许不同进程的线程之间同步2、 内核对象,用户模式和内核模式切换的时候需要更多的cpu开销特别说 明:WaitForSingleObject/WaitForMultipleObject是抑制线程本身的一种手法,配合以共用资源对象或所依赖的其他 对象“通知状态”的原子性变化,以达到线程在争用资源、互相依赖时执行的顺序化,从而达到同步的目的。综上:其实Windows的线程同步机制是提供了一 组不同情况下的资源争用处理办法而已。与此同时推出的Wait××却可以带来很多其他好处,甚至部分缓解C++语言没有事件机制的缺憾,部分达到了 JAVA,C#中事件机制的效果,为Oberserve模式的实现做了些贡献。

一般情况下多线程编程多采用MFC类库实现,那么如果不使用MFC 如何进行多线程程序设计呢?本文将就这个问题进行讨论:

  微软在Windows API中提供了建立新的线程的函数CreateThread,它的语法如下:

hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;

  第一个参数是指向SECURITY_ATTRIBUTES型态的结构的指针。 在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。

  CreateThread的第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明:

DWORD WINAPI ThreadProc (PVOID pParam) ; 
  
  CreateThread的第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据。

  CreateThread的第五个参数通常为0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。第六个参数是一个指标,指向接受执行绪ID值的变量。

  大多数Windows程序写作者喜欢用在PROCESS.H表头文件中声明的C执行时期链接库函数_beginthread。它的语法如下:

hThread = _beginthread (ThreadProc, uiStackSize, pParam) ;

  它更简单,对于大多数应用程序很完美,这个线程函数的语法为:

void __cdecl ThreadProc (void * pParam) ;

  在建立多线程的Windows程序时,需要在「Project Settings」对话框中做一些修改。选择「C/C++」页面标签,然后在「Category」下拉式清单方块中选择「Code Generation」。在「Use Run-Time Library」下拉式清单方块中,可以看到用于「Release」设定的「Single-Threaded」和用于Debug设定的「Debug Single-Threaded」。将这些分别改为「Multithreaded」和「Debug Multithreaded」。这将把编译器旗标改为/MT,它是编译器在编译多线程的应用程序所需要的。

  第一个demo.

/*******************************************************
*
* deom1---四个线程同时写一个文件( 没有参数 ) 
*
*
***********************************************************/
#i nclude <windows.h>
#i nclude <process.h> /* _beginthread, _endthread */
#i nclude <iostream>
#i nclude <fstream>
using namespace std;

ofstream out("out.txt");

void ThreadFunc1(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread l"<<endl;
 }
}
void ThreadFunc2(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread 2"<<endl;
 }
}
void ThreadFunc3(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread 3"<<endl;
 }
}
void ThreadFunc4(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread 4"<<endl;
 }
}
int main()
{
 int i=0;
 _beginthread(ThreadFunc1,0,NULL);
 _beginthread(ThreadFunc2,0,NULL);

 _beginthread(ThreadFunc3,0,NULL);
 _beginthread(ThreadFunc4,0,NULL);
 Sleep(3000);
 out<<"end";
 return 0;
}

//demo1 end-----------------------------------------------

第二个demo.
/*******************************************************
*
* deom2---四个线程同时写一个文件( 有参数 ) 
*
*
***********************************************************/
#i nclude <windows.h>
#i nclude <process.h> /* _beginthread, _endthread */
#i nclude <iostream>
#i nclude <fstream>
#i nclude <string>
using namespace std;
ofstream out("out.txt");

void ThreadFunc1(PVOID param)

 while(1)
 {
  char *p;
  p=(char *) param;
  Sleep(10);
  out<<p<<"This was draw by thread l"<<endl;
 }
}
void ThreadFunc2(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread 2"<<endl;
 }
}
void ThreadFunc3(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread 3"<<endl;
 }
}
void ThreadFunc4(PVOID param)

 while(1)
 {
  Sleep(10);
  out<<"This was draw by thread 4"<<endl;
 }
}
int main()
{
 char *pstr=" 参数传递成功";

 _beginthread(ThreadFunc1,0,pstr);
 _beginthread(ThreadFunc2,0,NULL);

 _beginthread(ThreadFunc3,0,NULL);
 _beginthread(ThreadFunc4,0,NULL);
 Sleep(1000);
 out<<"end";
 return 0;
}

// demo2 end ------------------------------------------------

第三个demo( 一个win32 应用程序 )

/*******************************************************
*
* deom3--- 在屏幕上随机画出一系列矩形
*
*
***********************************************************/


#i nclude <windows.h>
#i nclude <process.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HWND hwnd ;

int cxClient, cyClient ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)
{
 static TCHAR szAppName[] = TEXT ("RndRctMT") ;
  MSG msg ;

WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))
{
 MessageBox (NULL, TEXT ("This program requires Windows NT!"),szAppName, MB_ICONERROR) ;
 return 0 ;
}

hwnd = CreateWindow ( szAppName, TEXT ("Random Rectangles"),
  WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT, CW_USEDEFAULT,
  CW_USEDEFAULT, CW_USEDEFAULT,
  NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))
{
 TranslateMessage (&msg) ;
 DispatchMessage (&msg) ;
}
return msg.wParam ;
}

VOID Thread (PVOID pvoid)
{
 HBRUSH hBrush ;
 HDC hdc ;
 int xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;

 while (TRUE)
 {
  if (cxClient != 0 || cyClient != 0)
  {
   xLeft = rand () % cxClient ;
   xRight = rand () % cxClient ;
   yTop = rand () % cyClient ;
   yBottom = rand () % cyClient ;
   iRed = rand () & 255 ;
   iGreen = rand () & 255 ;
   iBlue = rand () & 255 ;
  
   hdc = GetDC (hwnd) ;
   hBrush = CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;
   SelectObject (hdc, hBrush) ;

   Rectangle (hdc,min (xLeft, xRight), min (yTop, yBottom),
     max (xLeft, xRight), max (yTop, yBottom)) ;

   ReleaseDC (hwnd, hdc) ;
   DeleteObject (hBrush) ;
  }
 }
}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 switch (message)
 {
  case WM_CREATE:
   _beginthread (Thread, 0, NULL) ;
   return 0 ;
  case WM_SIZE:
   cxClient = LOWORD (lParam) ;
   cyClient = HIWORD (lParam) ;
   return 0 ;
  case WM_DESTROY:
   PostQuitMessage (0) ;
   return 0 ;
 }
 return DefWindowProc (hwnd, message, wParam, lParam) ;
}

//demo4 end-----------------------------------------------

Windows下多线程编程技术及其实现 
 
无忧IT网 - 软件开发 ( 日期:2005-9-15)  
 
 

  本文首先讨论16位Windows下不具备的线程的概念,然后着重讲述在32位Windows 95环境下多线程的编程技术,最后给出利用该技术的一个实例,即基于Windows95下TCP/IP的可视电话的实现。

  一、问题的提出

  作者最近在开发基于Internet网上的可视电话过程中,碰到了这样一个问 题。在基于Internet网上的可视电话系统中,同时要进行语音采集、语音编解码、图象采集、图象编解码、语音和图象 码流的传输, 所有的这些事情,都要并行处理。特别是语音信号,如果进行图象编解码时间过长,语音信号得不到服务,通话就有间断,如果图象或语音处理时间过长,而不能及 时的传输码流数据,通信同样也会中断。这样就要求我们实现一种并行编程,在只有一个CPU的机器上,也就是要将该CPU时间按照一定的优先准则分配给各个 事件,定期处理某一事件而不会在某一事件处理过长,在32位Windows95或WindowsNT下,我们可以用多线程的编程技术来实现这种并行编程。 实际上这种并行编程在很多场合下都是必须的。例如,在FileManager拷贝文件时,它显示一个对话框, 列出源文件和目标文件的名称,并在对话框中包含了一个Cancel按钮。如果在文件拷贝过程中,点中Cancel按钮,就会终止拷贝。

  在16位Windows中,实现这类功能需要在FileCopy循环内部周期 性地调用PeekMessage函数。如果正在读一个很大的数据块,则只有当这个块读完以后才能响应这个按钮动作,如果从软盘读文件,则要花费好几秒的时 间,由于机器反应太迟钝,你会频繁地点中这个按钮,以为系统不知道你想终止这个操作。如果把FileCopy指令放入另外一个线程,你就不需要在代码中放 一大堆PeekMessage函数,处理 用户界面的线程将与它分开操作,这样,点中Cancel按钮后会立即得到响应。同样的道理,在应用程序中创建一个单独线程来处理所有打印任务也是很有用 的,这样,用户可以在打印处理时继续使用应用程序。

  二、线程的概念

  为了了解线程的概念,我们必须先讨论一下进程的概念。

  一个进程通常定义为程序的一个实例。在Win32中, 进程占据4GB的地址空间。与它们在MS-DOS和16位Windows操作系统中不同, Win32进程是没有活力的。这就是说,一个Win32进程并不执行什么指令,它只是占据着4GB的地址空间,此空间中有应用程序EXE文件的 代码和数据。EXE需要的任意DLL也将它们的代码和数据装入到进程的地址空间。除了地址空间,进程还占有某些资源,比如文件、动态内存分配和线程。当进 程终止时,在它生命期中创建的各种资源将被清除。

  但是进程是没有活力的,它只是一个静态的概念。为了让进程完成一些工作,进程 必须至少占有一个线程,所以线程是描述进程内的执行,正是线程负责执行包含在进程的地址空间中的代码。实际上,单个进程可以包含几个线程, 它们可以同时执行进程的地址空间中的代码。为了做到这一点,每个线程有自己的一组CPU寄存器和堆栈。

  每个进程至少有一个线程在执行其地址空间中的代码,如果没有线程执行进程 地址空间中的代码, 进程也就没有继续存在的理由,系统将自动清除进程及其地址空间。为了运行所有这些线程,操作系统为每个独立线程安排一些CPU 时间,操作系统以轮转方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。创建一个Win32进程时,它的第一个线程称为主线程,它 由系统自动生成,然后可由这个主线程生成额外的线程,这些线程,又可生成更多的线程。
 
Windows下多线程编程技术及其实现 
 
无忧IT网 - 软件开发 ( 日期:2005-9-15)  
 
 


3 下一页

  三、线程的编程技术

  1、编写线程函数

  所有线程必须从一个指定的函 数开始执行,该函数称为线程函数,它必须具有下列原型:

DWORDWINAPIYourThreadFunc(LPVOIDlpvThreadParm);
   该函数输入一个LPVOID型的参数,可以是一个DWORD型的整数,也可以是一个指向一个缓冲区的指针, 返回一个DWORD型的值。象WinMain函数一样,这个函数并不由操作系统调用, 操作系统调用包含在KERNEL32.DLL中的非C运行时的一个内部函数,如StartOfThread,然后由StartOfThread函数建立起 一个异常处理框架后,调用我们的函数。

  2、创建一个线程

  一个进程的主线程是由操作系统自动生成,如果你要让一个主线程创建额外的线程,你可以调用来CreateThread完成。

  HANDLECreateThread(LPSECURITY_ATTRIBUTES lpsa,DWORDcbstack,LPTHREAD_START_ROUTINElpStartAddr,
LPVOID lpvThreadParm,DWORDfdwCreate,LPDWORDlpIDThread);

  其中lpsa参数为一个指向SECURITY_ATTRIBUTES结构的指 针。如果想让对象为缺省安全属性的话,可以传一个NULL,如果想让任一个子进程都可继承一个该线程对象句柄,必须指定一个 SECURITY_ATTRIBUTES结构,其中bInheritHandle成员初始化为TRUE。参数cbstack表示线程为自己所用堆栈分配的 地址空间大小,0表示采用系统缺省值。

  参数lpStartAddr用来表示新线程开始执行时代码所在函数的地址,即 为线程函数。lpvThreadParm为传入线程函数的参数,fdwCreate参数指定控制线程创建的附加标志,可以取两种值。如果该参数为0,线程 就会立即开始执行,如果该参数为CREATE_SUSPENDED,则系统产生线程后,初始化CPU,登记CONTEXT结构的成员,准备好执行该线程函 数中的第一条指令,但并不马上执行,而是挂起该线程。最后一个参数lpIDThread 是一个DWORD类型地址,返回赋给该新线程的ID值。

  3、终止线程

  如果某线程调用了ExitThread 函数,就可以终止自己。

VOIDExitThread(UINTfuExitCode );
  这个函数为调用该函数的线程设置了退出码fuExitCode后, 就终止该线程。调用TerminateThread函数亦可终止线程。

BOOLTerminateThread(HANDLE hThread,DWORDdwExitCode);
  该函数用来结束由hThread参数指定的线程, 并把dwExitCode设成该线程的退出码。当某个线程不在响应时,我们可以用其他线程调用该函数来终止这个不响应的线程。

  4、设定线程的相对优先级

  当一个线程被首次创建时,它的优先级等同于它所属进程的优先级。在单个进程内可以通过调用SetThreadPriority函数改变线程的相对优先级。一个线程的优先级是相对于其所属的进程的优先级而言的。

BOOLSetThreadPriority(HANDLE hThread,intnPriority);
  其中参数hThread是指向待修改 优先级线程的句柄,nPriority可以是以下的值:

  THREAD_PRIORITY_LOWEST,
  THREAD_PRIORITY_BELOW_NORMAL,
  THREAD_PRIORITY_NORMAL,
  THREAD_PRIORITY_ABOVE_NORMAL,
  THREAD_PRIORITY_HIGHEST
  5、挂起及恢复线程

  先前我提到过可以创建挂起状态的线程(通过传递 CREATE_SUSPENDED标志给函数CreateThread来实现)。当你这样做时,系统创建指定线程的核心对象,创建线程的栈,在 CONTEXT结构中初始化线程CPU注册成员。然而,线程对象被分配了一个初始挂起计数值1,这表明了系统将不再分配CPU去执行线程。要开始执行一个 线程,另一个线程必须调用ResumeThread并传递给它调用CreateThread时返回的线程句柄。

DWORD ResumeThread(HANDLEhThread);
  一个线程可以被挂起多次。如果一个线程被挂起3次, 则该线程在它被分配CPU之前必须被恢复3次。除了在创建线程时使用CREATE_SUSPENDED标志,你还可以用SuspendThread函数挂起线程。

DWORDSuspendThread(HANDLE hThread);
 
Windows下多线程编程技术及其实现 
 
无忧IT网 - 软件开发 ( 日期:2005-9-15)  
 
 

  四、多线程编程技术的应用

  我在前面说过,为了实现基于TCP/IP下的可视电话,就必须“并行”地执行 语音采集、语音编解码、图象采集、图象编解码以及码流数据的接收与发送。语音与图象的采集由硬件采集卡进行,我们的程序只需初始化该硬件采集卡,然后实时 读取采集数据即可,但语音和图象数据的编解码以及码流数据的传输都必须由程序去协调执行,决不能在某一件事件上处理过长,必须让CPU轮流的为各个事件服 务,Windows95下的线程正是满足这种要求的编程技术。

  下面我给出了利用Windows95 环境下的多线程编程技术实现的基于TCP/IP的可视电话的部分源码,其中包括主窗口过程函数,以及主叫端与被叫端的TCP/IP接收线程函数和语音编解 码的线程函数。由于图象编解码的实时性比语音处理与传输模块的实时性的 要求要低些,所以我以语音编解码为事件去查询图象数据,然后进行图象编解码,而没有为图象编解码去单独实现一个线程。

  在主窗口初始化时, 我用CREATE_SUSPENDED标志创建了两个线程hThreadG7231和hThreadTCPRev。一个用于语音编解码,它的线程函数为 G723Proc, 该线程不断查询本地有无编好码的语音和图象的码流,如有,则进行H.223打包,然后通过TCP的端口发送给对方。另外一个线程用于TCP/IP的接收, 它的线程函数为AcceptThreadProcRev,该线程不断侦 测TCP/IP端口有无对方传来的码流,如有,就接收码流,进行H.223解码后送入相应的缓冲区。该缓冲区的内容,由语音编解码线程G723Proc查 询,并送入相应的解码器。由于使用了多线程的编程技术,使得操作系统定时去服务语 音编解码模块和传输模块,从而保证了通信的不中断。

  五、程序源码


//基于TCP/IP可视电话主窗口的窗口过程
LONG APIENTRY MainWndProc(HWND hWnd,UINT message,UINT wParam, LONG lParam)
{
 static HANDLE hThreadG7231,hThreadTCPListen,hThreadTCPRev;
 DWORDThreadIDG7231,ThreadIDTCPListen,ThreadIDTCPRev;
 static THREADPACK tp;
 static THREADPACK tp1;
 unsigned char Buf[80];
 CAPSTATUS capStatus;
 switch (message)
  {
   case WM_CREATE:
     Init_Wsock(hWnd); //初始化一些数据结构
     Init_BS(2,&bs);
     vd_tx_pdu.V_S = 0;vd_tx_pdu.N_S = 0;
     vd_rx_pdu.V_R = 0;vd_tx_sdu.bytes = 0;
     if( dnldProg ( hWnd, "h324g723.exe") )
      {
       //装入语音编解码的DSP核心
       MessageBox(hWnd,"Load G.723.1 Kernel Error","Error",MB_OK);
       PostQuitMessage(0); }
     else
       MessageBox(hWnd,"Load G.723.1 Kernel OK!","Indication",MB_OK);
     //创建语音编解码的线程
     parag7231.hWnd = hWnd;
     hThreadG7231=CreateThread (NULL, 0,(LPTHREAD_START_ROUTINE)G723Proc,
                   (G7231DATA *)?g7231,
                   CREATE_SUSPENDED,(LPDWORD)&ThreadIDG7231);
     if (!hThreadG7231)
      {
       wsprintf(Buf, "Error in creating G7231 thread: %d",GetLastError());
       MessageBox (hWnd, Buf, "WM_CREATE", MB_OK);}
     //创建TCP/IP接收线程
     tp1.hWnd = hWnd;
     hThreadTCPRev = CreateThread (NULL, 0,(LPTHREAD_START_ROUTINE)AcceptThreadProcRev,
                    (G7231DATA *)&tp1,CREATE_SUSPENDED,
                    (LPDWORD)&ThreadIDTCPRev);
     if (!hThreadTCPRev)
      {
       wsprintf(Buf, "Error in creating TCP Receive thread: %d",GetLastError());
       MessageBox (hWnd, Buf, "WM_CREATE", MB_OK);}
     //开始侦听网络
     SendMessage(hWnd,WM_COMMAND,IDM_LISTEN,NULL);
     break;
   case WM_VIDEO_ENCODE: //图象编码
     if(needencode)EncodeFunction(hWnd);
     needencode = SendVideoToBuff(&vd_tx_sdu, buff);
     frameMode=TRUE;
     capPreview(capWnd,FALSE);
     capOverlay(capWnd,FALSE);
     capGrabFrameNoStop(capWnd);
     break;
   case WM_VIDEO_DECODE: //图象解码
     Video_Decod_begin = 1;
     play_movie();
     Video_Decod_begin = 0;
     break;
   case WM_COMMAND:
     switch(LOWORD(wParam))
      {
       case IDM_CONNECT: //响应对方的呼叫,接通可视电话
         WskConnect( hWnd );
         ResumeThread(hThreadTCPRev); //运行TCP/IP接收线程
         ResumeThread(hThreadG7231); //运行语音编解码线程
         BeginG7231Codec(); //初始化图象采集卡,并开始采集图象
         frameMode = FALSE;
         capWnd = capCreateCaptureWindow((LPSTR)"Capture Window",
                          WS_CHILD | WS_VISIBLE,
                          100, 100, 176,144 ,
                          (HWND) hWnd, (int) 0);
         capSetCallbackOnError(capWnd, (FARPROC)ErrorCallbackProc) ;
         capSetCallbackOnStatus(capWnd, (FARPROC)StatusCallbackProc) ;
         capSetCallbackOnFrame(capWnd, (FARPROC)FrameCallbackProc) ;
         capDriverConnect(capWnd, 0);
         CenterCaptureWindow(hWnd, capWnd);
         capDlgVideoSource(capWnd);
         capDlgVideoFormat(capWnd);
         capDlgVideoCompression(capWnd);
         capGetStatus(capWnd,&capStatus,sizeof(CAPSTATUS));
         StartNewVideoChannel(hWnd, capWnd) ;
         image = image_one;
         frameMode = TRUE;
         capPreview(capWnd,FALSE);
         capOverlay(capWnd,FALSE);
         capGrabFrameNoStop(capWnd);
         break;
       case IDM_LISTEN: //拨对方号码,呼叫对方
         sock = socket( AF_INET, SOCK_STREAM, 0);
         if (sock == INVALID_SOCKET) {
          MessageBox(hWnd, "socket() failed", "Error", MB_OK);
          closesocket(sock);
          break;}
         if (!FillAddr(hWnd, &local_sin, FALSE )) //获取TCP/IP地址和端口号
          break;
         EnableMenuItem(GetMenu( hWnd ), IDM_LISTEN, MF_GRAYED);
         SetWindowText( hWnd, "Waiting for connection..");
         bind ( sock , (struct sockaddr FAR *)&local_sin,sizeof(local_sin);
         if (listen( sock, MAX_PENDING_CONNECTS ) <0)
          {
           sprintf(szBuff, "%d is the error", 
               WSAGetLastError()); MessageBox(hWnd, szBuff, "listen(sock) failed",
               MB_OK);
           break;}
         tp.hWnd="hWnd; //开始本地的TCP/IP接收线程"
         _beginthread(AcceptThreadProc,0,&tp);
         ResumeThread(hThreadG7231); // 开始本地语音编解码的线程
         break;
       case IDM_DISCONNECT: //挂断可视电话
         CloseG7231Codec();
         SuspendThread(hThreadG7231);
         SuspendThread(hThreadTCPRev);
         WSACleanup();
         Init_Video_Decod_Again();
         capSetCallbackOnError(capWnd, NULL);
         capSetCallbackOnStatus(capWnd, NULL);
         InvalidateRect(hWnd,NULL,1); capSetCallbackOnFrame(capWnd, NULL);
         capSetCallbackOnVideoStream(capWnd, NULL);
         capDriverDisconnect(capWnd);
         Init_Wsock(hWnd);
         MessageBox(hWnd, "Now closing the Video telephone","",MB_OK);
         SetDisConnectMenus(hWnd);
         SendMessage(hWnd, WM_COMMAND,IDM_LISTEN,NULL);
         break;
       case IDM_EXIT:
         CloseG7231Codec();
         SendMessage(hWnd, WM_CLOSE, 0, 0l);
         break; default:
      return (DefWindowProc(hWnd, message, wParam, lParam));
      }
      break;
    case WM_CLOSE:
      if (IDOK !="MessageBox(" hWnd, "OK to close window?", gszAppName,
                  MB_ICONQUESTION | MB_OKCANCEL ))break ;
    case WM_DESTROY:
      WSACleanup();
      CloseG7231Codec();
      TerminateThread(hThreadG7231,0);
      TerminateThread(hThreadTCPRev,0);
      capSetCallbackOnError(capWnd, NULL);
      capSetCallbackOnStatus(capWnd, NULL);
      capSetCallbackOnFrame(capWnd, NULL);
      capSetCallbackOnVideoStream(capWnd, NULL);
      capDriverDisconnect(capWnd);
      FreeAll();
      PostQuitMessage(0);
      break;
   default: /* Passes it on if unproccessed */
       return (DefWindowProc(hWnd, message, wParam, lParam));
   }
 return (0);
}

//主叫方TCP/IP接收线程
DWORD WINAPI AcceptThreadProc( PTHREADPACK ptp )
{
 SOCKADDR_IN acc_sin; /* Accept socket address internet style */
 int acc_sin_len; /* Accept socket address length */
 int status;
 acc_sin_len="sizeof(acc_sin);"
 //调用阻塞函数accept,一直到远端响应为止
 sock="accept(" sock,(struct sockaddr FAR *) &acc_sin,(int FAR *) &acc_sin_len );
 if (sock < 0)
  {
   sprintf(szBuff, "%d is the error", WSAGetLastError());
       MessageBox(ptp->hWnd, szBuff, "accept(sock) failed", MB_OK);
   return (1);
  }
 SetConnectMenus( ptp->hWnd ); //远端提机,可视电话接通
 BeginG7231Codec();
 while (1)
  {
   beg1: 
    status = recv((SOCKET)sock, r_mux_buf,MY_MSG_LENGTH, NO_FLAGS_SET );
   if (status == SOCKET_ERROR) {
    status = WSAGetLastError();
    if( status == 10054 ){
     MessageBox(ptp->hWnd,"对方挂断电话","Indication", MB_OK);
     SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
     _endthread();
     return (1);
     }
    goto beg1;
    }
   if (status) {
     r_mux_buf[ status ] = '\0';
     if ( r_mux_buf_filled == 1 )
      r_mux_buf_overwrite = 1;
     else
     r_mux_buf_filled = 1;
     r_mux_buf_length = status;
    }
   else
    {
     MessageBox( hWnd, "Connection broken", "Error", MB_OK);
     SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
     _endthread();
     return (2);
    }
   demux(); //线路码流H.223解码
   }
 return (0);
}

//被叫方TCP/IP接收线程 
DWORD WINAPI AcceptThreadProcRev( PTHREADPACK ptp )
{
 int status;
 while (1)
  {
  beg2:
  status = recv((SOCKET)sock, r_mux_buf,MY_MSG_LENGTH, NO_FLAGS_SET );
  if (status == SOCKET_ERROR)
   {
    status =WSAGetLastError();
    if( status == 10054 )
     {
      MessageBox(ptp->hWnd,"对方挂断电话","Indication", MB_OK);
      SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
      return (1);
     }
    goto beg2;
    }
  if (status)
   {
    r_mux_buf[ status ] = '\0';
    if( r_mux_buf_filled == 1 )
     r_mux_buf_overwrite = 1;
    else
     r_mux_buf_filled = 1;
     r_mux_buf_length = status;
   }
  else
   {
    MessageBox( hWnd, "Connection broken", "Error", MB_OK);
    SendMessage( ptp->hWnd, WM_COMMAND,IDM_DISCONNECT,NULL);
    return (2);
   }
  demux();
 } /* while (forever) */
 return (0);
}

//语音编解码线程
DWORD WINAPI G723Proc(G7231DATA *data)
{
 int i,len;
 Audio_tx_pduad_tx_pdu;
 unsigned char mux[MAX_MUX_PDU_SIZE];
 do
  {
   len = 0;
   //检测本地有无语音,图象码流要传输
   i = DetectAudioVideoData();
   switch(i)
    {
     case AUDIO_ONLY: //只有语音码流
       AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);
       //H.223打包
       len = AL2_To_MUX(&ad_tx_pdu, mux);
       break;
     case VIDEO_ONLY: //只有图象码流
       SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);
       tx_AL3_I_PDU(&vd_tx_pdu ,&bs , 1); //H.223打包
       len = AL3_To_MUX(&vd_tx_pdu,mux);
       break;
     case AUDIO_VIDEO: //语音和图象码流
       AL2_CRC_coder(&ad_tx_sdu,&ad_tx_pdu);
       SDU_To_PDU(&vd_tx_sdu,&vd_tx_pdu);
       tx_AL3_I_PDU(&vd_tx_pdu ,&bs , 1);
       //H.223打包
       len = AL2_AL3_To_MUX(&ad_tx_pdu,&vd_tx_pdu,mux);
       break;
     case NO_AUDIO_VIDEO: //此刻无码流要传输
       break;
    }
   //TCP/IP发送码流
   if(len != 0)
   send((SOCKET)sock,mux,len,0);
   //是否接收到待解码的码流,有就调用解码器
   PutVideoStreamToDecod();
  }
 while(1);
 return (0);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值