TimeMachine

统计

常用链接

阅读排行榜

评论排行榜

最新评论

re: Vc编程基础 bettery 2009-04-24 17:40  
写得真的不错 谢谢 学习了
re: Vc编程基础 guiyinglu 2008-09-28 16:57  
看了就应说谢谢,何况确实不错。
re: VC基础教程 张未 2008-07-17 08:42  
谢谢你!我也是个初学者,很多疑问在你这里得到了解答,并且使我能够快速地使用一些东西。希望以后一直能够上你的网页。:)
re: VC基础教程 感恩作者 2008-07-02 01:52  
谢谢你!!我是初学者,你的总结帮我解决了一个大忙!!真的感谢啊
谢谢···
re: C++中的虚函数(virtual function) 秦辉 2008-04-29 15:32  
不错 哪里有类似的文章 还想再看
楼上说的也是一种原因。但是很多情况下你直接输入ip地址也不管用,这个时候就要考虑tcp/ip连接的问题。因为QQ一般是用udp协议,电子邮件是smtp。
晕,应该是DNS有问题了。
DNS出错将会导致域名不能被解析成为IP地址,所以你可以ping的通,但是打不开网页。同样解释可以上qq,msn和玩游戏,因为这些软件一般是写好了IP地址的。
re: windows网络编程[未登录] wo 2007-03-29 14:51  
ding~~~

这么多人看了,一个回帖的都没有。

小心我哪天删了,让你们都看不成:-)。
re: CMPP2.0 协议SP端的·NET开发 zhang 2006-06-13 11:08  
不错,真的不错,对我很有启发!
re: socket编程原理 adsfs 2006-01-16 17:33  
aa
re: MIMO的文章 eedreams 2005-12-06 21:30  
good!
re: C++中的虚函数(virtual function) 饽饽 2005-06-02 10:23  
三、vtable 分析:

  分析1:虚拟函数表包含此类及其父类的所有虚拟函数的地址。如果它没有重载父类的虚拟函数,vtable中对应表项指向其父类的此函数。反之,指向重载后的此函数。

  分析2:虚拟函数被继承后仍旧是虚拟函数,虚拟函数非常严格地按出现的顺序在 vtable 中排序,所以确定的虚拟函数对应 vtable 中一个固定的位置n,n是一个在编译时就确定的常量。所以,使用vptr加上对应的n,就可得到对应函数的入口地址。

  四、编译器调用虚拟函数的汇编码(参考think in c++):

  push funparam ;先将函数参数压栈

  push si ;将this指针压栈,以确保在当前类上操作

  mov bx,word ptr[si] ;因为vc++编译器将vptr放在类的第一个位置上,所以bx内为vptr

  call word ptr[bx+n] ;调用虚拟函数。n = 所调用的虚拟函数在对应 vtable 中的位置

  纯虚函数:

  一、引入原因:

  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。

  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual returntype function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

  二、纯虚函数实质:

  1、类中含有纯虚函数则它的vtable表不完全,有一个空位,所以,不能生成对象(编译器绝对不允许有调用一个不存在函数的可能)。在它的派生类中,除非重载这个函数,否则,此派生类的vtable表亦不完整,亦不能生成对象,即它也成为一个纯虚基类。

  虚函数与构造、析构函数:

  1、构造函数本身不能是虚拟函数;并且虚机制在构造函数中不起作用(在构造函数中的虚拟函数只会调用它的本地版本)。

  想一想,在基类构造函数中使用虚机制,则可能会调用到子类,此时子类尚未生成,有何后果!?。

  2、析构函数本身常常要求是虚拟函数;但虚机制在析构函数中不起作用。

  若类中使用了虚拟函数,析构函数一定要是虚拟函数,比如使用虚拟机制调用delete,没有虚拟的析构函数,怎能保证delete的是你希望delete的对象。

  虚机制也不能在析构函数中生效,因为可能会引起调用已经被delete掉的类的虚拟函数的问题。

  对象切片:

  向上映射(子类被映射到父类)的时候,会发生子类的vtable 完全变成父类的vtable的情况。这就是对象切片。

  原因:向上映射的时候,接口会变窄,而编译器绝对不允许有调用一个不存在函数的可能,所以,子类中新派生的虚拟函数的入口在vtable中会被强行“切”掉,从而出现上述情况。

  虚拟函数使用的缺点

  优点讲了一大堆,现在谈一下缺点,虚函数最主要的缺点是执行效率较低,看一看虚拟函数引发的多态性的实现过程,你就能体会到其中的原因。
C++虚函数和动态联编技术分析 饽饽 2005-06-02 10:22  
面向对象程序设计的基本观点是用程式来仿真大千世界,这使得它的各种根本特性非常人性化,如封装、继承、多态等等,而虚拟函数就是c++中实现多态性的主将。为了实现多态性,c++编译器也革命性地提供了动态联编(或叫晚捆绑)这一特征。

  虚拟函数亦是mfc编程的关键所在,mfc编程主要有两种方法:一是响应各种消息,进行对应的消息处理。二就是重载并改写虚拟函数,来实现自己的某些要求或改变系统的某些默认处理。

  虚函数的地位是如此的重要,对它进行穷根究底,力求能知其然并知其所以然 对我们编程能力的提高大有好处。下面且听我道来。

  多态性和动态联编的实现过程分析

  一、基础略(限于篇幅,请参阅相应的c++书籍):

  1、多态性:使用基础类的指针动态调用其派生类中函数的特性。

  2、动态联编:在运行阶段,才将函数的调用与对应的函数体进行连接的方式,又叫运行时联编或晚捆绑。

  二、过程描述:

  1、编译器发现一个类中有虚函数,编译器会立即为此类生成虚拟函数表 vtable(后面有对vtable的分析)。虚拟函数表的各表项为指向对应虚拟函数的指针。

  2、编译器在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)。

  有一个办法可以让你感知这个隐含指针的存在,虽然你不能在类中直接看到它,但你可以比较一下含有虚拟函数时的类的尺寸和没有虚拟函数时的类的尺寸,你能够发现,这个指针确实存在。

  class cnovirtualfun
   {
    private:
    long lmember;
    public:
    long getmembervalue();
   } class chavevirtualfun
   {
    private:
     long lmember;
    public:
     virtual long getmembervalue();
    }

   cnovirtualfun obj;
   sizeof(obj) -> == 4;
   chavevirtualfun obj;
   sizeof(obj) -> == 8;

  3、在调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable。这就将类与此类的vtable联系了起来。

  4、在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,从而实现了多态性。在此时才能真正与函数体进行连接,这就是动态联编。
