Debian下Apache 2 Mysql 5 Php 5安装配置

安装AMP
apt-get install apache2 mysql-server php5 php5-mysql5

配置 Apache2 默认站点
vi /etc/apache2/sites-enabled/000-default

加入一行
RedirectMatch ^/$ /apache2-default/

试验这一步不需要

修改 Apache2 主配置
vi /etc/apache2/apache2.conf

要改为:
Include module configuration:
Include /etc/apache2/mods-enabled/*.load Include /etc/apache2/mods-enabled/*.conf

还有:
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps

重新启动 Apache2
/etc/init.d/apache2 reload

现在要配置 PHP 5
vi /etc/php5/apache2/php.ini

Php5 默认已经不支持 Mysql,为了打开支持,要修改

extension=mysql.so

设置 Mysql 根密码
默认安装的 Mysql,其 root 用户没有密码,实在危险,我们为他加一个密码。
mysqladmin -uroot password ‘abc123’

其中abc123就是你的密码。

当然你也可以用下面的命令来设置密码:
mysql -u root mysql
mysql> update user set password=password(‘pass’) where user=’root’;

为将要架设的站点建一个库及其用户
mysql -u root -p mysql
mysql> create database drupal;
mysql> use drupal;
mysql> grant all on drupal.* to drupal_user@localhost;
mysql> use mysql;
Database changed
mysql> update user set password=password(‘pass’) where user = ‘drupal_user’;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql>q;
mysqladmin reload

TCP协议的拥塞控制策略及改进

摘 要:TCP是针对固定网络设计的一种传输协议,其错误控制机制是基于将所有丢包原因都归结于网络拥塞的假设。这种错误控制机制在有线网络上获得了很大的成功;但由于移动计算环境有着明显不同于有线网络环境的特点,如较高的位出错率、可用带宽小、衰减信道等,因此针对传统有线网络设计的TCP协议,其性能受到了很大影响。本文对目前移动计算环境下TCP协议的一些主要改进方案进行了综述,在对这些方案进行分类的基础上,对其优缺点进行了分析,并且对这些方案进行了比较。最后,提出了进一步研究的方向。 

关键词:TCP, 移动计算,无线网络,错误控制 

1. 引言 

  互联网最初源于美国国防部的ARPANET计划。在上世纪60年代中期,正是冷战的高峰,美国国防部希望有一个命令和控制网络能够在核战争的条件下幸免于难,而传统的电路交换的电话网络则显得太脆弱。国防部指定其下属的高级研究计划局(ARPA)解决这个问题,此后诞生的一个新型网络便称为ARPANET,其最大特点是采用无连接的端到端包交换服务。随后ARPANET开始与美国国家科学基金会(NSF)建成的NSFNET及加拿大、欧洲和太平洋地区的网络互联。到了80年代中期,人们开始把互联的网络称为互联网。 

  早在70年代中期,ARPA为了实现异种网络之间的互联与互通,推出了TCP/IP体系结构和协议规范。时至今日,TCP/IP协议也成为最流行的网际互联协议,并由单纯的TCP/IP协议发展成为一系列以IP为基础的TCP/IP协议簇。TCP/IP协议簇为互联网提供了基本的通信机制。 

  互联网采用的是无连接的端到端数据包交换,提供“尽力而为”(best effort)服务模型的设计机制。这种机制的最大优势是设计简单,可扩展性强。互联网在过去的十几年中经历了爆炸式的增长,这已经充分证明了这种设计机制的成功。然而这种优势并不是没有代价的,随着互联网用户数量的膨胀,网络的拥塞问题也越来越严重。例如由于队列溢出,互联网路由器会丢弃约10%的数据包。据统计,互联网上95%的数据流使用的是TCP/IP协议,因此,互联网上主要的互连协议TCP/IP的拥塞控制(congestion control)机制对控制网络拥塞具有特别重要的意义。拥塞控制是确保互联网鲁棒性(robustness)的关键因素,也是各种管理控制机制和应用(如多媒体通信中QoS控制、区分服务(differentiated services))的基础,因此关于互联网的拥塞控制问题一直是网络研究的一个热点。 

  TCP是目前Internet上使用最广泛的一种传输协议,根据MCI的统计,Internet上总字节数的95%及总数据包数的90%使用TCP协议传输[25]。TCP的目的是为了解决Internet的稳定性、异质性(接受端缓冲区大小、网络带宽及延迟等)、各流之间享用带宽的公平性、使用效率及拥塞控制等问题,从而为Internet提供可靠、健壮(robust)的端到端通讯。Internet近十年来的迅猛发展已证明TCP协议在设计上是成功的。 

  但是,TCP是为固定主机及有线网络设计的一种滑动窗口协议,它在位出错率(bit rate error,BER)很低、丢包的主要原因是网络拥塞的传统网络上的成功在移动计算环境下受到了巨大的挑战。移动计算带来的新问题主要是无线链路传输的可靠性、移动操作的特点以及对效率进行评估的性能尺度等。因此,对TCP协议的改进已经成为近几年网络通讯领域的一个研究热点。 

  本文第二部分对网络拥塞的基本概念进行了简要介绍;第三部分TCP的拥塞控制机制及有线网络环境下的改进进行了介绍;第四部分分析了TCP在移动计算环境下的缺点及其需要增加的功能;第五部分对增强移动环境下TCP的技术方案进行了分类介绍,分析了各自的优缺点,并对这些方案进行了比较。最后进行了总结,并提出了有待进一步研究的一些热点方向。 

2. 网络拥塞的基本概念 

2.1 拥塞的基本概念和互联网模型 

  当网络中存在过多的数据包时,网络的性能就会下降,这种现象称为拥塞。在网络发生拥塞时,会导致吞吐量下降,严重时会发生“拥塞崩溃”(congestion collapse)现象。一般来说,拥塞崩溃发生在网络负载的增加导致网络效率的降低的时候。最初观察到这种现象是在1986年10月,在这个过程中,LBL与UC Berkeley之间的吞吐量从32kbps下降到了40bps。Floyd总结出拥塞崩溃主要包括以下几种:传统的崩溃、未传送数据包导致的崩溃、由于数据包分段造成的崩溃、日益增长的控制信息流造成的崩溃等。 

 

图1:网络负载与吞吐量及响应时间的关系

  对于拥塞现象,我们可以进一步用图1来描述。当网络负载较小时,吞吐量基本上随着负载的增长而增长,呈线性关系,响应时间增长缓慢。当负载达到网络容量时,吞吐量呈现出缓慢增长,而响应时间急剧增加,这一点称为Knee。如果负载继续增加,路由器开始丢包,当负载超过一定量时,吞吐量开始急剧下降,这一点称为Cliff。拥塞控制机制实际上包含拥塞避免(congestion avoidance)和拥塞控制(congestion control)两种策略。前者的目的是使网络运行在Knee附近,避免拥塞的发生;而后者则是使得网络运行在Cliff的左侧区域。前者是一种“预防”措施,维持网络的高吞吐量、低延迟状态,避免进入拥塞;后者是一种“恢复”措施,使网络从拥塞中恢复过来,进入正常的运行状态。

 
  拥塞现象的发生和前面提到的互联网的设计机制有着密切关系,我们对这种设计机制作一个简单的归纳: 

  1. 数据包交换(packet switched)网络:与电路交换(circuit switched)网络相比,由于包交换网络对资源的利用是基于统计复用(statistical multiplexing)的,因此提高了资源的利用效率。但在基于统计复用的情况下,很难保证用户的服务质量(quality of service,QoS),并且很容易出现数据包“乱序”的现象,对乱序数据包的处理会大大增加拥塞控制的复杂性。 
     
  2. 无连接(connectionless)网络:互联网的节点之间在发送数据之前不需要建立连接,从而简化了网络的设计,网络的中间节点上无需保留和连接有关的状态信息。但无连接模型很难引入接纳控制(admission control),在用户需求大于网络资源时难以保证服务质量;此外,由于对数据发送源的追踪能力很差,给网络安全带来了隐患;无连接也是网络中出现乱序数据包的主要原因。 
     
  3. “尽力而为”的服务模型:不对网络中传输的数据提供服务质量保证。在这种服务模型下,所有的业务流被“一视同仁”地公平地竞争网络资源,路由器对所有的数据包都采用先来先处理(First Come First Service,FCFS)的工作方式,它尽最大努力将数据包包送达目的地。但对数据包传递的可靠性、延迟等不能提供任何保证。这很适合Email、Ftp、WWW等业务。但随着互联网的飞速发展,IP业务也得到了快速增长和多样化。特别是随着多媒体业务的兴起,计算机已经不是单纯的处理数据的工具。这对互联网也就相应地提出了更高的要求。对那些有带宽、延迟、延迟抖动等特殊要求的应用来说,现有的“尽力而为”服务显然是不够的。

2.2 拥塞产生的原因 

  拥塞发生的主要原因在于网络能够提供的资源不足以满足用户的需求,这些资源包括缓存空间、链路带宽容量和中间节点的处理能力。由于互联网的设计机制导致其缺乏“接纳控制”能力,因此在网络资源不足时不能限制用户数量,而只能靠降低服务质量来继续为用户服务,也就是“尽力而为”的服务。 

图2(a) 图2(b)


  拥塞虽然是由于网络资源的稀缺引起的,但单纯增加资源并不能避免拥塞的发生。例如增加缓存空间到一定程度时,只会加重拥塞,而不是减轻拥塞,这是因为当数据包经过长时间排队完成转发时,它们很可能早已超时,从而引起源端超时重发,而这些数据包还会继续传输到下一路由器,从而浪费网络资源,加重网络拥塞。事实上,缓存空间不足导致的丢包更多的是拥塞的“症状”而非原因。另外,增加链路带宽及提高处理能力也不能解决拥塞问题,例如,图2(a)中,四个节点之间的链路带宽都是19.2kbps,传输某个文件需要用时5分钟;当第一个节点和第二个节点之间的链路带宽提高到1Mbps时(如图2(b)所示),传输完该文件所需时间反而大大增加到了7个小时!这是因为在路由器R1中,数据包的到达速率远远大于转发的速率,从而导致大量数据包被丢弃,源端的发送速度被抑止,从而使得传输时间大大增加。即使所有链路具有同样大的带宽也不能解决拥塞问题,例如图3中, 

  所有链路带宽都是1Gbps,如果A和B同时向C以1Gbps的速率发送数据,则路由器R的输入速率为2Gbps,而输出速率只能为1Gbps,从而产生拥塞。 

  单纯地增加网络资源之所以不能解决拥塞问题,是因为拥塞本身是一个动态问题,它不可能只靠静态的方案来解决,而需要协议能够在网络出现拥塞时保护网络的正常运行。目前对互联网进行的拥塞控制主要是依靠在源端执行的基于窗口的TCP拥塞控制机制。网络本身对拥塞控制所起的作用较小,但近几年这方面的研究已经成了一个新的热点。

3. TCP拥塞控制及其改进

3.1 TCP拥塞控制机制介绍 

  基于源端的拥塞控制策略中,使用最为广泛的是TCP协议中的拥塞控制策略,TCP协议是目前互联网中使用最为广泛的传输协议。根据MCI的统计,互联网上总字节数的95%及总数据包数的90%使用TCP协议传输。 

  早期的TCP协议只有基于窗口的流控制(flow control)机制而没有拥塞控制机制,因而易导致网络拥塞。1988年Jacobson针对TCP在网络拥塞控制方面的不足,提出了“慢启动”(Slow Start)和“拥塞避免”(Congestion Avoidance)算法。1990年出现的TCP Reno版本增加了“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)算法,避免了网络拥塞不严重时采用“慢启动”算法而造成过度减小发送窗口尺寸的现象,这样TCP的拥塞控制就主要由这4个核心算法组成。 

  TCP协议的目的是为上层应用提供可靠的服务,其主要特征在于: 

  1. 确保各流享用带宽的公平性。 
     
  2. 动态发现当前可利用的带宽。 
     
  3. 拥塞避免及控制机制以避免拥塞崩溃(congestion collapse)的发生。

  标准版本的TCP使用基于窗口的的和式增加积式减小(Additive Increase Multiplicative Decrease,AIMD)方式控制发送速率,以保证稳定性及带宽享用的公平性。 

  错误控制机制是一个可靠传输协议的关键部分。它对协议的性能有很大的影响,包括吞吐量、能量消耗及可靠性。错误控制通常包括错误检测和错误恢复两部分。为了保证数据传输的可靠性,TCP要求接受端在正确接收到数据段(data segment)后向发送端发送一个确认包,确认包中包含了期望接收到的下一个数据段的序号。TCP发送端通过监测确认包的序号来检测是否发生了错误。如果发生超时或者发送端收到一定数量(通常是3个)重复的确认包,则认为传输过程中发生了错误,数据段被丢弃。由于有线网络的位出错率很低(例如光纤的BER通常只有10-12[22]),因此TCP假设丢包是由于网络拥塞引起的。在错误恢复处理过程中,TCP重传丢弃的数据段、减小发送端窗口大小并且在超时情况下重置超时时钟。 

  最初的TCP协议只有基于窗口的流控制(flow control)机制而没有拥塞控制机制。流控制作为接受方管理发送方发送数据的方式,用来防止接受方可用的数据缓存空间的溢出。流控制是一种局部控制机制,其参与者仅仅是发送方和接收方,它只考虑了接收端的接收能力,而没有考虑到网络的传输能力;而拥塞控制则注重于整体,其考虑的是整个网络的传输能力,是一种全局控制机制。正因为流控制的这种局限性,从而导致了拥塞崩溃现象的发生。 

  1986年初,Jacobson开发了现在在TCP应用中的拥塞控制机制。运行在端节点主机中的这些机制使得TCP连接在网络发生拥塞时回退(back off),也就是说TCP源端会对网络发出的拥塞指示(congestion notification)(例如丢包、重复的ACK等)作出响应。1988年Jacobson针对TCP在控制网络拥塞方面的不足,提出了“慢启动”(Slow Start)和“拥塞避免”(Congestion Avoidance)算法。1990年出现的TCP Reno版本增加了“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)算法,避免了网络拥塞不严重时采用“慢启动”算法而造成过大地减小发送窗口尺寸的现象,这样TCP的拥塞控制就由这4个核心部分组成。近几年又出现TCP的改进版本如NewReno和选择性应答(selective acknowledgement,SACK)等。正是这些拥塞控制机制防止了今天网络的拥塞崩溃。 

TCP拥塞控制四个主要过程(如图4(a)和(b)所示)简要介绍如下: 

  1. 慢启动阶段:早期开发的TCP应用在启动一个连接时会向网络中发送大量的数据包,这样很容易导致路由器缓存空间耗尽,网络发生拥塞,使得TCP连接的吞吐量急剧下降。由于TCP源端无法知道网络资源当前的利用状况,因此新建立的TCP连接不能一开始就发送大量数据,而只能逐步增加每次发送的数据量,以避免上述现象的发生。具体地说,当建立新的TCP连接时,拥塞窗口(congestion window,cwnd)初始化为一个数据包大小。源端按cwnd大小发送数据,每收到一个ACK确认,cwnd就增加一个数据包发送量,这样cwnd就将随着回路响应时间(Round Trip Time,RTT)呈指数增长,源端向网络发送的数据量将急剧增加。事实上,慢启动一点也不慢,要达到每RTT发送W个数据包所需时间仅为RTT×logW。由于在发生拥塞时,拥塞窗口会减半或降到1,因此慢启动确保了源端的发送速率最多是链路带宽的两倍。 
     
  2. 拥塞避免阶段:如果TCP源端发现超时或收到3个相同ACK副本时,即认为网络发生了拥塞(主要因为由传输引起的数据包损坏和丢失的概率很小(<<1%))。此时就进入拥塞避免阶段。慢启动阈值(ssthresh)被设置为当前拥塞窗口大小的一半;如果超时,拥塞窗口被置1。如果cwnd>ssthresh,TCP就执行拥塞避免算法,此时,cwnd在每次收到一个ACK时只增加1/cwnd个数据包,这样,在一个RTT内,cwnd将增加1,所以在拥塞避免阶段,cwnd不是呈指数增长,而是线性增长。 
     
  3. 快速重传和快速恢复阶段:快速重传是当TCP源端收到到三个相同的ACK副本时,即认为有数据包丢失,则源端重传丢失的数据包,而不必等待RTO超时。同时将ssthresh设置为当前cwnd值的一半,并且将cwnd减为原先的一半。快速恢复是基于“管道”模型(pipe model)的“数据包守恒”的原则(conservation of packets principle),即同一时刻在网络中传输的数据包数量是恒定的,只有当“旧”数据包离开网络后,才能发送“新”数据包进入网络。如果发送方收到一个重复的ACK,则认为已经有一个数据包离开了网络,于是将拥塞窗口加1。如果“数据包守恒”原则能够得到严格遵守,那么网络中将很少会发生拥塞;本质上,拥塞控制的目的就是找到违反该原则的地方并进行修正。

 

图4(a):慢启动和拥塞避免 图4(b):快速重传和快速恢复


   经过十多年的发展,目前TCP协议主要包含有四个版本:TCP Tahoe、TCP Reno、TCP NewReno和TCP SACK。TCP Tahoe是早期的TCP版本,它包括了3个最基本的拥塞控制算法-“慢启动”、“拥塞避免”和“快速重传”。TCP Reno在TCP Tahoe基础上增加了“快速恢复”算法。TCP NewReno对TCP Reno中的“快速恢复”算法进行了修正,它考虑了一个发送窗口内多个数据包丢失的情况。在Reno版中,发送端收到一个新的ACK后旧退出“快速恢复”阶段,而在NewReno版中,只有当所有的数据包都被确认后才退出“快速恢复”阶段。TCP SACK关注的也是一个窗口内多个数据包丢失的情况,它避免了之前版本的TCP重传一个窗口内所有数据包的情况,包括那些已经被接收端正确接收的数据包,而只是重传那些被丢弃的数据包。 

  另外,在1994年,L.S.Brakmo等提出了一种新的拥塞控制策略-TCP Vegas。由于RTT值与网络运行情况有密切关系,因此,TCP Vegas通过观察TCP连接中RTT值改变感知网络是否发生拥塞,从而控制拥塞窗口大小。如果发现RTT值变大,Vegas就认为网络正在发生拥塞,于是开始减小拥塞窗口;另一方面,如果RTT变小,Vegas就认为网络拥塞正在解除,于是再次增加拥塞窗口。这样,拥塞窗口在理想情况下就会稳定在一个合适的值上。TCP Vegas的最大优点在于拥塞机制的触发只与RTT的改变有关,而与包的具体传输时延无关。由于TCP Vegas不是利用丢包来判断网络可用带宽,而是以RTT的变化来判断,因此能更精确地预测网络的可利用带宽,其公平性、效率都较好。但TCP Vegas之所以未能在互联网上大规模使用,主要是因为使用TCP Vegas的流在带宽竞争能力方面不及未使用TCP Vegas的流,从而导致网络资源享用不公平,而不是算法本身的问题。 

3.2 拥塞控制的主要问题

  拥塞控制的问题主要集中在效率和公平性(fairness)上。网络资源的使用效率是指源端要求的总资源与网络所能提供的资源之间的关系。如果二者刚好相等或者很接近,那么这种算法的效率就是高的,否则都是效率不高的表现。 

  公平性是指在网络发生拥塞时各连接能公平地共享网络资源。产生公平性的根本原因在于拥塞发生必然导致数据包丢失,而数据包丢失会导致各数据流之间为争抢有限的网络资源发生竞争,竞争能力强的数据流将到更多网络资源,从而损害了其他流的利益。所以说没有拥塞,也就没有公平性问题。公平性问题表现在两方面:一是拥塞响应的TCP流和非拥塞响应的UDP流之间资源享用不公平;二是TCP流之间资源享用的不公平。前者主要是在发生拥塞时对拥塞指示作出的不同反应造成的。由于TCP流具有拥塞控制机制,在收到拥塞指示后,源端会主动降低发送速率;而UDP流由于没有端到端的拥塞控制机制,因此在收到拥塞指示后,UDP不会降低数据发送速率。结果在网络拥塞时,拥塞适应的TCP流得到的资源越来越少,非拥塞适应的UDP得到的资源越来越多,从而导致了网络资源分配的不公平。网络资源分配的不公平反过来会加重拥塞情况,甚至可能导致拥塞崩溃。对于第二个不公平性问题,研究表明,不同的窗口大小、RTT值及数据包的尺寸都会影响TCP流对带宽的占用。窗口较大,或者RTT较小,或者数据包较大的流往往能占用更多的带宽。 

3.3 有线网络中TCP拥塞控制机制的改进 

3.3.1 针对对不必要的超时重传和快速重传 

  我们知道,当前的TCP应用主要有两种重传机制-快速重传和超时重传。当TCP源端收到3个ACK副本时,就会触发快速重传机制,此时源端重传丢失的数据包并且将拥塞窗口大小减半。这种情况下,TCP流往往能够很快从丢包中恢复过来,重新回到原先的发送速率。但如果TCP源端没有收到3个ACK副本,例如拥塞窗口大小小于4,那么TCP源端则需要等待相当长时间,以便超时重发。这样,小窗口的TCP流就很容易陷入不必要的超时重发,使其吞吐量大大下降。 

  为了避免这种不必要的超时重传,一种改进办法就是只要TCP源端收到一个或者两个ACK副本,并且如果通告窗口允许,便继续发送新的数据包。这是因为只要收到ACK副本,就表明有数据包已经离开网络被接受端接收了,而此时源端还无法判断数据包是否被丢弃,根据“数据包守恒”原则,只要遵守拥塞窗口的规范,也即同时在网络中传送的数据包数量不能超过拥塞窗口的大小(以数据包为单位),源端就可以继续发送新的数据包。这种机制称为限制传输机制(Limited Transmit mechanism),这种机制对排序的数据包尤其有效。 

  限制传输机制可以使小窗口的TCP流很快从丢包中恢复过来。例如,对于拥塞窗口大小为4地TCP流,如果其第二个数据包丢失,那么按传统地做法需要等待超时重传。而在限制传输机制下,当源端收到对第三个数据包确认的ACK副本时(ACK中要求源端发送第二个数据包),继续发送新的数据包,最终源端可以收到三个ACK副本从而触发快速重传,从而减少了不必要的超时重传。 

3.3.2 针对乱序包和延迟包引起的重传 

  在不少情况下,TCP源端推断认为数据包被丢弃了,从而导致重传及拥塞窗口的减小,而实际上数据包并没有被丢弃。如果超时时钟过早地到时了(事实上数据包或者ACK并没有丢失,只要能够再等待一会儿并可收到ACK),源端便毫无必要地重发了数据包,更严重的是拥塞窗口的减小,而实际上并没有数据包被丢弃。类似地,如果由于数据包的乱序导致源端接收到3个ACK副本,便会导致快速重传,TCP源端也毫无必要地重发了数据包,并且减小了拥塞窗口。对于前者,虽然可以通过更为精确地调节超时时钟来减少不必要地超时重传,但要完全避免却是不可能的。同样,对于后者,虽然可以通过提高快速重传算法的性能来减少不必要的快速重传,但也不可能完全避免。 

  对于拥塞窗口较大的流,比如大小为W,不必要地减小拥塞窗口会导致其至少花费W/2 RTT时间恢复到原来拥塞窗口的大小,从而使其性能大大下降,特别是在数据包持续出现乱序或者对RTT的估算不很精确的情况下。持续乱序的数据包往往是由于路由的改变或者链路层重传受损的数据包引起的。 

  为了使得在出现不必要的超时重传和快速重传情况下,TCP性能能够更加健壮(robust),一种方法就是在出现这些情况时向TCP源端发送有关的信息。这个工作已经由D-SACK扩展(duplicate-SACK extension)完成了。D-SACK扩展允许TCP接受端在利用SACK选项来通报收到重复的数据包,从而TCP源端能够正确地推断出接受端是否收到了重复地数据包。因此,D-SACK扩展使得TCP源端在重发后一个RTT时间内正确地推断出重发是否必要。如果源端认为重发是不必要的,那么拥塞窗口减半也就没必要了,源端就会将拥塞窗口大小和慢启动阈值分别恢复到原来的值,这样拥塞窗口恢复到原来的大小只需1RTT时间而不是W/2 RTT时间了。 

3.3.3一种新的拥塞控制机制XCP 

  针对目前基于窗口的TCP拥塞控制机制的不足,最近MIT的D.Katabi、C.Rohrs和UC Berkeley的M.Handley共同提出了一种新的互联网拥塞控制机制XCP(eXplicit Control Protocol)。XCP源端维持有拥塞窗口cwnd和回路响应时间RTT并且通过数据包中的拥塞头(congestion header)将这两个值与路由器进行通信。当XCP连接刚刚建立时,与TCP一样,初始cwnd较小,XCP将其理想的发送速率填入到拥塞头中,如果链路带宽允许,则在一个RTT后就以次速率发送数据;如果链路带宽不足,则网络会给出一个发送速率,在一个RTT后源端就以此速率发送数据。 

  在随后的数据包传输过程中,根据数据流入速率和链路带宽之间的关系,路由器通知每个流是要增加还是减少拥塞窗口并将有关信息填入到拥塞头中。如果在后面的传输过程中,有路由器拥塞更加严重,则该路由器将拥塞头中的有关信息改写。最终该数据包将获得传输过程中的瓶颈链路信息,并将传送给接收端。接收端再将次信息写入到确认包中传送给源端,源端依此信息对拥塞窗口进行调整。通过将拥塞状态信息放入数据包中,XCP无需路由器维持每流状态信息,扩展性较好。 

  实验表明,与传统的TCP拥塞控制机制相比,XCP具有链路利用效率高、公平性好、可扩展性强、排队时延小的优点,并且路由器的开销也非常小。但XCP最终能否被标准化作为下一代互联网的传输协议我们将拭目以待。 

(未完待续)

VC字符串转换

 一、BSTR、LPSTR和LPWSTR 

   在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。

  那么什么是BSTR、LPSTR以及LPWSTR呢?

  BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的 API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种 场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。

  LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘’)结尾的8位 ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、 LPCTSTR等,它们的含义如图2所示。

  例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。

  一般地,还有下列类型定义:

#ifdef UNICODE
  typedef LPWSTR LPTSTR;
  typedef LPCWSTR LPCTSTR;
#else
  typedef LPSTR LPTSTR;
  typedef LPCSTR LPCTSTR;
#endif

二、CString、CStringA 和 CStringW

  Visual C++.NET中将CStringT作为ATL和MFC的共享的“一般”字符串类,它有CString、CStringA和CStringW三种形式,分 别操作不同字符类型的字符串。这些字符类型是TCHAR、char和wchar_t。TCHAR在Unicode平台中等同于WCHAR(16位 Unicode字符),在ANSI中等价于char。wchar_t通常定义为unsigned short。由于CString在MFC应用程序中经常用到,这里不再重复。

三、VARIANT、COleVariant 和_variant_t

  在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:

struct tagVARIANT {
  VARTYPE vt;
  union {
   short iVal; // VT_I2.
   long lVal; // VT_I4.
   float fltVal; // VT_R4.
   double dblVal; // VT_R8.
   DATE date; // VT_DATE.
   BSTR bstrVal; // VT_BSTR.
   …
   short * piVal; // VT_BYREF|VT_I2.
   long * plVal; // VT_BYREF|VT_I4.
   float * pfltVal; // VT_BYREF|VT_R4.
   double * pdblVal; // VT_BYREF|VT_R8.
   DATE * pdate; // VT_BYREF|VT_DATE.
   BSTR * pbstrVal; // VT_BYREF|VT_BSTR.
  };
};

  显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:

VARIANT va;
:: VariantInit(&va); // 初始化
int a = 2002;
va.vt = VT_I4; // 指明long数据类型
va.lVal = a; // 赋值

  为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:

  VariantInit —— 将变量初始化为VT_EMPTY;

  VariantClear —— 消除并初始化VARIANT;

  VariantChangeType —— 改变VARIANT的类型;

  VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。

  COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行 初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函 数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与 VARIANT类型转换中为我们提供极大的方便。例如下面的代码:

COleVariant v1("This is a test"); // 直接构造
COleVariant v2 = "This is a test";
// 结果是VT_BSTR类型,值为"This is a test"
COleVariant v3((long)2002);
COleVariant v4 = (long)2002;
// 结果是VT_I4类型,值为2002

  _variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:

  #include "comutil.h"

  #pragma comment( lib, "comsupp.lib" )

四、CComBSTR和_bstr_t

  CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:

CComBSTR bstr1;
bstr1 = "Bye"; // 直接赋值
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
CComBSTR bstr2(wcslen(str)); // 定义长度为5
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中
CComBSTR bstr3(5, OLESTR("Hello World"));
CComBSTR bstr4(5, "Hello World");
CComBSTR bstr5(OLESTR("Hey there"));
CComBSTR bstr6("Hey there");
CComBSTR bstr7(bstr6);
// 构造时复制,内容为"Hey there"

  _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。

五、BSTR、char*和CString转换

  (1) char*转换成CString

  若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:

char chArray[] = "This is a test";
char * p = "This is a test";

  或

LPSTR p = "This is a test";

  或在已定义Unicode应的用程序

TCHAR * p = _T("This is a test");

  或

LPTSTR p = _T("This is a test");
CString theString = chArray;
theString.Format(_T("%s"), chArray);
theString = p;

  (2) CString转换成char*

  若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:

  方法一,使用强制转换。例如:

CString theString( "This is a test" );
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;

  方法二,使用strcpy。例如:

CString theString( "This is a test" );
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
_tcscpy(lpsz, theString);

  需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。

  方法三,使用CString::GetBuffer。例如:

CString s(_T("This is a test "));
LPTSTR p = s.GetBuffer();
// 在这里添加使用p的代码
if(p != NULL) *p = _T(‘’);
s.ReleaseBuffer();
// 使用完后及时释放,以便能使用其它的CString成员函数

  (3) BSTR转换成char*

  方法一,使用ConvertBSTRToString。例如:

#include
#pragma comment(lib, "comsupp.lib")
int _tmain(int argc, _TCHAR* argv[]){
BSTR bstrText = ::SysAllocString(L"Test");
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
SysFreeString(bstrText); // 用完释放
delete[] lpszText2;
return 0;
}

  方法二,使用_bstr_t的赋值运算符重载。例如:

_bstr_t b = bstrText;
char* lpszText2 = b;

  (4) char*转换成BSTR

  方法一,使用SysAllocString等API函数。例如:

BSTR bstrText = ::SysAllocString(L"Test");
BSTR bstrText = ::SysAllocStringLen(L"Test",4);
BSTR bstrText = ::SysAllocStringByteLen("Test",4);

  方法二,使用COleVariant或_variant_t。例如:

//COleVariant strVar("This is a test");
_variant_t strVar("This is a test");
BSTR bstrText = strVar.bstrVal;

  方法三,使用_bstr_t,这是一种最简单的方法。例如:

BSTR bstrText = _bstr_t("This is a test");

  方法四,使用CComBSTR。例如:

BSTR bstrText = CComBSTR("This is a test");

  或

CComBSTR bstr("This is a test");
BSTR bstrText = bstr.m_str;

  方法五,使用ConvertStringToBSTR。例如:

char* lpszText = "Test";
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);

  (5) CString转换成BSTR

  通常是通过使用CStringT::AllocSysString来实现。例如:

CString str("This is a test");
BSTR bstrText = str.AllocSysString();

SysFreeString(bstrText); // 用完释放

  (6) BSTR转换成CString

  一般可按下列方法进行:

BSTR bstrText = ::SysAllocString(L"Test");
CStringA str;
str.Empty();
str = bstrText;

  或

CStringA str(bstrText);

  (7) ANSI、Unicode和宽字符之间的转换

  方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。

  方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:

TCHAR tstr[] = _T("this is a test");
wchar_t wszStr[] = L"This is a test";
String* str = S”This is a test”;

  方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:

  其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、 T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面 是一些示例代码:

LPTSTR tstr= CA2TEX<16>("this is a test");
LPCTSTR tcstr= CA2CT("this is a test");
wchar_t wszStr[] = L"This is a test";
char* chstr = CW2A(wszStr); 

C++字符串完全指引之一 —— Win32 字符编码

 

 

 

C++字符串完全指引之一 —— Win32 字符编码


原著:Michael Dunn

翻译:Chengjie Sun

原文出处:CodeProject:The Complete Guide to C++ Strings, Part I

 引言

毫无疑问,我们都看到过像 TCHAR, std::string, BSTR 等各种各样的字符串类型,还有那些以 _tcs 开头的奇怪的宏。你也许正在盯着显示器发愁。本指引将总结引进各种字符类型的目的,展示一些简单的用法,并告诉您在必要时,如何实现各种字符串类型之间的转换。
在第一部分,我们将介绍3种字符编码类型。了解各种编码模式的工作方式是很重要的事情。即使你已经知道一个字符串是一个字符数组,你也应该阅读本部分。一旦你了解了这些,你将对各种字符串类型之间的关系有一个清楚地了解。
在第二部分,我们将单独讲述string类,怎样使用它及实现他们相互之间的转换。

 字符基础 — ASCII, DBCS, Unicode

所有的 string 类都是以C-style字符串为基础的。C-style 字符串是字符数组。所以我们先介绍字符类型。这里有3种编码模式对应3种字符类型。第一种编码类型是单子节字符集(single-byte character set or SBCS)。在这种编码模式下,所有的字符都只用一个字节表示。ASCII是SBCS。一个字节表示的0用来标志SBCS字符串的结束。
第二种编码模式是多字节字符集(multi-byte character set or MBCS)。一个MBCS编码包含一些一个字节长的字符,而另一些字符大于一个字节的长度。用在Windows里的MBCS包含两种字符类型,单字节字符 (single-byte characters)和双字节字符(double-byte characters)。由于Windows里使用的多字节字符绝大部分是两个字节长,所以MBCS常被用DBCS代替。
在DBCS编码模式中,一些特定的值被保留用来表明他们是双字节字符的一部分。例如,在Shift-JIS编码中(一个常用的日文编码模式),0x81-0x9f之间和 0xe0-oxfc之间的值表示"这是一个双字节字符,下一个子节是这个字符的一部分。"这样的值被称作"leading bytes",他们都大于0x7f。跟随在一个leading byte子节后面的字节被称作"trail byte"。在DBCS中,trail byte可以是任意非0值。像SBCS一样,DBCS字符串的结束标志也是一个单字节表示的0。
第三种编码模式是Unicode。Unicode是一种所有的字符都使用两个字节编码的编码模式。Unicode字符有时也被称作宽字符,因为它比单 子节字符宽(使用了更多的存储空间)。注意,Unicode不能被看作MBCS。MBCS的独特之处在于它的字符使用不同长度的字节编码。Unicode 字符串使用两个字节表示的0作为它的结束标志。
单字节字符包含拉丁文字母表,accented characters及ASCII标准和DOS操作系统定义的图形字符。双字节字符被用来表示东亚及中东的语言。Unicode被用在COM及Windows NT操作系统内部。
你一定已经很熟悉单字节字符。当你使用char时,你处理的是单字节字符。双字节字符也用char类型来进行操作(这是我们将会看到的关于双子节字符 的很多奇怪的地方之一)。Unicode字符用wchar_t来表示。Unicode字符和字符串常量用前缀L来表示。例如:

wchar_t wch = L''1''; // 2 bytes, 0x0031
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters

 字符在内存中是怎样存储的

单字节字符串:每个字符占一个字节按顺序依次存储,最后以单字节表示的0结束。例如。"Bob"的存贮形式如下:

42 6F 62 00
B o b BOS

Unicode的存储形式,L"Bob"

42 00 6F 00 62 00 00 00
B o b BOS

使用两个字节表示的0来做结束标志。

一眼看上去,DBCS 字符串很像 SBCS 字符串,但是我们一会儿将看到 DBCS 字符串的微妙之处,它使得使用字符串操作函数和永字符指针遍历一个字符串时会产生预料之外的结果。字符串" " ("nihongo")在内存中的存储形式如下(LB和TB分别用来表示 leading byte 和 trail byte)

93 FA 96 7B 8C EA 00
LB TB LB TB LB TB EOS
EOS

值得注意的是,"ni"的值不能被解释成WORD型值0xfa93,而应该看作两个值93和fa以这种顺序被作为"ni"的编码。

 使用字符串处理函数

我们都已经见过C语言中的字符串函数,strcpy(), sprintf(), atoll()等。这些字符串只应该用来处理单字节字符字符串。标准库也提供了仅适用于Unicode类型字符串的函数,比如wcscpy(), swprintf(), wtol()等。
微软还在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str***()函数都有对应名字的DBCS版本_mbs***()。如果你料到可能会遇到DBCS 字符串(如果你的软件会被安装在使用DBCS编码的国家,如中国,日本等,你就可能会),你应该使用_mbs***()函数,因为他们也可以处理SBCS 字符串。(一个DBCS字符串也可能含有单字节字符,这就是为什么_mbs***()函数也能处理SBCS字符串的原因)
让我们来看一个典型的字符串来阐明为什么需要不同版本的字符串处理函数。我们还是使用前面的Unicode字符串 L"Bob":

42 00 6F 00 62 00 00 00
B o b BOS

  因为x86CPU是little-endian,值0x0042在内存中的存储形式是42 00。你能看出如果这个字符串被传给strlen()函数会出现什么问题吗?它将先看到第一个字节42,然后是00,而00是字符串结束的标志,于是 strlen()将会返回1。如果把"Bob"传给wcslen(),将会得出更坏的结果。wcslen()将会先看到0x6f42,然后是 0x0062,然后一直读到你的缓冲区的末尾,直到发现00 00结束标志或者引起了GPF。
到目前为止,我们已经讨论了str***()和wcs***()的用法及它们之间的区别。Str***()和_mbs**()之间的有区别区别呢?明 白他们之间的区别,对于采用正确的方法来遍历DBCS字符串是很重要的。下面,我们将先介绍字符串的遍历,然后回到str***()与_mbs***() 之间的区别这个问题上来。

 正确的遍历和索引字符串

因为我们中大多数人都是用着SBCS字符串成长的,所以我们在遍历字符串时,常常使用指针的++-和-操作。我们也使用数组下标的表示形式来操作字符 串中的字符。这两种方式是用于SBCS和Unicode字符串,因为它们中的字符有着相同的宽度,编译器能正确的返回我们需要的字符。
然而,当碰到DBCS字符串时,我们必须抛弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了这两条规则,你的程序就会存在DBCS有关的bugs。

  • 1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte;
  • 2.永远不要使用-操作进行后向遍历。

  我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册 表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:Program FilesMyCoolApp,那么你合成的文件名应该是C:Program FilesMyCoolAppconfig.bin。当你进行测试时,你发现程序运行正常。
现在,想象你合成文件名的代码可能是这样的:

bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
    char szConfigFilename[MAX_PATH];

    // Read install dir from registry... we''ll assume it succeeds.

    // Add on a backslash if it wasn''t present in the registry value.
    // First, get a pointer to the terminating zero.
    char* pLastChar = strchr ( szConfigFilename, '''' );

    // Now move it back one character.
    pLastChar--;

    if ( *pLastChar != '''' )
        strcat ( szConfigFilename, "" );

    // Add on the name of the config file.
    strcat ( szConfigFilename, "config.bin" );

    // If the caller''s buffer is big enough, return the filename.
    if ( strlen ( szConfigFilename ) >= nBuffSize )
        return false;
    else
        {
        strcpy ( pszName, szConfigFilename );
        return true;
        }
}      

  这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:。下面是这个名字在内存中的存储形式:

43 3A 5C 83 88 83 45 83 52 83 5C 00
      LB TB LB TB LB TB LB TB  
C : EOS

  当使用 GetConfigFileName() 检查尾部的””时,它寻找安装目录名中最后的非0字节,看它是等于””的,所以没有重新增加一个””。结果是代码返回了错误的文件名。
哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠””的值是0x5c。” ”的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。
正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明)

bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
    char szConfigFilename[MAX_PATH];

    // Read install dir from registry... we''ll assume it succeeds.

    // Add on a backslash if it wasn''t present in the registry value.
    // First, get a pointer to the terminating zero.
    char* pLastChar = _mbschr ( szConfigFilename, '''' );

    // Now move it back one double-byte character.
    pLastChar = CharPrev ( szConfigFilename, pLastChar );

    if ( *pLastChar != '''' )
        _mbscat ( szConfigFilename, "" );

    // Add on the name of the config file.
    _mbscat ( szConfigFilename, "config.bin" );

     // If the caller''s buffer is big enough, return the filename.
    if ( _mbslen ( szInstallDir ) >= nBuffSize )
        return false;
    else
        {
        _mbscpy ( pszName, szConfigFilename );
        return true;
        }
}

  上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0x5c。
让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了”:”。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于”:”的值。
与规则2相关的关于字符串索引的规则:

2a. 永远不要使用减法去得到一个字符串的索引。

违背这条规则的代码和违背规则2的代码很相似。例如,

char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];

这和向后移动一个指针是同样的效果。

 回到关于str***()和_mbs***()的区别

现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C: ", ””),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的””的指针。
关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节 字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。

 Win32 API中的MBCS和Unicode

两组 APIs:
尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受 Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和 SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。
当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如:

BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );

#ifdef UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif      

当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:

#define SetWindowText SetWindowTextA

  这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。)
所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowText ( hwnd, szNewText );

在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowTextW ( hwnd, szNewText );

  看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:

HWND hwnd = GetSomeWindowHandle();
#ifdef UNICODE
wchar_t szNewText[] = L"we love Bob!";
#else
char szNewText[] = "we love Bob!";
#endif
SetWindowText ( hwnd, szNewText );

你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR.

 使用TCHAR

TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:

#ifdef UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif

所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。

#ifdef UNICODE
#define _T(x) L##x
#else
#define _T(x) x
#endif

  ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。

TCHAR szNewText[] = _T("we love Bob!");

   像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例 如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS 还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。
不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。

 字符串和TCHAR typedefs

由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于 Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。

type Meaning in MBCS builds Meaning in Unicode builds
WCHAR wchar_t wchar_t
LPSTR zero-terminated string of char (char*) zero-terminated string of char (char*)
LPCSTR constant zero-terminated string of char (const char*) constant zero-terminated string of char (const char*)
LPWSTR zero-terminated Unicode string (wchar_t*) zero-terminated Unicode string (wchar_t*)
LPCWSTR constant zero-terminated Unicode string (const wchar_t*) constant zero-terminated Unicode string (const wchar_t*)
TCHAR char wchar_t
LPTSTR zero-terminated string of TCHAR (TCHAR*) zero-terminated string of TCHAR (TCHAR*)
LPCTSTR constant zero-terminated string of TCHAR (const TCHAR*) constant zero-terminated string of TCHAR (const TCHAR*)

 何时使用 TCHAR 和 Unicode

到现在,你可能会问,我们为什么要使用Unicode。我已经用了很多年的char。下列3种情况下,使用Unicode将会使你受益:

  • 1.你的程序只运行在Windows NT系统中。
  • 2. 你的程序需要处理超过MAX_PATH个字符长的文件名。
  • 3. 你的程序需要使用XP中引入的只有Unicode版本的API.

  Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。
只要你使用Unicode API,NT系统允许使用非常长的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一个优点是你的程序会自动处理用户输入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们。
最后,随着windows 9x产品的淡出,微软似乎正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。使用Unicode来build你的程序将会简化字符串的处理,你不必在MBCS和Unicdoe之间相互转换。
即使你现在不使用Unicode来build你的程序,你也应该使用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCS,而且如果将来你想用Unicode来build你的程序,你只需要改变一下预处理器中的设置就可以实现了。

 

 

 

 作者简介
Michael Dunn:居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在 UCLA 获得数学学士学位,随后在Symantec 公司做 QA 工程师,在 Norton AntiVirus 组工作。他自学了 Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus 的新界面。 
Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发了UltraBar,一个IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在洛杉矶和丹麦的 Odense 都设有办事处。
他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和 MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。

 

 

 

R程序语言推动数据挖掘进入黄金时代

 对一些人来说,R是字母表上的第18个字母;对另一些人,R级代表电影是限制级,有低俗画面或对白。但R同时也是一种程序语言,一个GNU项目,主要用于统计分析和绘图。《纽约时报》报道,R语言正越来越受到企业和学术界中数据分析专家的欢迎。在一定程度上,它变成了他们手中的通用语言,因为数据挖掘进入了黄金时代,无论是制定广告价格、更迅速的发现新药物,还是调整商业模型。像Google、Pfizer、Merck、美国银行、洲际酒店集团和壳牌等之类的企业都在使用它。R也受到了没有多少编程技能的科学家、统计学家和工程师的欢迎,因为发现它很容易使用。

System.map文件的作用

有关System.map文件的信息好象很缺乏。其实它一点也不神秘,并且在整个事情当中它并不象看上去那么得重要。但是由于缺乏必要的文档说明,使其显得比较神秘。它就象耳垂,我们每个人都有,但却不知道是干什么用的。本网页就是用来说明这个问题的。

注意,我并不会是百分之一百正确的。例如,一个系统很可能没有/proc文件系统支持,但是大多数系统肯定有。这里我假定你是“随大流的”,并有一个典型配置的系统。

某些有关内核出错(oops)的阐述来自于Alessandro Rubini的“Linux设备驱动程序” 一书,我是从其中学到大部分内核编程知识的。

什么是符号(Symbols)?

    在编程中,一个符号(symbol)是一个程序的创建块:它是一个变量名或一个函数名。 正如你自己编制的程序一样,内核具有各种符号也是不应该感到惊奇的。当然,区别在 于内核是一非常复杂的代码块,并且含有许多、许多的全局符号。

内核符号表(Kernel Symbol Table)是什么东西?

    内核并不使用符号名。它是通过变量或函数的地址(指针)来使用变量或函数的,而 不是使用size_t BytesRead,内核更喜欢使用(例如)c0343f20来引用 这个变量。

    而另一方面,人们并不喜欢象c0343f20这样的名字。我们跟喜欢使用象 size_t BytesRead这样的表示。通常,这并不会带来什么问题。内核主要 是用C语言写成的,所以在我们编程时编译器/连接程序允许我们使用符号名,并且使 内核在运行时使用地址表示。这样大家都满意了。

    然而,存在一种情况,此时我们需要知道一个符号的地址(或者一个地址对应的 符号)。这是通过符号表来做到的,与gdb能够从一个地址给出函数名(或者给出一个 函数名的地址)的情况很相似。符号表是所有符号及其对应地址的一个列表。这里是 一个符号表例子:

       c03441a0 B dmi_broken
       c03441a4 B is_sony_vaio_laptop
       c03441c0 b dmi_ident
       c0344200 b pci_bios_present
       c0344204 b pirq_table
       c0344208 b pirq_router
       c034420c b pirq_router_dev
       c0344220 b ascii_buffer
       c0344224 b ascii_buf_bytes
        

    你可以看出名称为dmi_broken的变量位于内核地址c03441a0处。

什么是System.map文件?

    有两个文件是用作符号表的:

       1. /proc/ksyms
       2. System.map

    这里,你现在可以知道System.map文件是干什么用的了。

    每当你编译一个新内核时,各种符号名的地址定会变化。

    /proc/ksyms 是一个 "proc文件" 并且是在内核启动时创建的。实际上 它不是一个真实的文件;它只是内核数据的简单表示形式,呈现出象一个磁盘文件似 的。如果你不相信我,那么就试试找出/proc/ksyms(注:我的centos 5.1是/proc/kallsyms)的文件大小来。因此, 对于当前运行的内核来说,它总是正确的..

    然而,System.map却是文件系统上的一个真实文件。当你编译一个新内核时,你原 来的System.map中的符号信息就不正确了。随着每次内核的编译,就会产生一个新的 System.map文件,并且需要用该文件取代原来的文件。

什么是一个Oops?

    在自己编制的程序中最常见的出错情况是什么?是段出错(segfault),信号11。

    Linux 内核中最常见的bug是什么?也是段出错。除此,正如你想象的那样,段出错的问题是非常复杂的,而且也是非常严重的。当内核引用了一个无效指针时,并不 称其为段出错 — 而被称为"oops"。一个oops表明内核存在一个bug,应该总是提出 报告并修正该bug。

    请注意,一个oops与一个段出错并不是一回事。你的程序并不能从段出错中恢复 过来,当出现一个oops时,并不意味着内核肯定处于不稳定的状态。Linux内核是非常 健壮的;一个oops可能仅杀死了当前进程,并使余下的内核处于一个良好的、稳定的 状态。

    一个oops并非是内核死循环(panic)。在内核调用了panic()函数后,内核就不能 继续运行了;此时系统就处于停顿状态并且必须重启。如果系统中关键部分遭到破坏 那么一个oops也可能会导致内核进入死循环(panic)。例如,设备驱动程序中 出现的oops就几乎不会导致系统进行死循环。

    当出现一个oops时,系统就会显示出用于调试问题的相关信息,比如所有CPU寄存器 中的内容以及页描述符表的位置等,尤其会象下面那样打印出EIP(指令指针)的内容:

       EIP: 0010:[<00000000>]
       Call Trace: []
       

一个Oops与System.map文件有什么关系呢?

    我想你也会认为EIP和Call Trace所给出的信息并不多,但是重要 的是,对于内核开发人员来说这些信息也是不够的。由于一个符号并没有固定的地址, c010b860可以指向任何地方。

    为了帮助我们使用oops含糊的输出,Linux使用了一个称为klogd(内核日志后台程序)的 后台程序,klogd会截取内核oops并且使用syslogd将其记录下来,并将某些象c010b860 的信息转换成我们可以识别和使用的信息。换句话说,klogd是一个内核消息记录器(logger), 它可以进行名字-地址之间的解析。一旦klogd开始转换内核消息,它就使用手头的记录器, 将整个系统的消息记录下来,通常是使用syslogd记录器。

    为了进行名字-地址解析,klogd就要用到System.map文件。我想你现在 知道一个oops与System.map的关系了。

    深入说明: 其实klogd会执行两类地址解析活动。

        * 静态转换,将使用System.map文件。
        * 动态转换,该方式用于可加载模块,不使用System.map,因此与本讨论没有关系,但我仍然对其加以简单说明。

        Klogd动态转换

        假设你加载了一个产生oops的内核模块。于是就会产生一个oops消息,klogd就会截获它,并发现该oops发生在d00cf810处。由于该地址 属于动态加载模块,因此在System.map文件中没有对应条目。klogd将会在其中寻找并会毫无所获,于是断定是一个可加载模块产生了oops。此 时klogd就会向内核查询该可加载模块输出的符号。即使该模块的编制者没有输出其符号,klogd也起码会知道是哪个模块产生了oops,这总比对一个 oops一无所知要好。

    还有其它的软件会使用System.map,我将在后面作一说明。

System.map应该位于什么地方?

    System.map应该位于使用它的软件能够寻找到的地方,也就是说,klogd会在什么地方寻找它。在系统启动时,如果没有以一个参数的形式为klogd给出System.map的位置,则klogd将会在三个地方搜寻System.map。依次为:

       1. /boot/System.map
       2. /System.map
       3. /usr/src/linux/System.map

    System.map 同样也含有版本信息,并且klogd能够智能化地搜索正确的map文件。例如,假设你正在运行内核2.4.18并且相应的map文件位于/boot /System.map。现在你在目录/usr/src/linux中编译一个新内核2.5.1。在编译期间,文件 /usr/src/linux/System.map就会被创建。当你启动该新内核时,klogd将首先查询 /boot/System.map,确认它不是启动内核正确的map文件,就会查询 /usr/src/linux/System.map, 确定该文件是启动内核正确的map文件并开始读取其中的符号信息。

    几个注意点:

        * 在2.5.x系列内核的某个版本,Linux内核会开始untar成linux-version,而非只是linux (请举手表决 — 有多少人一直等待着这样做?)。我不知道klogd是否已经修改为在/usr/src/linux-version/System.map中搜索。 TODO:查看klogd源代码。
        * 在线手册上对此也没有完整描述,请看:

             # strace -f /sbin/klogd | grep ‘System.map’
             31208 open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2
                 

          显然,不仅klogd在三个搜索目录中寻找正确版本的map文件,klogd也同样知道寻找名字为 "System.map" 后加"-内核版本",象 System.map-2.4.18. 这是klogd未公开的特性。

    有一些驱动程序将使用System.map来解析符号(因为它们与内核头连接而非glibc库等),如果没有System.map文件,它们将不能正确地 工作。这与一个模块由于内核版本不匹配而没有得到加载是两码事。模块加载是与内核版本有关,而与即使是同一版本内核其符号表也会变化的编译后内核无关。

还有谁使用了System.map?

    不要认为System.map文件仅对内核oops有用。尽管内核本身实际上不使用System.map,其它程序,象klogd,lsof,

       satan# strace lsof 2>&1 1> /dev/null | grep System
       readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23
       

    ps,

       satan# strace ps 2>&1 1> /dev/null | grep System
       open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6
       

    以及其它许多软件,象dosemu,需要有一个正确的System.map文件。

如果我没有一个好的System.map,会发生什么问题?

    假设你在同一台机器上有多个内核。则每个内核都需要一个独立的 System.map文件!如果所启动的内核没有对应的System.map文件,那么你将定期地看到这样一条信息:

        System.map does not match actual kernel (System.map与实际内核不匹配)

    不是一个致命错误,但是每当你执行ps ax时都会恼人地出现。有些软件,比如dosemu,可能不会正常工作。最后,当出现一个内核oops时,klogd或ksymoops的输出可能会不可靠。

我如何对上述情况进行补救?

    方法是将你所有的System.map文件放在目录/boot下,并使用内核版本号重新对它们进行命名。假设你有以下多个内核:

        * /boot/vmlinuz-2.2.14
        * /boot/vmlinuz-2.2.13

    那么,只需对应各内核版本对map文件进行改名,并放在/boot下,如:

       /boot/System.map-2.2.14
       /boot/System.map-2.2.13

    如果你有同一个内核的两个拷贝怎么办?例如:

        * /boot/vmlinuz-2.2.14
        * /boot/vmlinuz-2.2.14.nosound

    最佳解决方案将是所有软件能够查找下列文件:

       /boot/System.map-2.2.14
       /boot/System.map-2.2.14.nosound

    但是说实在的,我并不知道这是否是最佳情况。我曾经见到搜寻"System.map-kernelversion",但是对于搜 索"System.map-kernelversion.othertext"的情况呢? 我不太清楚。此时我所能做的就是利用这样一个事实:/usr/src/linux是标准map文件的搜索路径,所以你的map文件将放在:

        * /boot/System.map-2.2.14
        * /usr/src/linux/System.map (对于nosound版本)

    你也可以使用符号连接:

       System.map-2.2.14
       System.map-2.2.14.sound
       System.map -> System.map-2.2.14.sound

netlink相关问题


这两天在研究用netlink来做内核和用户空间的通讯,碰到了一些问题 ,在这里说一说。

  • 新的API定义问题。

参考资料中使用的内核版本都比较早,我使用的是2.6.26版本内核,几个主要的函数接口都已经发生了变化,而且没有相关文档。解决方法就是参考内核中netlink相关函数的定义及其他用到netlink的模块的实现方法:如net/netlink/genetlink.c中的genl_init()函数中就有用到netlink_kernel_create。

内核中netlink_kernel_create 的定义

netlink_kernel_create(struct net *net, int unit, unsigned int groups,
        void (*input)(struct sk_buff *skb),
        struct mutex *cb_mutex, struct module *module){…}

参数说明:

net:

>>指向struct net结构体的指针,内核中调用时,使用的是init_net变量,我在测试中使用init_net或NULL都可以正常运行。

unit

>>使用的协议编号,与之前版本定义一致

groups

>>用于多播的吧,没用的话置0就好了

input

>>指定内核中的接收函数,如: void nl_receive(struct sk_buff *__skb)。这个函数在原有版本中的定义为void (*input)(struct sock *sk,int  len);

cb_mutex

>>估计是用来做互斥的,内核调用时都传入NULL,我也跟了。

module

>>模块,调用时使用THIS_MODULE宏

调用示例:

nlfd = netlink_kernel_create(&init_net, NETLINK_TEST, 0, nl_receive, NULL, THIS_MODULE); 

  • 在使用过程中经常碰到莫名其妙死机问题。

解决方法:接收函数在获取skb时应使用get_skb()函数,即:

C++代码
  1. nl_receive(struct sk_buff * __skb)   
  2. {   
  3.         …   
  4.         struct sk_buff * skb;   
  5.         skb = get_skb(__skb);   
  6.         …   
  7.   
  8. }  

 

原因分析:skb_get()函数唯一的作用就是使skb->users加1,但这非常关键。因为内核使用kfree_skb()函数来回收skb的内存空间,kfree_skb()将skb->users减1并测试其值是否为零,若不为零则不会进行实际的释放操作。这样就避免在skb的使用过程中被意外释放掉而导致系统异常。内核中的skb_get()和kfree_skb()代码如下:

 

C++代码
  1. /**  
  2.  *  skb_get – reference buffer  
  3.  *  @skb: buffer to reference  
  4.  *  
  5.  *  Makes another reference to a socket buffer and returns a pointer  
  6.  *  to the buffer.  
  7.  */  
  8. static inline struct sk_buff *skb_get(struct sk_buff *skb)   
  9. {   
  10.     atomic_inc(&skb->users);   
  11.     return skb;   
  12. }   
  13.   
  14.   
  15. /**  
  16.  *  kfree_skb – free an sk_buff  
  17.  *  @skb: buffer to free  
  18.  *  
  19.  *  Drop a reference to the buffer and free it if the usage count has  
  20.  *  hit zero.  
  21.  */  
  22. void kfree_skb(struct sk_buff *skb)   
  23. {   
  24.     if (unlikely(!skb))   
  25.         return;   
  26.     if (likely(atomic_read(&skb->users) == 1))   
  27.         smp_rmb();   
  28.     else if (likely(!atomic_dec_and_test(&skb->users)))   
  29.         return;   
  30.     __kfree_skb(skb);   
  31. }  