引言

  随着无线通信技术的快速发展,频谱资源的严重不足已经日益成为遏制无线通信事业的瓶颈。如何充分开发利用有限的频谱资源,提高频谱利用率,是当前通信界研究的热点课题之一。

  多输入多输出(MIMO)无线通信技术的概念非常简单,任何一个无线通信系统,只要其发射端和接收端均采用了多个天线或者天线阵列,就构成了一个无线MIMO系统。该系统采用空时处理技术进行信号处理。在多经环境下,该技术能在不增加带宽的情况下成倍地提高通信系统的容量和频谱利用率,是新一代移动通信系统必须采用的关键技术。MIMO技术实质上是为系统提供空间复用增益和空间分集增益,目前针对MIMO信道所进行的研究也主要围绕这两个方面。空间复用技术可以大大提高信道容量,而空间分集则可以提高信道的可靠性,降低信道误码率。

  空间复用就是在接收端和发射端使用多副天线,充分利用空间传播中的多径分量,在同一频带上使用多个数据通道(MIMO子信道)发射信号,从而使得容量随着天线数量的增加而线性增加。这种信道容量的增加不需要占用额外的带宽,也不需要消耗额外的发射功率,因此是提高信道和系统容量一种非常有效的手段。

  空间分集是用来克服无线传输中的信道衰落的一种技术,以分为接收分集和发射分集两类,通常可以认为SIMO系统是接收分集,MISO系统是发射分集。无线信号在复杂的无线信道中传播产生Rayleigh衰落,在不同空间位置上其衰落特性不同。如果两个位置间距大于天线之间的相关距离(通常相隔十个信号波长以上),就认为两处的信号完全不相关,这样就可以实现信号空间分集接收。

  MIMO技术的关键是能够将传统通信系统中存在的多径衰落影响因素变成对用户通信性能有利的增强因素。MIMO技术有效地利用了随机衰落和可能存在的多径传播来成倍地提高业务传输速率。MIMO技术成功之处主要是它能够在不增加所占用的信号带宽的前提下带来无线通信的性能上几个数量级的改善。因此在多天线相关的信道建模、信息论和编码理论、信号处理算法、天线设计以及固定和移动的蜂窝设计每一面都取得了进展。

  MIMO技术带来无线通信系统的四大变革

  无线通信系统从发送端开始,首先经过发送端编码和发送端调制,然后通过无线通信链路到接收端天线,在接收端解调译码恢复原始信号。MIMO技术的引入是无线通信的一场革命,它的影响是全方位的。从无线通信系统的结构来看,MIMO技术通过和无线通信系统的各个关键部分相结合以改善系统的整体性能,如和编码技术结合形成空时编码技术、与调制技术结合形成MIMO OFDM技术、与天线技术结合成为多天线的智能天线系统。MIMO技术的引入使先前的通信信号的时频域处理转变为空时频三维的信号处理,极大的增强了系统性能。MIMO无线通信系统在以下一些方面具有巨大的潜力,如频谱的高效使用、带宽的动态分配、安全的无线应用、更高的服务质量、高性能的信号调制传输技术。为此,下一代MIMO无线通信系统使用了许多新的关键技术,包括空时编码技术、MIMO OFDM技术、智能天线术以及空时频三维信号的自适应技术等等。
                                   
                                    无线通信系统简图


  1. 空时编码技术——MIMO与编码技术的结合

  空时编码技术是在1998年由Vahid Tarokh等人提出的一项基于发射分集的技术。Tarokh等人认为:如果在发射端采用适合多天线传输的编码技术,同时在接收端进行相应地信号处理技术,能获得很大的性能增益,这样就能够实现数据的高速传输。

  最近几年来空时编码技术在无线通信领域引起了广泛关注。朗讯实验室的Forchini和Gans,AT&T的Tarokh及其同事们在这方面作了关键性工作,率先提出了空时编码的概念。空时编码的有效工作需要在发射和接收端使用多个天线,因为空时编码同时利用时间和空间两维信号处理来构造码字,这样才能有效抵消衰落,提高功率效率。并且能够在传输信道中实现并行的多路传送,提高频谱效率。需要说明的是,空时编码技术因为属于空间分集的范畴,所以要求在多散射体的多径情况下应用,天线间距应适当拉开以保证发射、接收信号的相互独立性,以充分利用多散射体所造成的多径(也称之为充分多径)。
 
                       
                            
  空时编码方案结合了信道编码和多发送天线,通过空时编码后的数据被串并转换成n个数据流,每一路数据流经脉冲形成、调制,然后通过n个天线同时发送到无线空间。在接收端,可以用单一天线,也可以用多个天线进行接收,每一个接收天线接收到的是n个发送信号与干扰噪声线性的叠加(衰落系数为权重),然后通过最大似然检测的方法,正确地识别出发送信号。空时译码算法和信道估计技术结合以获得分集增益和编码增益。

  目前,空时编码基本上有空时分组码(STBC)、空时格状码(STTC)和空时分层码(BLAST)等多种。
                                 


  2. MIMO OFDM技术——MIMO与调制技术的结合

  OFDM是一种新的高效的多载波调制技术,它能够有效地对抗多径传播,使受到干扰的信号能够可靠地接收。到目前为止,对于OFDM系统的研究已经相当深入,同时对于MIMO系统研究也早在1998,1999年就有人提出空时码的概念,利用空时码和多个发射、接受天线来完成系统的发射分集和接受分集,可以说这就是MIMO系统的雏形。我们知道单纯的OFDM系统要对抗无线环境中的多经衰落是不够的,他必须和相应的分集技术结合起来,才能更好的发挥其功效。因此我们可以将MIMO系统和OFDM系统结合起来,即构成MIMO OFDM系统.这种系统利用的是空间分集。

  在未来的宽带无线通信系统中,存在两个最严峻的挑战:多径衰落信道和带宽效率。OFDM通过将频率选择性多径衰落信道在频域内转换为平坦信道,从而减小了多径衰落的影响。而MIMO技术能够在空间中产生独立的并行信道同时传输多路数据流,这样就有效的增加了系统的传输速率,即由MIMO提供的空间复用技术能够在不增加系统带宽的情况下增加频谱效率。这样,如果我们将OFDM和MIMO两种技术相结合,就能达到两种效果:一种是系统很高的传输速率,另一种是通过分集达到的很强的可靠性。同时,在MIMO-OFDM系统中加入合适的数字信号处理的算法能更好的增强系统的稳定性。

  3. 智能天线技术——MIMO与天线技术的结合

  MIMO技术的核心是空时信号处理,也就是利用在空间中分布的多个天线将时间域和空间域结合起来进行信号处理。因此,MIMO技术可以看作是智能天线的扩展,

  在多天线技术中,最受关注的是智能天线技术,国际电联已明确将智能天线技术作为三代以后移动通信技术发展的主要方向。智能天线通常也被称作自适应天线,主要用于完成空间滤波和定位。从本质上看,智能天线利用了天线阵列中各单元之间的位置关系,即利用了信号的相位关系,这是它与传统分集技术的本质区别。从一定意义上看,智能天线可看作是一种空分多址SDMA,在SDMA中,多个用户可共享一个信道,这将极大地增加系统容量。

  智能天线技术可以定义为:具有波束成形能力的天线阵列,可以形成特定的天线波束,实现定向发送和接收。智能天线可以利用信号的空间特征分开用户信号、多址干扰以及多径干扰信号。智能天线分为两大类:多波束智能天线与自适应阵智能天线,简称多波束天线和自适应阵天线。

  更高数据速率和越来越复杂的工作特性必然要求下一代无线通信的天线系统提供精确而且灵活的干扰控制。而智能天线提供了一种方案来解决困扰无线网络的根本问题。因此,智能天线一定会在移动通信系统中得到广泛的应用。

  4.空时频自适应技术

  正交频分复用技术由于能有效地抑制码间干扰(ISI)而成为多径衰落信道环境下传输高速数据的有效调制技术。但在传统OFDM传输系统(即没有采用空间分集和自适应调制等性能增强技术的基本OFDM系统)中,由于在所有的子载波中采用相同且固定的调制方式,因此它没有充分利用各子载波的频率选择性衰落信道信息。为了克服这个缺点,人们提出了充分利用子载波信道特性的自适应OFDM技术。在自适应OFDM中,子载波上的调制方式随信道质量而自适应变化,这样就充分利用了各子载波的信道信息,从而大大地提高了系统的误比特性能。

  MIMO系统中不同信道相互独立,在高信噪比的情况下系统的容量几乎跟发射端和接收端天线的最小数目成正比。不同调制技术也会给系统带来不同的增益。根据一些已经发表的论文,MIMO技术跟自适应调制技术结合可以获得更大的系统增益,所以MIMO技术与先进自适应技术结合的研究也成为无线通信研究的一个重要方面。

  MIMO与OFDM技术结合,可以将无线通信的信号处理从时频分集扩展为时空频分集,进一步分割信道为空时频正交的子信道。这样,我们就需要根据各个子信道的实际传输情况灵活的分配发送功率和信息比特。而且由于无线信道的频率选择性和时变性,也需要实时地对信道进行检测,更加有效的利用无线资源。

  对于所有子载波都使用相同固定调制方案的通信系统来说,其误码率主要由经历衰落最严重的子载波决定。因此在频率选择性衰落信道中,随着平均信噪比的增加,系统的误码率下降是十分缓慢的。但我们可以对不同的子信道采用不同传输方案。所有的传输方案要适应每个子信道的信噪比。而且也可以对单个子信道的功率分配实现最优化。(图3为自适应方案的系统结构图)

结束语

  最近十年来随着因特网和移动通信飞速发展,第三代移动通信也已经引入了无线因特网和多媒体业务。而在下一代移动通信系统(即所谓的Beyond 3G或4G)中,人们对传输速率提出了更高的要求,这就需要采用更先进的技术来实现更高的传输速率。然而频谱资源总是有限的,要支持高速率就要开发具有极高频谱利用率的无线通信技术。MIMO技术可以显著提高无线系统的频谱利用率,也非常适用于室内环境下的无线局域网系统使用,采用MIMO技术的无线局域网系统在室内传播环境下的频谱效率可以达到20~40 bit/s/Hz;而使用传统无线通信技术在移动蜂窝中的频谱效率仅为1~5 bit/s/Hz,在点到点的固定微波系统中也只有10~12 bit/s/Hz。MIMO技术作为提高数据传输速率的重要手段得到人们越来越多的关注,已经被认为是新一代无线宽带通信系统的革命技术。

  MIMO技术将成为第三代和未来移动通信与个人通信系统实现数据速率,提高传输质量的重要途径。
re: MIMO的文章 饽饽 2005-05-30 10:58  
移动通信中的MIMO技术

任立刚 宋梅 郗松楠 宋俊德

摘要:未来的新一代移动通信系统(4G)需要提供极高的数据速率,在有限的频谱下提供尽可能高的传输速率,这就需要采用高频谱利用率技术。在理想情况下,多进多出(MIMO)技术可以随着天线数目的增大而线性增大信道容量,具有极高的频谱利用率,是未来移动通信系统中最富有竞争力的技术之一。详细介绍MIMO技术中的空间复用和分集,并给出了MIMO技术的研究方向。

关键词:移动通信,MIMO,4G,空间复用,空间分集

  最近十年来,因特网和移动通信飞速发展,在第三代蜂窝移动通信中已经部分地引入了无线因特网和多媒体业务。而在新一代移动通信系统(即所谓的Beyond 3G或4G)中,人们对传输速率提出了更高的要求,这就需要采用更先进的技术来实现更高的传输速率。然而频谱资源总是有限的,要支持高速率就要开发具有极高频谱利用率的无线通信技术。最近的研究表明,多进多出(MIMO:Multiple Input - Multiple Output)技术可以显著提高无线系统的频谱利用率。实验室的研究证明,采用MIMO技术在室内传播环境下的频谱效率可以达到20?40 bit/s/Hz;而使用传统无线通信技术在移动蜂窝中的频谱效率仅为1?5 bit/s/Hz,在点到点的固定微波系统中也只有10?12 bit/s/Hz。MIMO技术作为提高数据传输速率的重要手段得到人们越来越多的关注,已经被认为是新一代无线传输系统的关键技术之一。

  MIMO是指在发射端和接收端分别使用多个发射天线和接收天线,而传统的通信系统是单进单出(SISO)系统,基于发射分集和接收分集的多进单出(MISO)方式和单进多出(SIMO)方式也是MIMO的一部分。MIMO并不是一门新技术,早在1908年马可尼就提出用MIMO方式来抵抗无线信道的衰落。贝尔实验室的E.Telatar和G.J.Foschini分别独立地在他们各自的论文[1]和[2]中论证了理论上的MIMO信道的香农容量。他们指出,使用N×M信道矩阵描述M副发射天线和N副接收天线系统的无线信道,如果N×M信道矩阵的元素间具有理想的独立衰落,系统容量将会随发射方和接收方天线数中最小一方的天线数minNM的增加而线性增加。这可以在SISO基础上成倍地增加系统容量。同时,Foschini还开发了用于MIMO系统的实际发射/接收算法,这就是著名的贝尔实验室分层空时码(BLAST)算法[2]。后来另外一个突破性的方案,即空时编码的思想由AT&T实验室提出[3],它可以提高MISO和MIMO系统的分集增益。这些信号处理方案可以提高MIMO系统的容量,因而吸引了大量的研究开发人员和工程技术人员进行更深入的研究。