还有问题:

1、为何内核在使用是没有用skb_get()来获取skb,是不是我的初始化有问题?
2、还有实现时自己定义了一个协议,但内核中有个genetlink似乎就是用来作自定义的内核/用户通讯,要研究研究!

 

 

参考资料

>> Why and How to Use Netlink Socket
 
>> 在 Linux 下用户空间与内核空间数据交换的方式
>>>> 第1部分: 内核启动参数、模块参数与sysfs、sysctl、系统调用和netlink
>>>> 第2部分: procfs、seq_file、debugfs和relayfs

>> Linux 系统内核空间与用户空间通信的实现与分析

 

附上完整的代码:

 …coming soon

数据挖掘拯救新闻调查

 传统的新闻业遭受了数字革命的冲击,主流新闻报道的垄断受到了新兴在线竞争对手的排挤。传统新闻机构的收入也大幅度下降。但是,曾经被数字革命削弱的深入报道将以新的形式再次回归。通过新的理论和专业培训,一种名为“计算机新闻业”的新兴职业将拯救传统的新闻调查杜克大学DeWitt Wallace媒体和民主中心负责人James Hamilton目前正致力于开发一套计算机工具,能增强记者和其他试图监督政府官员行为的公民的能力和效率。目标是开发出一种计算机算法,能对互联网上的海量数据库信息进行分类,向记者或公民记者提供其它方法无法获取的一系列可能的故事,简单而言就是从公共利益中挖掘有用数据。