一、 MIMO技术

  MIMO技术实质上是为系统提供空间复用增益和空间分集增益,目前针对MIMO信道所进行的研究也主要围绕这两个方面。空间复用技术可以大大提高信道容量,而空间分集则可以提高信道的可靠性,降低信道误码率。

  1. 空间复用技术

  空间复用就是在接收端和发射端使用多副天线?充分利用空间传播中的多径分量,在同一频带上使用多个数据通道(MIMO子信道)发射信号,从而使得容量随着天线数量的增加而线性增加。这种信道容量的增加不需要占用额外的带宽,也不需要消耗额外的发射功率,因此是提高信道和系统容量一种非常有效的手段。

  空间复用的实现如图1所示,首先将需要传送的信号经过串并转换转换成几个平行的信号流?并且在同一频带上使用各自的天线同时传送,由于多径传播?每一副发射天线针对接收端产生一个不同的空间信号,接收方利用信号不同来区分各自的数据流。实现空间复用必须要求发射和接收天线之间的间距大于相关距离,这样才能保证收发端各个子信道是独立衰落的不相关信道。

  实现空间复用的接收端的解码算法有迫零算法(ZF)、最小均方误差算法(MMSE)、垂直-贝尔实验室分层空时码(V-BLAST)算法和最大似然算法(ML)。迫零算法是一种线性接收方法,可以很好地分离同频信号,但是需要有较高的信噪比才能保持较好的性能。另一种线性接收算法是最小均方误码算法,该算法可以使由于噪声和同频信号相互干扰造成的错误最小,尽管它降低了信号分离的质量,但具有较好的抗噪性能。最大似然算法接收性能最好,但是计算复杂性高。

  BLAST是一种可以实现空间复用增益的算法。1996年Foschini提出对角-贝尔实验室分层空时码(D-BLAST)算法,但是由于算法的复杂度太大,很难实际应用。1998年由Foschini和G.Golden提出V-BLAST算法。V-BLAST算法不是对所有的发送信号一起解码,而是首先对最强的信号解码,然后在接收到的信号中减去这个最强的信号,再对剩余信号中的最强信号解码,再减去这个信号,这样依次进行,直到所有的信号都被译出。V-BLAST算法是算法复杂度和译码性能综合考虑下一种最优的译码算法。

  2. 发送分集和接收分集

  空间分集技术可以分为接收分集和发射分集两类,通常可以认为SIMO系统是接收分集,MISO系统是发射分集。无线信号在复杂的无线信道中传播产生Rayleigh衰落,在不同空间位置上其衰落特性不同。如果两个位置间距大于天线之间的相关距离(通常相隔十个信号波长以上),就认为两处的信号完全不相关,这样就可以实现信号空间分集接收。空间分集一般用两副或者多副大于相关距离的天线同时接收信号,然后在基带处理中将多路信号合并。在SIMO系统中的接收分集技术可以分成最大比率合并(MRC)、等增益合并(EGC)和选择分集合并(SDC)三种类型。在最大比率合并的接收中,每一副天线的输出用一个复数加权,然后相加;等增益合并接收使各副天线的输出信号保持同相,然后相加。选择分集合并接收中,简单地选择众多信号中的一个质量最好的天线的信号,并使用该信号作为接收到的信号。由于最大比率合并之后信号的信噪比等于合并之前各支路的信噪比之和,因此是最佳的合并方式。

  发射分集就是将分集的负担从终端转移到基站端,然而采用发射分集的主要问题是在发射端不知道衰落信道的信道状态信息(CSI)。因此,必须采用信道编码以保证各信道具有良好的性能,具体是采用空时编码(参见文献[3?5])。空时码(STC)是信道编码设计和多发射天线的结合,由AT&T 实验室的Tarokh等人提出。空时码在将数据分成n个数据子流在N副天线上同时发射时,建立了空间分离信号(空域)和时间分离信号(时域)之间的关系,而且在采用最大比率接收合并(MRRC)技术接收时,这些空时码方案可以获得相同的分集增益。除了分集增益以外,好的空时码还可以获得一定的编码增益。

  基于分集发射的空时码可以分为空时格码(STTC:Space-Time Trellis Code)和空时块码(STBC:Space-Time Block Code)。空时格码有较好的性能,但其译码复杂度与传输速率成指数关系,实现难度较大。S.M.Alamouti在文献3中论证了通过一定的信道编码可以将1×2的接收分集增益,转换成2×1的发射分集增益而不会损失分集增益,这可以认为是空时块码的原始模型。在这个基础上Tarokh提出了空时块码,正交设计理论的空时块码性能稍逊于空时格码,但其译码复杂度很低,还可能得到最大的分集发射增益。经过空时编码的信号经过多条相关性较小的无线信道到达接收端,接收端通常需要知道各无线信道参数,即信道估计,可以使用基于导频训练序列进行信道估计,也可以使用盲估计。

二、MIMO技术的应用

  目前,朗讯、松下、金桥和NTT DoCoMo等公司都在积极倡导MIMO天线系统技术的应用。在3GPP的高速下行分组接入方案(HSPDA)中提出了使用MIMO天线系统,这种系统在发送和接收方都有多副天线,可以认为是双天线分集的进一步扩展。另外,在3GPP的WCDMA协议中,涉及到了六种分集发射方法:空时分集发射(STTD:Space Time Transmit Diversity)、时间切换分集发射(TSTD:Time Switched Transmit Diversity)、两种闭环分集发射模式、软切换中的宏分集,以及站点选择分集发射(SSDT:Site Selection Diversity Transmit)。宏分集是指在CDMA系统的软切换过程中,可以通过两个甚至三个基站同时向一个移动台发射同样的信号,这是宏分集发射;同样,接收时通过相邻的基站进行分集接收(多个基站接收),即进行宏分集接收。

re: Vc编程基础 饽饽 2005-05-24 22:46  
上面讲了窗体类CFrameWnd 的封装,下面讲一下应用程序类CWinApp的封装,这两个类是构成Windows应用程序开发 必须用到的 两个类!!

二 应用程序类的封装

在一个应用程序类中,至少应该有一个窗体类的对象m_pMainWnd 作为CWinApp类的 数据成员。
同时在CWinApp类中,还应该有成员函数InitInstance(初始化函数) 和Run(消息循环函数) 这两个基本函数。
在成员函数InitInstance中 要创建窗体对象m_pMainWnd,因为 只有创建了窗体类对象,CWinApp类才能和 窗体类CFrameWnd 建立联系。。。也就是说,m_pMainWnd对象 是它们的联系纽带。。创建窗体对象m_pMainWnd以后,通过它就可以调用窗体类的成员函数。

上面的这段说明 很重要得!可以更好的理解下面的程序。

//应用程序类的声明

class CWinApp
{
public:
CFrameWnd* m_pMainWnd;
public:
BOOL InitInstance(int nCmdShow);
int Run();
};

//应用程序类的 成员函数 实现
BOOL CWinApp:nitInstance(int nCmdShow)
{
m_pMainWnd=new CFrameWnd; //前面只是一个声明,也就是只是一个定义,
// 而此处是为m_pMainWnd变量赋值。使其具有真正的内容。。
m_pMainWnd->Create(NULL,"封装的windows应用程序"); //创建窗口,可以看一下上次的窗体类封装
// 里面的Create函数,就会理解他的调用过程
m_pMainWnd->ShowWindow(nCmdShow); //
m_pMainWnd->UpdateWindow(); //
return TRUE;
}

int CWinApp::Run()
{
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//------------------------------------------------------------
Run()函数是一个消息成员函数。系统为程序建立了叫做消息队列的一个存储空间,在程序的运行过程中,如果发生了一个事件,Windows就会把这个事件所对应的消息送入消息队列等待使用。而应用程序为了取得队列中的消息,就会调用Windows提供的一个API函数GetMessage,并利用这个函数的返回值(当获得消息时,返回TRUE,否则,返回FALSE)组织一个循环来不断获取消息队列中的消息,一旦获得消息,就把这个消息送给系统,即DispatchMessage(&msg)函数。而TranslateMessage(&msg)是把 键盘消息 翻译成字符消息。

//---------------------------------------

re: Vc编程基础 饽饽 2005-05-24 22:45  
对VC编程基础(1)的主程序进行分析后,就会知道,主函数的任务就是 注册(MyRegisterClass(hInstance)),创建(Create(hInstance,nCmdShow))并显示窗体(ShowWindow(hWnd,nCmdShow); UpdateWindow(hWnd); )而且 实现消息循环(Run())。如果用面向对象的思想来考虑,主函数的函数体可以看成一个对象-应用程序类对象,而其中的窗体应该是应该是嵌入在这个应用程序类对象中的另一个对象。。。。。因此,为了形成应用程序框架,应该声明两个类:应用程序类和窗体类。也就是说,应用程序类和窗体类 分别与 主函数和窗体函数 相对应。。。

一窗体类的封装

窗体类应该具有窗体类的 定义、注册、创建和显示 等功能。同时在类中应该有一个HWND类型的窗口句柄hWnd,作为类的数据成员。 因此,窗体类只要把窗口句柄与对窗口操作的API函数封装到一起就可以了..........

//窗体类的声明
class CFrameWnd
{
public:
HWND hWnd;
public:
int RegisterWindow(); //注册函数
void Create(LPCTSTR lpClassName, LPCTSTR lpWindowName); //创建窗体函数
void ShowWindow(int nCmdShow);
void UpdateWindow();
};

//窗体类的成员函数--------------------------

int CFrameWnd::RegisterWindow()
{
WNDCLASS wc; // 定义窗口类结构 对象wc
wc.style=0;
wc.lpfnWndProc=WndProc;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hInstance=hInstance;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName=NULL;
wc.lpszClassName=lpszClassname;
return RegisterClass(&wc); // 实现 窗口类的注册
}

void CFrameWnd::Create(LPCTSTR lpClassName, LPCTSTR lpWindowName)
{
RegisterWindow();
hInst=hInstance;
hWnd=CreateWindow(lpszClassname,lpWindowName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,
NULL,NULL,hInstance,NULL);
}

void CFrameWnd::ShowWindow(int nCmdShow)
{
::ShowWindow(hWnd,nCmdShow);
}

void CFrameWnd::UpdateWindow()
{
::UpdateWindow(hWnd);
}

//--------------------------------
通过上面 分析,然后再与 VC编程基础(1) 的程序中的前面部分比较,你就会理解MFC的类的封装过程。。

下一部分将讲述 应用程序类 的 封装。。。。。


envily edited on 2005-04-20 15:30

可以在图书馆网页下进入所有数据库,这样就好了
论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码)。呵呵,在这里我就用自己实现的一个源代码来说明UDP穿越NAT的原理。

首先先介绍一些基本概念:
NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要目的就是为了能够地址重用。NAT分为两大类,基本的NAT和NAPT(Network Address/Port Translator)。
最开始NAT是运行在路由器上的一个功能模块。

最先提出的是基本的NAT,它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连接(呵呵,这是在上世纪90年代中期提出的)。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。
因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全球唯一的IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会改变IP包中的原IP地址,但是不会改变IP包中的端口)
关于基本的NAT可以参看RFC 1631

另外一种NAT叫做NAPT,从名称上我们也可以看得出,NAPT不但会改变经过这个NAT设备的IP数据报的IP地址,还会改变IP数据报的TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵,我没有见到过),NAPT才是我们真正讨论的主角。看下图:
Server S1
18.181.0.31:1235
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 155.99.25.11:62000 v |
|
NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ |
| 18.181.0.31:1235 | |
v 10.0.0.1:1234 v |
|
Client A
10.0.0.1:1234
有一个私有网络10.*.*.*,Client A是其中的一台计算机,这个网络的网关(一个NAT设备)的外网IP是155.99.25.11(应该还有一个内网的IP地址,比如10.0.0.10)。如果Client A中的某个进程(这个进程创建了一个UDP Socket,这个Socket绑定1234端口)想访问外网主机18.181.0.31的1235端口,那么当数据包通过NAT时会发生什么事情呢?
首先NAT会改变这个数据包的原IP地址,改为155.99.25.11。接着NAT会为这个传输创建一个Session(Session是一个抽象的概念,如果是TCP,也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的第一个UDP开始,结束呢,呵呵,也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个Session分配一个端口,比如62000,然后改变这个数据包的源端口为62000。所以本来是(10.0.0.1:1234->18.181.0.31:1235)的数据包到了互联网上变为了(155.99.25.11:62000->18.181.0.31:1235)。
一旦NAT创建了一个Session后,NAT会记住62000端口对应的是10.0.0.1的1234端口,以后从18.181.0.31发送到62000端口的数据会被NAT自动的转发到10.0.0.1上。(注意:这里是说18.181.0.31发送到62000端口的数据会被转发,其他的IP发送到这个端口的数据将被NAT抛弃)这样Client A就与Server S1建立以了一个连接。

呵呵,上面的基础知识可能很多人都知道了,那么下面是关键的部分了。
看看下面的情况:
Server S1 Server S2
18.181.0.31:1235 138.76.29.7:1235
| |
| |
+----------------------+----------------------+
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 155.99.25.11:62000 v | v 155.99.25.11:62000 v
|
Cone NAT
155.99.25.11
|
^ Session 1 (A-S1) ^ | ^ Session 2 (A-S2) ^
| 18.181.0.31:1235 | | | 138.76.29.7:1235 |
v 10.0.0.1:1234 v | v 10.0.0.1:1234 v
|
Client A
10.0.0.1:1234
接上面的例子,如果Client A的原来那个Socket(绑定了1234端口的那个UDP Socket)又接着向另外一个Server S2发送了一个UDP包,那么这个UDP包在通过NAT时会怎么样呢?
这时可能会有两种情况发生,一种是NAT再次创建一个Session,并且再次为这个Session分配一个端口号(比如:62001)。另外一种是NAT再次创建一个Session,但是不会新分配一个端口号,而是用原来分配的端口号62000。前一种NAT叫做Symmetric NAT,后一种叫做Cone NAT。我们期望我们的NAT是第二种,呵呵,如果你的NAT刚好是第一种,那么很可能会有很多P2P软件失灵。(可以庆幸的是,现在绝大多数的NAT属于后者,即Cone NAT)

好了,我们看到,通过NAT,子网内的计算机向外连结是很容易的(NAT相当于透明的,子网内的和外网的计算机不用知道NAT的情况)。
但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是P2P所需要的)。
那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢?首先,我们必须在内网的NAT上打上一个“洞”(也就是前面我们说的在NAT上建立一个Session),这个洞不能由外部来打,只能由内网内的主机来打。而且这个洞是有方向的,比如从内部某台主机(比如:192.168.0.10)向外部的某个IP(比如:219.237.60.1)发送一个UDP包,那么就在这个内网的NAT设备上打了一个方向为219.237.60.1的“洞”,(这就是称为UDP Hole Punching的技术)以后219.237.60.1就可以通过这个洞与内网的192.168.0.10联系了。(但是其他的IP不能利用这个洞)。

呵呵,现在该轮到我们的正题P2P了。有了上面的理论,实现两个内网的主机通讯就差最后一步了:那就是鸡生蛋还是蛋生鸡的问题了,两边都无法主动发出连接请求,谁也不知道谁的公网地址,那我们如何来打这个洞呢?我们需要一个中间人来联系这两个内网主机。
现在我们来看看一个P2P软件的流程,以下图为例:

Server S (219.237.60.1)
|
|
+----------------------+----------------------+
| |
NAT A (外网IP:202.187.45.3) NAT B (外网IP:187.34.1.56)
| (内网IP:192.168.0.1) | (内网IP:192.168.0.1)
| |
Client A (192.168.0.20:4000) Client B (192.168.0.10:40000)

首先,Client A登录服务器,NAT A为这次的Session分配了一个端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,这就是Client A的外网地址了。同样,Client B登录Server S,NAT B给此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。
此时,Client A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client B,那么他可以从Server S那儿获得B的公网地址187.34.1.56:40000,是不是Client A向这个地址发送信息Client B就能收到了呢?答案是不行,因为如果这样发送信息,NAT B会将这个信息丢弃(因为这样的信息是不请自来的,为了安全,大多数NAT都会执行丢弃动作)。现在我们需要的是在NAT B上打一个方向为202.187.45.3(即Client A的外网地址)的洞,那么Client A发送到187.34.1.56:40000的信息,Client B就能收到了。这个打洞命令由谁来发呢,呵呵,当然是Server S。
总结一下这个过程:如果Client A想向Client B发送信息,那么Client A发送命令给Server S,请求Server S命令Client B向Client A方向打洞。呵呵,是不是很绕口,不过没关系,想一想就很清楚了,何况还有源代码呢(侯老师说过:在源代码面前没有秘密 8)),然后Client A就可以通过Client B的外网地址与Client B通信了。