SLIP





属于链路层协议

英文原义:Serial Line Internet Protocol

中文释义:串行线路网际协议

注解:该协议是Windows远程访问的一种旧工业标准,主要在Unix远程访问服务器中使用,现今仍然用于连接某些ISP。因为SLIP协议是面向低速串行线路的,可以用于专用线路,也可以用于拨号线路,Modem的传输速率在1200bps到19200bps。

应 用:在Windows中要设置SLIP协议,比如在Windows 98中,假设已经创建了“拨号连接”,右键单击该连接,选择“属性”。接着,在打开的属性窗口中,选择“服务器类型”选项卡,在“拨号网络服务器类型”中 选择“SLIP:Unix连接”。最后,单击“确定”按钮即可。

 

SLIP:串行线路IP

S L I P的全称是Serial Line IP。它是一种在串行线路上对IP数据报进行封装的简单形式,在RFC 1055[Romkey 1988]中有详细描述。SLIP适用于家庭中每台计算机几乎都有的RS-232串行端口和高速调制解调器接入Internet。

 

IPv6









 

2^32=4,294,967,296 43亿

 

2^1283.4x1038 3.4×1030亿

 

(3.4028236692093846346337460743177x1038)

 

现有的互联网是在IPv4协议的基础上运行。IPv6是下一版本的互联网协议,它的提出最初是因为随着互联网的迅速发展,IPv4定义的有限地址空间将被 耗尽,地址空间的不足必将影响互联网的进一步发展。为了扩大地址空间,拟通过IPv6重新定义地址空间。IPv4采用32位地址长度,只有大约43亿个地址,估计在20052010年间将被分配完毕,IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地 址,整个地球每平方米面积上可分配1000多个地址。在IPv6的设计过程中除了一劳永逸地解决地址短缺问题以外,还考虑了在IPv4中解决不好的其它问 题。IPv6的主要优势体现在以下几方面:扩大地址空间、提高网络的整体吞吐量、改善服务质量(QoS)、安全性有更好的保证、支持即插即用和移动性、更 好实现多播功能

 