注意:以上过程只适合于Cone NAT的情况,如果是Symmetric NAT,那么当Client B向Client A打洞的端口已经重新分配了,Client B将无法知道这个端口(如果Symmetric NAT的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致失败的因素太多,我们不推荐这种猜测端口的方法)。
利用CSocket传送大型数据实现 饽饽 2005-03-25 16:49  
利用CSocket传送大型数据实现
(dlutyuanhongl发表于2005-3-19 16:08:05)

传输大型文件实列

SOCKET API,98/NT/2000调试通过。

C/S均建立读/写线程,一但连接,C/S便都可发送/接收文件,发送文件方式使用字节流传送。将传送文件进行分割,每次传送1K,CLIENT或SERVER方收到后进行重组。接收文件方式使用异步SOCKET,根据实际读入数据写文件。在LAN上测试,C/S多次相互传输大型文件如:IIS,SQL SP,和几个上百M的文件效果良好。

Server.cpp

//---------------------------------------------------------------------------
// SERVER端
//
// 贾佳,jiasys@21cn.com
//---------------------------------------------------------------------------

#include <vcl.h>
#include <winsock.h>
#define BUFFSIZE 1024
#pragma hdrstop

#include "Server.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

WSADATA wsaData;
SOCKET sck,sc;
SOCKADDR_IN to,client;
BOOL flag=TRUE;
int iAddrSize,ret,ret_no,i=0;
TCHAR szBuf[BUFFSIZE];
fd_set FdRead;

//---------------------------------------------------------------------------
// ReadClient
//
// 接收文件数据线程,判断建立文件关键字,每次将实际读到的数据写入文件。
//
//---------------------------------------------------------------------------

DWORD WINAPI ReadClient(LPVOID lPort)
{
HANDLE hFile=NULL;
DWORD dwWrite,dwFileSize;
TCHAR szFileName[MAX_PATH];

FD_ZERO(&FdRead);
FD_SET(sck,&FdRead);

while(TRUE)
{
ret_no=select(0,&FdRead,NULL,NULL,NULL);
if(ret_no==SOCKET_ERROR)
{
closesocket(sck);
return FALSE;
}

if(FD_ISSET(sck,&FdRead))
{
iAddrSize=sizeof(client);
sc=accept(sck,(SOCKADDR *)&client,&iAddrSize);
if(sc==INVALID_SOCKET)
{
MessageBox(NULL,"accept error",NULL,MB_OK);
closesocket(sc);
WSACleanup();
}
getpeername(sck,(SOCKADDR *)&client,&iAddrSize);
ShowMessage(inet_ntoa(client.sin_addr));
FD_SET(sc,&FdRead);
}

if(FD_ISSET(sc,&FdRead))
{
ZeroMemory(szBuf,sizeof(szBuf));
if((dwFileSize=recv(sc,szBuf,BUFFSIZE,0))==SOCKET_ERROR)
{
closesocket(sc);
CloseHandle(hFile);
return FALSE;
}
else if((strncmp(szBuf,"UPFILE_",lstrlen("UPFILE_")))==0)
{
wsprintf(szFileName,"c:\%s",szBuf);
hFile=CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,
NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);

if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Server Open File Error",NULL,MB_OK);
return FALSE;
}
ZeroMemory(szBuf,sizeof(szBuf));
}
else
WriteFile(hFile,szBuf,dwFileSize,&dwWrite,NULL);
}
}
CloseHandle(hFile);
return TRUE;
}

//---------------------------------------------------------------------------
// WriteClient
//
// 发送文件数据线程,每次预读1K数据,根据实际读取发送,直到读取数据小于1K
//
//---------------------------------------------------------------------------

DWORD WINAPI WriteClient(LPVOID szFileName)
{

HANDLE hFile;
DWORD dwRead,dwNdx;
BOOL bRet;
TCHAR szFileBuff[BUFFSIZE],szSend[MAX_PATH];

wsprintf(szSend,"DOWNFILE_%s",ExtractFileName((LPCTSTR)szFileName).c_str());
send(sc,szSend,lstrlen(szSend),0);
hFile=CreateFile((LPCTSTR)szFileName,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);

if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Open File Error",NULL,MB_OK);
ExitProcess(0);
}

do
{
bRet=ReadFile(hFile,szFileBuff,BUFFSIZE,&dwRead,NULL);
if(bRet==FALSE)
{
MessageBox(NULL,"Read Buf ERROR!",NULL,MB_OK);
break;
}
else if(dwRead==0)
{
MessageBox(NULL,"File EOF!",NULL,MB_OK);
break;
}
else
{
send(sc,szFileBuff,dwRead,0);
}
}while(dwRead==BUFFSIZE);

CloseHandle(hFile);
return TRUE;
}

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)
{

HANDLE hThread;
DWORD dwTid;

if(WSAStartup(MAKEWORD(1,1),&wsaData)!=NULL)
{
ShowMessage("初始化WINSOCK错误");
WSACleanup();
}

if((sck=socket(AF_INET,SOCK_STREAM,0))==SOCKET_ERROR)
{
ShowMessage("SOCKET错误");
closesocket(sck);
WSACleanup();
}

to.sin_family=AF_INET;
to.sin_port=htons(926);
to.sin_addr.s_addr=htonl(INADDR_ANY);

if(setsockopt(sck,SOL_SOCKET,SO_REUSEADDR,(LPSTR)&flag,sizeof(flag))==SOCKET_ERROR)
{
ShowMessage("setsockopt error!");
closesocket(sck);
}


if(bind(sck,(struct sockaddr *)&to,sizeof(to))==SOCKET_ERROR)
{
ShowMessage("Could not bind");
closesocket(sck);
}

else
{
listen(sck,1);
hThread=CreateThread(NULL,0,ReadClient,(LPVOID)0,0,&dwTid);
CloseHandle(hThread);
}

}
//---------------------------------------------------------------------------


void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;

if(OpenDialog1->Execute())
{
hThread=CreateThread(NULL,0,WriteClient,(LPVOID)OpenDialog1->FileName.c_str(),0,&dwTid);
CloseHandle(hThread);
}


}
//---------------------------------------------------------------------------

Client.cpp

//---------------------------------------------------------------------------
// CLIENT端
//
// 贾佳,jiasys@21cn.com
//---------------------------------------------------------------------------

#include <vcl.h>
#include <winsock.h>
#define BUFFSIZE 1024
#pragma hdrstop

#include "Client.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

WSADATA wsa;
SOCKET sck;
SOCKADDR_IN tto;
hostent *host;
fd_set FdRead;
int port=926,ret,i;
DWORD dwRead;


//---------------------------------------------------------------------------
// ReadClient
//
// 接收文件数据线程,判断建立文件关键字,每次将实际读到的数据写入文件。
//
//---------------------------------------------------------------------------

DWORD WINAPI ReadClient(LPVOID lParam)
{
HANDLE hFile;
DWORD dwWrite,dwFileSize;
TCHAR szFileName[MAX_PATH];
TCHAR szBuff[BUFFSIZE];

FD_ZERO(&FdRead);
FD_SET(sck,&FdRead);

while(TRUE)
{
ret=select(0,&FdRead,NULL,NULL,NULL);
if(ret==SOCKET_ERROR)
{
closesocket(sck);
return FALSE;
}
else if(FD_ISSET(sck,&FdRead))
{
ZeroMemory(szBuff,sizeof(szBuff));
if((dwFileSize=recv(sck,szBuff,BUFFSIZE,0))==SOCKET_ERROR)
{
closesocket(sck);
CloseHandle(hFile);
return FALSE;
}
else if((strncmp(szBuff,"DOWNFILE_",lstrlen("DOWNFILE_")))==0)
{
wsprintf(szFileName,"c:\%s",szBuff);
hFile=CreateFile(szFileName,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,
NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);

if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Cli Open File Error",NULL,MB_OK);
return FALSE;
}
ZeroMemory(szBuff,sizeof(szBuff));
}
else
WriteFile(hFile,szBuff,dwFileSize,&dwWrite,NULL);
}
}
return TRUE;
}

//---------------------------------------------------------------------------
// WriteClient
//
// 发送文件数据线程,每次预读1K数据,根据实际读取发送,直到读取数据小于1K
//
//---------------------------------------------------------------------------


DWORD WINAPI WriteClient(LPVOID szFileName)
{
HANDLE hFile;
DWORD dwRead,dwNdx;
BOOL bRet;
TCHAR szFileBuff[BUFFSIZE],szSend[MAX_PATH];

wsprintf(szSend,"UPFILE_%s",ExtractFileName((LPCTSTR)szFileName).c_str());
send(sck,szSend,lstrlen(szSend),0);
hFile=CreateFile((LPCTSTR)szFileName,GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL|
FILE_ATTRIBUTE_ARCHIVE,(HANDLE)NULL);

if(hFile==INVALID_HANDLE_VALUE)
{
MessageBox(NULL,"Open File Error",NULL,MB_OK);
ExitProcess(0);
}

do
{
bRet=ReadFile(hFile,szFileBuff,BUFFSIZE,&dwRead,NULL);
if(bRet==FALSE)
{
MessageBox(NULL,"Read Buf ERROR!",NULL,MB_OK);
break;
}
else if(dwRead==0)
{
MessageBox(NULL,"File EOF!",NULL,MB_OK);
break;
}
else
{
send(sck,szFileBuff,dwRead,0);
}
}while(dwRead==BUFFSIZE);

CloseHandle(hFile);
return TRUE;
}

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;

WSAStartup(MAKEWORD(1,1),&wsa);
sck=socket(AF_INET,SOCK_STREAM,0);

if(sck==INVALID_SOCKET)
{
ShowMessage("Could not create a sock");
ExitProcess(0);
}

else
{
host=gethostbyname(Edit1->Text.c_str());
tto.sin_family=AF_INET;
tto.sin_port=htons(port);
CopyMemory(&tto.sin_addr,host->h_addr,host->h_length);
if((connect(sck,(struct sockaddr FAR *)&tto,sizeof(tto))==SOCKET_ERROR))
{
ShowMessage("connect error!");
closesocket(sck);
}

else
{
hThread=CreateThread(NULL,0,ReadClient,(LPVOID)0,0,&dwTid);
CloseHandle(hThread);
}
}

}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
HANDLE hThread;
DWORD dwTid;

if(OpenDialog1->Execute())
{
hThread=CreateThread(NULL,0,WriteClient,(LPVOID)OpenDialog1->FileName.c_str(),0,&dwTid);
CloseHandle(hThread);
}

}

//---------------------------------------------------------------------------
re: 今天的工作 饽饽 2005-03-18 16:54  
下午是英雄无敌3
晚上是qq斗地主
哈哈
re: 怎样读串口 饽饽 2005-03-16 20:42  
这是我的一个读串口的函数:

HANDLE m_hIDComDev;

int ReceiveComm(char* RecCommData)
{
DWORD dRead,dReadNum;

COMSTAT ComStat;

LPDWORD ComError;

char *Data;

ClearCommBreak(m_hIDComDev);

ClearCommError(m_hIDComDev,ComError,&ComStat);

dRead=ReadFile(m_hIDComDev, Data, ComStat.cbInQue, &dReadNum, NULL); //接收200个字符
//dReadNum为实际接收字节数
PurgeComm(m_hIDComDev,PURGE_RXCLEAR); //清空接收缓冲区

for(int i = 0 ;i < ComStat.cbInQue; i++)
{
*RecCommData = *Data;

RecCommData++;

Data++;
}

*RecCommData = '\0';

if(dRead)
return 1;
else
return 0;

}
re: 怎样读串口 饽饽 2005-03-16 20:41  
方法三 多线程下实现串行通信

  方法一,二适用于单线程通信。在很多工业控制系统中,常通过扩展串口连接多个外设,各外设发送数据的重复频率不同,要求后台实时无差错捕捉,采集,处理,记录各端口数据,这就需要在自定义的串行通信类中创建端口监视线程,以便在指定的事件发生时向相关的窗口发送通知消息。

  线程的基本概念可详见VC++参考书目,Windows内部的抢先调度程序在活动的线程之间分配CPU时间,Win 32 区分两种不同类型的线程,一种是用户界面线程UI(User Interface Thread),它包含消息循环或消息泵,用于处理接收到的消息;另一种是工作线程(Work Thread),它没有消息循环,用于执行后台任务。用于监视串口事件的线程即为工作线程。

  多线程通信类的编写在端口的配置,连接部分与单线程通信类相同,在端口配置完毕后,最重要的是根据实际情况,建立多线程之间的同步对象,如信号灯,临界区,事件等,相关细节可参考VC++ 中的同步类。

  一切就绪后即可启动工作线程:

CWinThrea *CommThread = AfxBegin
Thread(CommWatchThread, // 线程函数名
(LPVOID) m_pTTYInfo, // 传递的参数
THREAD_PRIORITY_ABOVE_NORMAL, // 设置线程优先级
(UINT) 0, // 最大堆栈大小
(DWORD) CREATE_SUSPENDED , // 创建标志
(LPSECURITY_ATTRIBUTES) NULL); // 安全性标志

  同时,在串口事件监视线程中:

if(WaitCommEvent(pTTYInfo->idComDev,&dwEvtMask,NULL))
{
if((dwEvtMask & pTTYInfo->dwEvtMask )== pTTYInfo->dwEvtMask)
{
WaitForSingleObject(pTTYInfo->hPostEvent,0xFFFFFFFF);
ResetEvent(pTTYInfo->hPostEvent); // 置同步事件对象为非信号态
::PostMessage(CSampleView,ID_COM1_DATA,0,0); // 发送通知消息
}
}

  用PostMessage()向指定窗口的消息队列发送通知消息,相应地,需要在该窗口建立消息与成员函数间的映射,用ON_MESSAGE将消息与成员函数名关联。

BEGIN_MESSAGE_MAP(CSampleView, CView)
//{{AFX_MSG_MAP(CSampleView)
ON_MESSAGE(ID_COM1_DATA, OnProcessCom1Data)
ON_MESSAGE(ID_COM2_DATA, OnProcessCom2Data)
.....
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

  然后在各成员函数中完成对各串口数据的接收处理,但必须保证在下一次监测到有数据到来之前,能够完成所有的中间处理工作。否则将造成数据的捕捉错误。

  多线程的实现可以使得各端口独立,准确地实现串行通信,使串口通信具有更广泛的灵活性与严格性,且充分利用了CPU时间。但在具体的实时监控系统中如何协调多个线程,线程之间以何种方式实现同步也是在多线程串行通信程序实现的难点。