显然,IPv6的优势能够对上述挑战直接或间接地作出贡献。其中最突出的是IPv6大大地扩大 了地址空间,恢复了原来因地址受限而失去的端到端连接功能,为互联网的普及与深化发展提供了基本条件。当然,IPv6并非十全十美、一劳永逸,不可能解决 所有问题。IPv6只能在发展中不断完善,也不可能在一夜之间发生,过渡需要时间和成本,但从长远看,IPv6有利于互联网的持续和长久发展。

简单的说就是,我们现在用的ip地址通常是192.168.1.1 40~255的数字组成,相对ipv6来说就是 192.168.x.x.x.x 6个数字组成,这样ip地址的资源就更大了~

什么是IPv4?

目前的全球因特网所采用的协议族是TCP/IP协议族。IPTCP/IP协议族中网络层的协议,是TCP/IP协议族的核心协议。目前IP协议的版本号是4(简称为IPv4),发展至今已经使用了30多年。

IPv4的地址位数为32位,也就是最多有232次方的电脑可以联到Internet上。

近十年来由于互联网的蓬勃发展,IP位址的需求量愈来愈大,使得IP位址的发放愈趋严格,各项资料显示全球IPv4位址可能在20052008年间全部发完。

什么是IPv6?

IPv6是下一版本的互联网协议,也可以说是下一代互联网的协议,它的提出最初是因为随着互联 网的迅速发展,IPv4定义的有限地址空间将被耗尽,地址空间的不足必将妨碍互联网的进一步发展。为了扩大地址空间,拟通过IPv6重新定义地址空间。 IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保守方法估算IPv6实际可分配的地址,整个地球的每平方米面积上仍可分配1000多个地 址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量 QoS)、安全性、多播、移动性、即插即用等。

IPv6IPv4相比有什么特点和优点?

更大的地址空间。IPv4中规定IP地址长度为32,即有2^32-1个地址;而IPv6IP地址的长度为128,即有2^128-1个地址。

更小的路由表。IPv6的地址分配一开始就遵循聚类(Aggregation)的原则,这使得路由器能在路由表中用一条记录(Entry)表示一片子网,大大减小了路由器中路由表的长度,提高了路由器转发数据包的速度。

增强的组播(Multicast)支持以及对流的支持(Flow-control)。这使得网络上的多媒体应用有了长足发展的机会,为服务质量(QoS)控制提供了良好的网络平台.

加入了对自动配置(Auto-configuration)的支持。这是对DHCP协议的改进和扩展,使得网络(尤其是局域网)的管理更加方便和快捷.

更高的安全性.在使用IPv6网络中用户可以对网络层的数据进行加密并对IP报文进行校验,这极大的增强了网络安全.