re: 怎样读串口 饽饽 2005-03-16 20:41  
方法二:在单线程中实现自定义的串口通信类

  控件简单易用,但由于必须拿到对话框中使用,在一些需要在线程中实现通信的应用场合,控件的使用显得捉襟见肘。此时,若能够按不同需要定制灵活的串口通信类将弥补控件的不足,以下将介绍如何在单线程中建立自定义的通信类。

  该通信类CSimpleComm需手动加入头文件与源文件,其基类为CObject,大致建立步骤如下:

  (1) 打开串口,获取串口资源句柄

  通信程序从CreateFile处指定串口设备及相关的操作属性。再返回一个句柄,该句柄将被用于后续的通信操作,并贯穿整个通信过程。CreateFile()函数中有几个值得注意的参数设置:串口共享方式应设为0,串口为不可共享设备;创建方式必须为OPEN_EXISTING,即打开已有的串口。对于dwFlagAndAttribute参数,对串口有意义的值是FILE_FLAG_OVERLAPPED,该标志表明串口采用异步通信模式,可进行重叠操作;若值为NULL,则为同步通信方式,在同步方式下,应用程序将始终控制程序流,直到程序结束,若遭遇通信故障等因素,将导致应用程序的永久等待,所以一般多采用异步通信。

  (2)串口设置

  串口打开后,其属性被设置为默认值,根据具体需要,通过调用GetCommState(hComm,&dcb)读取当前串口设备控制块DCB(Device Control Block)设置,修改后通过SetCommState(hComm,&dcb)将其写入。再需注意异步读写的超时控制设置, 通过COMMTIMEOUTS结构设置超时,调用SetCommTimeouts(hComm,&timeouts)将结果写入。以下是温度监控程序中串口初始化成员函数:

BOOL CSimpleComm::Open( )
{
DCB dcb;

m_hIDComDev=CreateFile( "COM2",
GENERIC_READ | GENERIC_WRITE,
0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_
NORMAL|FILE_FLAG_OVE RLAPPED, NULL );
// 打开串口,异步操作
if( m_hIDComDev == NULL ) return( FALSE );

dcb.DCBlength = sizeof( DCB );
GetCommState( m_hIDComDev, &dcb ); // 获得端口默认设置
dcb.BaudRate=CBR_4800;
dcb.ByteSize=8;
dcb.Parity= NOPARITY;
dcb.StopBits=(BYTE) ONESTOPBIT;
...... }

  (3)串口读写操作

  主要运用ReadFile()与WriteFile()API函数,若为异步通信方式,两函数中最后一个参数为指向OVERLAPPED结构的非空指针,在读写函数返回值为FALSE的情况下,调用GetLastError()函数,返回值为ERROR_IO_PENDING,表明I/O操作悬挂,即操作转入后台继续执行。此时,可以用WaitForSingleObject()来等待结束信号并设置最长等待时间,举例如下:

BOOL bReadStatus;
bReadStatus = ReadFile( m_hIDComDev, buffer,
dwBytesRead, &dwBytesRead, &m_OverlappedRead );
if(!bReadStatus)
{
if(GetLastError()==ERROR_IO_PENDING)
{
WaitForSingleObject(m_OverlappedRead.hEvent,1000);
return ((int)dwBytesRead);
}
return(0);
}
return ((int)dwBytesRead);

  定义全局变量m_Serial作为新建通信类CSimpleComm的对象,通过调用类的成员函数即可实现所需串行通信功能。与方法一相比,方法二赋予串行通信程序设计较大的灵活性,端口的读写可选择较简单的查询式,或通过设置与外设数据发送时间间隔TimeCycle相同的定时器:SetTimer(1,TimeCycle,NULL),进行定时读取或发送。

CSampleView:: OnTimer(UINT nIDEvent)
{
char InputData[30];
m_Serial.ReadData(InputData,30);
// 数据处理
}

  若对端口数据的响应时间要求较严格,可采用事件驱动I/O读写,Windows定义了9种串口通信事件,较常用的有:

  EV_RXCHAR: 接收到一个字节,并放入输入缓冲区。

  EV_TXEMPTY: 输出缓冲区中的最后一个字符发送出去。

  EV_RXFLAG: 接收到事件字符(DCB结构中EvtChar成员),放入输入缓冲区。

  在用SetCommMask()指定了有用的事件后,应用程序可调用WaitCommEvent()来等待事件的发生。
SetCommMask(hComm,0)可使WaitCommEvent()中止。
re: 怎样读串口 kugou123(酷狗)(每天学VC,补充你我的维C) 2005-03-16 20:40  
方法一:使用VC++提供的串行通信控件MSComm 首先,在对话框中创建通信控件,若Control工具栏中缺少该控件,可通过菜单Project --> Add to Project --> Components and Control插入即可,再将该控件从工具箱中拉到对话框中。此时,你只需要关心控件提供的对 Windows 通讯驱动程序的 API 函数的接口。换句话说,只需要设置和监视MSComm控件的属性和事件。

  在ClassWizard中为新创建的通信控件定义成员对象(CMSComm m_Serial),通过该对象便可以对串口属性进行设置,MSComm 控件共有27个属性,这里只介绍其中几个常用属性:

  CommPort 设置并返回通讯端口号,缺省为COM1。

  Settings 以字符串的形式设置并返回波特率、奇偶校验、数据位、停止位。

  PortOpen 设置并返回通讯端口的状态,也可以打开和关闭端口。

  Input 从接收缓冲区返回和删除字符。

  Output 向发送缓冲区写一个字符串。

  InputLen 设置每次Input读入的字符个数,缺省值为0,表明读取接收缓冲 区中的全部内容。

  InBufferCount 返回接收缓冲区中已接收到的字符数,将其置0可以清除接收缓 冲区。

  InputMode 定义Input属性获取数据的方式(为0:文本方式;为1:二进制方式)。

  RThreshold 和 SThreshold 属性,表示在 OnComm 事件发生之前,接收缓冲区或发送缓冲区中可以接收的字符数。

  以下是通过设置控件属性对串口进行初始化的实例:

BOOL CSampleDlg:: PortOpen()
{
BOOL m_Opened;
......
m_Serial.SetCommPort(2); // 指定串口号
m_Serial.SetSettings("4800,N,8,1"); // 通信参数设置
m_Serial.SetInBufferSize(1024); // 指定接收缓冲区大小
m_Serial.SetInBufferCount(0); // 清空接收缓冲区
m_Serial.InputMode(1); // 设置数据获取方式
m_Serial.SetInputLen(0); // 设置读取方式
m_Opened=m_Serail.SetPortOpen(1); // 打开指定的串口
return m_Opened;
}

  打开所需串口后,需要考虑串口通信的时机。在接收或发送数据过程中,可能需要监视并响应一些事件和错误,所以事件驱动是处理串行端口交互作用的一种非常有效的方法。使用 OnComm 事件和 CommEvent 属性捕捉并检查通讯事件和错误的值。发生通讯事件或错误时,将触发 OnComm 事件,CommEvent 属性的值将被改变,应用程序检查 CommEvent 属性值并作出相应的反应。在程序中用ClassWizard为CMSComm控件添加OnComm消息处理函数:

void CSampleDlg::OnComm()
{
......
switch(m_Serial.GetCommEvent())
{
case 2:
// 串行口数据接收,处理;
}
}



Top

回复人:kugou123(酷狗)(每天学VC,补充你我的维C) (