iperf-1.7.0 源代码分析

iperf-1.7.0 源代码分析
iperf-1.7.0 源代码分析

Iperf 源代码分析(一)

概述

前段时间学习Linux网络编程的有关知识,希望看一看这些网络编程的技术在实际的代码中是如何运用的。正巧实验室的项目中使用了开源网络性能测试软件Iperf,于是便初步分析了Iperf的源代码。现将分析代码的点滴收获写在我的Blog上,希望各位高人多多指教。

Iperf 是美国伊利诺斯大学(University of Illinois)开发的一种网络性能测试工具。可以用来测试网络节点间TCP或UDP连接的性能,包括带宽、延时抖动(jitter,适用于UDP)以及误码率(适用于UDP)等。关于Iperf的下载、安装以及详细的使用方法,可以参照

https://www.360docs.net/doc/5113988968.html,/Projects/Iperf/

Iperf是按照Server-Client范型工作的。在连接的一端使用以下命令启动Server:iperf -s

在连接的另一端启动Client:

iperf -c 1.1.1.1

此处假设Server端的IP地址为1.1.1.1。经过一段测试时间(默认为10秒),在Server端和Client端就会打印出网络连接的各种性能参数。Iperf作为一种功能完备的测试工具,还提供了各种选项,例如是建立TCP连接还是UDP 连接、测试时间、测试应传输的字节总数、测试模式等。而测试模式又分为单向测试(Normal T est)、同时双向测试(Dual T est)和交替双向测试(Tradeoff T est)。此外,用户可以指定测试的线程数。这些线程各自独立的完成测试,并可报告各自的以及汇总的统计数据。对于Iperf的详细使用方法以及命令行参数的意义,请参照上面的网页。

Iperf是用C++语言实现的,对设计中的各种结构和功能单元都按照面向对象的思想进行建模。它主要用到了Unix系统编程中两个主要的部分:Socket 网络编程和多线程编程。因此,通过分析Iperf的源代码,我们就可以在实际的例子中学习面向对象编程,Socket网络编程以及多线程编程的技术。同时,Iperf 实现的功能比较简单,代码并不复杂,而且功能比较单一。因此,Iperf是我们研究Unix 系统编程技术的一个很好的学习对象。

我所分析的是Iperf 1.7.0版的源代码。需要说明的是,Iperf的源代码中既包含了对应于Unix的部分,也包含了对应于Windows的部分。这两部分是通过条件编译的预处理语句分别编译的。我仅对Unix部分的代码进行分析。

Iperf提供的库

在开发Iperf的过程中,开发者把Socket编程和多线程编程中经常用到的一些系统调用封装成对象,屏蔽了底层函数的复杂接口,提供了模块化和面向对象的机制,也为我们提供了一些非常实用的编程工具,我们可以在实现自己的程序时复用这些类。由于这些类实现的源代码都比较简单,也为我们修改前人的代码实现自己的功能提供了方便。

这些类的定义与实现都在源代码文件夹的lib子文件夹下。主要包括以下一些对象:

SocketAddr类:封装了Socket接口中的网络地址结构(sockaddr_in等)以及各种地址转换的系统调用(gethostbyname、gethostbyaddr、inet_ntop等);Socket类:封装了socket文件描述符,以及socket、listen、connect等系统调用;

Mutex类以及Condition类:封装了POSIX标准中的mutex和condition(条件变量)线程同步机制;

Thread类:封装了POSIX标准中的多线程机制,提供了一种简单易用的线程模型;

Timestamp类:通过Unix系统调用gettimeofday实现了一个时间戳对象,提供了获得当前时间戳,计算两个时间戳之间的先后关系等方法。

此外,在lib文件夹中还包括一些Iperf的实现提供的实用工具函数,包括endian.c文件中的字节序转换函数、gnu_getopt文件中的命令行参数处理函数、snprintf文件中的字符串格式化函数、signal.c文件中的与信号处理有关的函数、string.c文件中的字符处理函数、tcp_window_size.c文件中的TCP窗口大小处理函数等。

接下来对lib文件夹中的一些比较重要的类和函数进行说明。

Iperf 源代码分析(二)

Thread类 Thread类封装了POSIX标准中的多线程机制,提供了一种简单易用的线程模型。Thread类是Iperf的实现中比较重要的类,使Iperf实现多线程并行操作的核心。

Thread类的定义在文件lib/Thread.hpp中,其实现位于lib/Thread.cpp 中。

[cpp]view plaincopyprint?

1/* ------------------------------------------------------------------- */ 2class Thread {

3public:

4 Thread( void );

5virtual ~Thread();

6

7// start or stop a thread executing

8void Start( void );

9void Stop( void );

10

11// run is the main loop for this thread

12// usually this is called by Start(), but may be called

13// directly for single-threaded applications.

14virtual void Run( void ) = 0;

15

16// wait for this or all threads to complete

17void Join( void );

18static void Joinall( void );

19

20void DeleteSelfAfterRun( void ) {

21 mDeleteSelf = true;

22 }

23

24// set a thread to be daemon, so joinall won't wait on it

25void SetDaemon( void );

26

27// returns the number of user (i.e. not daemon) threads

28static int NumUserThreads( void ) {

29return sNum;

30 }

31

32static nthread_t GetID( void );

33

34static bool EqualID( nthread_t inLeft, nthread_t inRight );

35

36static nthread_t ZeroID( void );

37

38protected:

39 nthread_t mTID;

40bool mDeleteSelf;

41

42// count of threads; used in joinall

43static int sNum;

44static Condition sNum_cond;

45

46private:

47// low level function which calls Run() for the object

48// this must be static in order to work with pthread_create

49

50static void* Run_Wrapper( void* paramPtr );

51

52}; // end class Thread

数据成员说明:

mTID纪录本线程的线程ID;

mDeleteSelf通过方法DeleteSelfAfterRun设置,用来说明是否在线程结束后释放属于该现程的变量;

sNum是一个静态变量,即为所有的Thread实例所共有的。该变量纪录所生成的线程的总数。Thread对象的Joinall方法通过该变量判断所有的Thread实例是

否执行结束;

sNum_cond是用来同步对sNum的操作的条件变量,也是一个静态变量。

主要函数成员说明:

Start方法:

[cpp]view plaincopyprint?

53/* -------------------------------------------------------------------

54 * Start the object's thread execution. Increments thread

55 * count, spawns new thread, and stores thread ID.

56 * ------------------------------------------------------------------- */

57

58void Thread::Start( void ) {

59if ( EqualID( mTID, ZeroID() ) ) {

60

61// increment thread count

62 sNum_cond.Lock();

63 sNum++;

64 sNum_cond.Unlock();

65

66 Thread* ptr = this;

67// pthreads -- spawn new thread

68int err = pthread_create( &mTID, NULL, Run_Wrapper, ptr );

69 FAIL( err != 0, "pthread_create" );

70 }

71} // end Start

首先通过Num++纪录一个新的线程的产生,之后通过pthread_create系统调用产生一个新的线程。新线程执行Run_Wrapper函数,以至向该Thread实例的ptr指针作为参数。原线程在判断pthread_create是否成功后退出Start函数。

Stop方法:

[cpp]view plaincopyprint?

72/* -------------------------------------------------------------------

73 * Stop the thread immediately. Decrements thread count and

74 * resets the thread ID.

75 * ------------------------------------------------------------------- */

76

77void Thread::Stop( void ) {

78if ( ! EqualID( mTID, ZeroID() ) ) {

79// decrement thread count

80 sNum_cond.Lock();

81 sNum--;

82 sNum_cond.Signal();

83 sNum_cond.Unlock();

84

85 nthread_t oldTID = mTID;

86 mTID = ZeroID();

87

88// exit thread

89// use exit() if called from within this thread

90// use cancel() if called from a different thread

91if ( EqualID( pthread_self(), oldTID ) ) {

92 pthread_exit( NULL );

93 } else {

94// Cray J90 doesn't have pthread_cancel; Iperf works okay without

95 pthread_cancel( oldTID );

96 }

97 }

98} // end Stop

首先通过sNum--纪录一个线程执行结束,并通过sNum_cond的Signal方法激活此时wait在 sNum_cond的线程(某个主线程会调用调用Joinall方法,等待全部线程的结束,在Joinall方法中通过sNum_cond.Wait() 等待在sNum_cond 条件变量上)。若结束的线程是自身,则调用pthread_exit函数结束,否则调用pthread_cancel函数。注意:传统的exit函数会结束整个进程(即该进程的全部线程)的运行,而pthread_exit函数仅结束该线程的运行。

Run_Wrapper方法:

[cpp]view plaincopyprint?

99/* -------------------------------------------------------------------

100 * Low level function which starts a new thread, called by

101 * Start(). The argument should be a pointer to a Thread object.

102 * Calls the virtual Run() function for that object.

103 * Upon completing, decrements thread count and resets thread ID.

104 * If the object is deallocated immediately after calling Start(),

105 * such as an object created on the stack that has since gone

106 * out-of-scope, this will obviously fail.

107 * [static]

108 * ------------------------------------------------------------------- */ 109

110void*

111Thread::Run_Wrapper( void* paramPtr ) {

112 assert( paramPtr != NULL );

113

114 Thread* objectPtr = (Thread*) paramPtr;

115

116// run (pure virtual function)

117 objectPtr->Run();

118

119#ifdef HAVE_POSIX_THREAD

120// detach Thread. If someone already joined it will not do anything

121// If noone has then it will free resources upon return from this

122// function (Run_Wrapper)

123 pthread_detach(objectPtr->mTID);

124#endif

125

126// set TID to zero, then delete it

127// the zero TID causes Stop() in the destructor not to do anything

128 objectPtr->mTID = ZeroID();

129

130if ( objectPtr->mDeleteSelf ) {

131 DELETE_PTR( objectPtr );

132 }

133

134// decrement thread count and send condition signal

135// do this after the object is destroyed, otherwise NT complains

136 sNum_cond.Lock();

137 sNum--;

138 sNum_cond.Signal();

139 sNum_cond.Unlock();

140

141return NULL;

142} // end run_wrapper

该方法是一个外包函数(wrapper),其主要功能是调用本实例的Run方法。实际上,Run_Wrapper是一个静态成员函数,是为所有的Thread实例所共有的,因此无法使用this指针。调用Run_Wrapper的Thread 是通过参数paramPtr指明具体的Thread实例的。在Run返回之后,通过pthread_detach

使该线程在运行结束以后可以释放资源。 Joinall函数是通过监视sNum的数值等待所有线程运行结束的,而并非通过pthread_join函数。在完成清理工作后,Run_Wrapper减少sNum的值,并通过sNum_cond.Signal函数通知在Joinall中等待的线程。

Run方法:

从Run方法的声明中知道,该方法是一个纯虚函数,因此Thread是一个抽

象基类,主要作用是为其派生类提供统一的对外接口。在Thread的派生类中,像Iperf中的Server,Client,Speader,Audience, Listener等类,都会为Run提供特定的实现,完成不同的功能,这是对面向对象设计多态特性的运用。Thread函数通过Run方法提供了一个通用的线程接口。

讨论:为什么要通过Run_Wrapper函数间接的调用Run函数?

首先,Thread的各派生类的完成的功能不同,但它们都是Thread的实例,都有一些相同的工作要做,如初始化和清理等。在Run_Wrapper中实现这些作为Thread实例所应有的相同功能,在Run函数中实现派生类各自不同的功能,是比较合理的设计。

更重要的是,由于要通过Pthread_create函数调用Run_Wrapper函数,因此 Run_Wrapper函数必须是一个静态成员,无法使用this指针区分运行

Run_Wrapper函数的具体实例,也就无法利用多态的特性。而这个问题可以通过把this指针作为Run_Wrapper函数的参数,并在Run_Wrapper中显示调用具有多态特性的Run函数来解决。

这种使用一个wrapper函数的技术为我们提供了一种将C++面向对象编程和传统的Unix系统调用相结合的思路。

Joinall方法和SetDaemon方法:

[cpp]view plaincopyprint?

143/* -------------------------------------------------------------------

144 * Wait for all thread object's execution to complete. Depends on the

145 * thread count being accurate and the threads sending a condition

146 * signal when they terminate.

147 * [static]

148 * ------------------------------------------------------------------- */ 149

150void Thread::Joinall( void ) {

151 sNum_cond.Lock();

152while ( sNum > 0 ) {

153 sNum_cond.Wait();

154 }

155 sNum_cond.Unlock();

156} // end Joinall

157

158/* -------------------------------------------------------------------

159 * set a thread to be daemon, so joinall won't wait on it

160 * this simply decrements the thread count that joinall uses,

161 * which is not a thorough solution, but works for the moment

162 * ------------------------------------------------------------------- */

163

164void Thread::SetDaemon( void ) {

165 sNum_cond.Lock();

166 sNum--;

167 sNum_cond.Signal();

168 sNum_cond.Unlock();

169}

由这两个方法的实现可见,Thread类是通过计数器sNum监视运行的线程数的。线程开始前(Start方法中的pthread_create)sNum加一,线程结束后(Stop 方法和Run_Wrapper方法末尾)sNum减一。Joinall通过条件变量类的实例sNum_cond的Wait方法等待sNum的值改变。而SetDaemon的目的是使调用线程不再受主线程Joinall的约束,只是简单的把sNum减一就可以了。

Iperf 源代码分析(三)

SocketAddr类

SocketAddr类定义在lib/SocketAddr.hpp中,实现在lib/SocketAddr.cpp 中。SocketAddr类封装了网络通信中经常用到的地址结构以及在这些结构上进行的操作。地址解析也是在SocketAddr的成员函数中完成的。

首先讨论一下Socket编程中用于表示网络地址的数据结构。

网络通信中的端点地址可以一般化的表示为(地址族,该族中的端点地址)。Socket接口系统中用来表示通用的网络地址的数据结构是sockaddr:

[cpp]view plainc opyprint?

1.struct sockaddr { /* struct to hold an address */

2. u_char sa_len /* total length */

3. u_short sa_family; /* type of address */

4.char sa_data[14]; /* value of address */

5.};

其中sa_family表示地址所属的地址族,TCP/IP协议的地址族用常量AF_INET 表示,而UNIX命名管道的地址族用常量AF_UNIX表示。

使用Socket的每个协议族都精确定义了自己的网络端点地址,并在头文件中提供了相应的结构声明。用来表示TCP/IP地址的数据结构如下:

[cpp]view plainc opyprint?

1.struct sockaddr_in {

2. u_char sin_len; /* total length */

3. u_short sin_family; /* type of address */

4. u_short sin_port; /* protocol port number */

5.struct in_addr sin_addr; /* IP address */

6.char sin_zero[8]; /* unused (set to zero) */

7.}

其中,sin_len和sin_family和sockaddr机构中的sa_len以及sa_family表示相同的数据。结构sockaddr_in将 sockaddr中通用的端点地址sa_data(14字节长)针对TCP/IP的地址结构作了细化,分为8bit的端口地址sin_port和 32bit 的IP地址。在Linux系统中,结构in_addr如下定义:

[html]view plainc opyprint?

1.struct in_addr {

2. unsigned long s_addr; /* IP address */

3.}

可见,结构in_addr仅有一个成员,表示一个32bit的数据,即IP地址。对于通用地址结构中的其余bit,填充0。

Socket接口中的很多函数都是为通用的网络地址结构设计的,例如,我们既可以用bind函数将一个socket绑定到一个TCP/IP的端口上,也可以用bind 函数将一个socket绑定到一个UNIX命名管道上。因此,像

bind, connect, recvfrom, sendto等函数都要求一个sockaddr结构作为指名地址的参数。这时,我们就要使用强制类型转换把表示IP地址的sockaddr_in 结构转换为sockaddr结构进行函数调用。但实际上,sockaddr和sockaddr_in 结构表示的均是同一地址。它们在内存中对应的区域是重合的。

SockedAddr类的功能比较单一,成员变量mAddress就是SocketAddr的实例所表示的TCP/IP端口地址(包括IP地址和 TCP/UDP端口号)。类声明mAddress 为iperf_sockaddr类型的变量,而在文件/lib/headers.h中,有

[html]view plainc opyprint?

1.typedef sockaddr_in iperf_sockaddr

因此,iperf_sockaddr实际上就是sockaddr_in类型的变量。SockedAddr

的成员函数都是对mAddress进行读取或修改的操作的。比较复杂的成员函数是setHostname,它完成了地址解析的过程,源代码如下(已将不相关部分删除)。

[cpp]view plainc opyprint?

1./* -------------------------------------------------------------------

2. * Resolve the hostname address and fill it in.

3. * ------------------------------------------------------------------- */

4.

5.void SocketAddr::setHostname( const char* inHostname ) {

6.

7. assert( inHostname != NULL );

8.

9. mIsIPv6 = false;

10. mAddress.sin_family = AF_INET;

11.// first try just converting dotted decimal

12.// on Windows gethostbyname doesn't understand dotted decimal

13.int rc = inet_pton( AF_INET, inHostname, (unsigned char*)&(mAddress.sin_

addr) );

14.if ( rc == 0 ) {

15.struct hostent *hostP = gethostbyname( inHostname );

16.if ( hostP == NULL ) {

17./* this is the same as herror() but works on more systems */

18.const char* format;

19.switch ( h_errno ) {

20.case HOST_NOT_FOUND:

21. format = "%s: Unknown host/n";

22.break;

23.case NO_ADDRESS:

24. format = "%s: No address associated with name/n";

25.break;

26.case NO_RECOVERY:

27. format = "%s: Unknown server error/n";

28.break;

29.case TRY_AGAIN:

30. format = "%s: Host name lookup failure/n";

31.break;

32.

33.default:

34. format = "%s: Unknown resolver error/n";

35.break;

36. }

37. fprintf( stderr, format, inHostname );

Iperf 源代码分析(四)

Socket 类

Socket的定义和实现分别在文件Socket.hpp和 Socket.cpp中。它的主要功能是封装了socket文件描述符、此socket对应的端口号,以及socket接口中的listen, accept, connect和close等函数,为用户提供了一个简单易用而又统一的接口。同时作为其他派生类的基类。

Socket类的定义如下:

[cpp]view plainc opyprint?

1. * -------------------------------------------------------------------

2. * A parent class to hold socket information. Has wrappers around

3. * the common listen, accept, connect, and close functions.

4. * ------------------------------------------------------------------- */

5.

6.#ifndef SOCKET_H

7.#define SOCKET_H

8.

9.#include "headers.h"

10.#include "SocketAddr.hpp"

11.

12./* ------------------------------------------------------------------- */

13.class Socket {

14.public:

15.// stores server port and TCP/UDP mode

16. Socket( unsigned short inPort, bool inUDP = false );

17.

18.// destructor

19.virtual ~Socket();

20.

21.protected:

22.// get local address

23. SocketAddr getLocalAddress( void );

24.

25.// get remote address

26. SocketAddr getRemoteAddress( void );

27.

28.// server bind and listen

29.void Listen( const char *inLocalhost = NULL, bool isIPv6 = false );

30.

31.// server accept

32.int Accept( void );

33.

34.// client connect

35.void Connect( const char *inHostname, const char *inLocalhost = NULL );

36.

37.// close the socket

38.void Close( void );

39.

40.// to put setsockopt calls before the listen() and connect() calls

41.virtual void SetSocketOptions( void ) {

42. }

43.

44.// join the multicast group

45.void McastJoin( SocketAddr &inAddr );

46.

47.// set the multicast ttl

48.void McastSetTTL( int val, SocketAddr &inAddr );

49.

50.int mSock; // socket file descriptor (sockfd)

51. unsigned short mPort; // port to listen to

52.bool mUDP; // true for UDP, false for TCP

53.

54.}; // end class Socket

55.

56.#endif // SOCKET_H

Socket类主要提供了四个函数:Listen,Accept,Connect和Close。getLocalAddress和 GetREmoteAddress的作用分别是获得socket本端的地址和对端的地址,两个函数均返回一个SocketAddr实例。 SetSocketOptions的作用是设置socket的属性,它是一个虚函数,因此不同的socket的派生类在实现此函数时会执行不同的操作。下面重点看一下Socket类的几个函数的实现。

Listen 函数

[cpp]view plainc opyprint?

1./* -------------------------------------------------------------------

2. * Setup a socket listening on a port.

3. * For TCP, this calls bind() and listen().

4. * For UDP, this just calls bind().

5. * If inLocalhost is not null, bind to that address rather than the

6. * wildcard server address, specifying what incoming interface to

7. * accept connections on.

8. * ------------------------------------------------------------------- */

9.

10.void Socket::Listen( const char *inLocalhost, bool isIPv6 ) {

11.int rc;

12.

13. SocketAddr serverAddr( inLocalhost, mPort, isIPv6 );

14.

15.// create an internet TCP socket

16.int type = (mUDP ? SOCK_DGRAM : SOCK_STREAM);

17.int domain = (serverAddr.isIPv6() ?

18.#ifdef IPV6

19. AF_INET6

20.#else

21. AF_INET

22.#endif

23. : AF_INET);

24.

25. mSock = socket( domain, type, 0 );

26. FAIL_errno( mSock == INVALID_SOCKET, "socket" );

27.

28. SetSocketOptions();

29.

30.// reuse the address, so we can run if a former server was killed off

31.int boolean = 1;

32. Socklen_t len = sizeof(boolean);

33.// this (char*) cast is for old headers that don't use (void*)

34. setsockopt( mSock, SOL_SOCKET, SO_REUSEADDR, (char*) &boolean, len );

35.

36.// bind socket to server address

37. rc = bind( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_s

ockaddr());

38. FAIL_errno( rc == SOCKET_ERROR, "bind" );

39.// listen for connections (TCP only).

40.// default backlog traditionally 5

41.if ( ! mUDP ) {

42. rc = listen( mSock, 5 );

43. FAIL_errno( rc == SOCKET_ERROR, "listen" );

44. }

45.} // end Listen

首先构造一个包含本地服务器地址结构的SocketAddr实例,inLocalhost 是本地IP地址(点分十进制字符串或URL,后者在创建 SocketAddr实例是完成

地址解析),mPort是Socket构造函数中设置的端口。再通过socket系统调用创建一个socket。 SetSocketOptions方法设置此socket的属性。因为SetSocketOptions是虚函数,在Socket类的实现中是一个空函数,而不同的Socket的派生类在覆盖(overwrite)该函数执行的操作是不同的,这是多态特性的应用。此后设置socket的可重用(reuse)属性,使服务器在重启后可以重用以前的地址和端口。此时该socket还没有绑定到某个网络端点(IP地址、端口对)上,bind系统调用完成此功能。最后,如果该socket用于一个TCP连接,则调用listen函数,一来向系统说明可以接受到socket绑定端口上的连接请求,二来设定请求等待队列的长度为5。

Socket的Listen方法将地址解析(地址结构生成)、socket、bind和listen 等系统调用组合为一个函数。在应用时,调用一个Listen方法就可以完成Server 端socket初始化的所有工作。

Accept函数

Accept函数是Server完成socket初始化,等待连接请求时调用的函数。代码如下:

[cpp]view plainc opyprint?

1./* -------------------------------------------------------------------

2. * After Listen() has setup mSock, this will block

3. * until a new connection arrives. Handles interupted accepts.

4. * Returns the newly connected socket.

5. * ------------------------------------------------------------------- */

6.

7.int Socket::Accept( void ) {

8. iperf_sockaddr clientAddr;

9. Socklen_t addrLen;

10.int connectedSock;

11.

12.while ( true ) {

13.// accept a connection

14. addrLen = sizeof( clientAddr );

15. connectedSock = accept( mSock, (struct sockaddr*) &clientAddr, &addr

Len );

16.

17.// handle accept being interupted

18.if ( connectedSock == INVALID_SOCKET && errno == EINTR ) {

19.continue;

20. }

21.

22.return connectedSock;

23. }

24.

25.} // end Accept

Accept函数为accept系统调用增添了在中断后自动重启的功能。Server 线程在执行accept函数是后被阻塞,直到有请求到达,或是接收到某个信号。若是后面一种情况,accept会返回INVALID_SOCKET并置errno为EINTR。Accept 方法检查这种情况,并重新调用accept函数。

Connect函数

Connect函数Client端调用的函数,其作用是连接指定的Server。代码如下:[cpp]view plainc opyprint?

1./* -------------------------------------------------------------------

2. * Setup a socket connected to a server.

3. * If inLocalhost is not null, bind to that address, specifying

4. * which outgoing interface to use.

5. * ------------------------------------------------------------------- */

6.

7.void Socket::Connect( const char *inHostname, const char *inLocalhost ) {

8.int rc;

9. SocketAddr serverAddr( inHostname, mPort );

10.

11. assert( inHostname != NULL );

12.

13.// create an internet socket

14.int type = (mUDP ? SOCK_DGRAM : SOCK_STREAM);

15.

16.int domain = (serverAddr.isIPv6() ?

17.#ifdef IPV6

18. AF_INET6

19.#else

20. AF_INET

21.#endif

22. : AF_INET);

23.

24. mSock = socket( domain, type, 0 );

25. FAIL_errno( mSock == INVALID_SOCKET, "socket" );

26.

27. SetSocketOptions();

28.

29.

30.if ( inLocalhost != NULL ) {

31. SocketAddr localAddr( inLocalhost );

32.// bind socket to local address

33. rc = bind( mSock, localAddr.get_sockaddr(), localAddr.get_sizeof_soc

kaddr());

34. FAIL_errno( rc == SOCKET_ERROR, "bind" );

35. }

36.

37.// connect socket

38. rc = connect( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_so

ckaddr());

39. FAIL_errno( rc == SOCKET_ERROR, "connect" );

40.

41.} // end Connect

首先构造一个SocketAddr实例保存Server端的地址(IP地址,端口对),同时按需完成地址解析。socket系统调用生成 socket接口。虚函数SetSocketOptions利用多态特性使不同的派生类按需要设置socket属性。如果传入的inLocalhost参数不是空指针,说明调用者希望指定某个本地接口作为连接的本地端点,此时通过bind系统调用把该socket绑定到这个接口对应的IP 地址上。最后调用 connect函数完成与远端Server的连接。

讨论: TCP和UDP在调用connect函数是的操作有何不同?

对于TCP连接,调用 connect函数会发起建立TCP连接的三次握手

(3-way handshaking)过程。当connect调用返回时,此过程已经完成,连接已经建立。因为TCP连接使用字符流模型,因此在建立好的连接上交换数据时,就好像从一个字符流中读取,向另一个字符流中写入一样。

而UDP是无连接的协议,使用数据报而不是连接的模型,因此调用connect 函数并不发起连接的过程,也没有任何数据向Server发送,而只是通知操作系统,发往该地址和端口的数据报都送到这个socket连接上来,也就是说,把这个(地址、端口)对和该socket关联起来。UDP在IP协议的基础上提供了多路访问(multiplex)的服务,UDP的connect系统调用对这种多路提供了socket 接口与对端地址间的对应关系。在UDP连接中,connect提功的这种功能是很有用的。例如,Server可以在接收到一个 Client的数据报后,分配一个线程执行connect函数与该Client绑定,处理与该client的后继交互,其他的线程继续在原来的UDP端口上监听新的请求.因为在Client端和Server端都执行了

connect函数,所以一个Server与多个Client间的连接不会发生混乱。在 Iperf 对UDP的处理中,就使用了这种技巧。

以上已经简要讨论了Iperf提供的库中几个比较重要的类的定义与实现。接下来将开始研究Iperf本身的实现细节。

Iperf 源代码分析(五)

Iperf的工作机制

Iperf是基于Server-Client模式实现的。在测量网络参数时,Iperf区分听者和说者两种角色。说者向听着发送一定量的数据,由听者统计并记录带宽、时延抖动等参数。说者的数据全部发送完成后,听者通过向说者回送一个数据包,将测量数据告知说者。这样,在听者和说者两边都可以显示记录的数据。如果网络过于拥塞或误码率较高,当听者回送的数据包无法被说者收到时,说者就无法显示完整的测量数据,而只能报告本地记录的部分网络参数,发送的数据量、发送时间、发送带宽等,像延时抖动等参数在说者一侧则无法获得。

Iperf提供了三种测量模式:normal, tradeoff, dualtest。对于每一种模式,用户都可以通过 -P 选项指定同时测量的并行线程数。以下的讨论假设用户设定的并行线程数为P个。

在normal模式下,Client生成P个说者线程,并行向Server发送数据。Server每接收到一个说者的数据,就生成一个听者线程,负责与该说者间的通信。Client有P个并行的说者线程,而Server端有P个并行的听者线程(针对这一Client),两者之间共有P个连接同时收发数据。测量结束后,Server端的每个听者向自己对应的说者回送测得的网络参数。

在tradeoff模式下,首先进行normal模式下的测量过程。然后Server和Client互换角色。Server生成P个说者,同时向Client发送数据。Client对应每个说者生成一个听者接收数据并测量参数。最后有Client 端的听者向Server端的说者回馈测量结果。这样就可以测量两个方向上的网络参数了。

dualtest模式同样可以测量两个方向上的网络参数,与tradeoff模式的不同在于,在dualtest模式下,由Server到Client方向上的测量与由Client到Server方向上的测量是同时进行的。Client生成P个说者和P个听者,说者向Server端发送数据,听者等待接收Server端的说者发来的数据。Server端也进行相同的操作。在Server端和Client端之间同时存在2P个网络连接,其中有P个连接的数据由Client流向Server,另外P个连接的数据由Server流向Client。因此,dualtest模式需要的测量时间是tradeoff 模式的一半。

在三种模式下,除了P个听者或说者进程,在Server和Client两侧均存在一个监控线程(monitor thread)。监控线程的作用包括:

生成说者或听者线程;

同步所有说者或听者的动作(开始发送、结束发送等);

计算并报告说有说者或听者的累计测量数据。

在监控线程的控制下,所有P个线程间就可以实现同步和信息共享。说者线程或听者线程向一个公共的数据区写入测量数据(此数据区位于实现监控线程的对象中),由监控线程读取并处理。通过互斥锁(mutex)实现对该数据区的同步访问。

Server可以同时接收来自不同Client的连接,这些连接是通过Client的IP地址标识的。Server将所有Client的连接信息组织成一个单向链表,每个Client对应链表中的一项,该项包含该Client的地址结构(sockaddr)以及实现与该Client对应的监控线程的对象(我们称它为监控对象),所有与此Client相关的听者对象和说者对象都是由该监控线程生成的。

Iperf 源代码分析(六)

Iperf中主要的类

Iperf的实现中主要的类及其相互间的派生关系如下图所示。

PerfSocket类

PerfSocket类以Socket为基类派生而来。该类实现了Iperf用于通信的大多数功能,像发送UDP包(SendUDP),接收UDP包(Recv_UDP),发送TCP数据(Send_TCP),接收TCP数据(Recv_TCP)以及传输初始化、报告网络参数、发送/回复结束包等。

Notify类

Notify类是Iperf中的另一个主要基类。它实现了对多个并行线程的监控机制。Notify类提供了对所监控线程的开始动作、结束动作进行同步的方法。这些方法主要是通过条件变量(condition)机制实现的。同时,Notify类的实例还含有供被监控的线程进行排他(exclusively)访问的数据区。一般是被监控的线程向该数据区中写入数据,由Notify类(或其派生类)的实例读取。

Listener类

Listener类以PerfSocket类和Thread类为基类,属多重继承。因为是Thread类的派生类,因此一个Listener实例就是一个执行线程,又因为Listener类是PerfSocket类的派生类,因此它也具有收发socket 数据的能力。在服务器端,在服务器程序启动后Listener就存在并开始工作。在客户端,如果用户指定进行双向测试(tradeoff或dualtest模式),也会生成Listener对象。Listener它实现的主要功能是在设定的端口上监听连接请求,接收到请求后,若请求来自一个新的客户,Listener生成一个Audience(听者监控线程,也可称为听者监控对象,见下文)实例来负责继续处理与该请求对应的后续操作,如果在请求中指明了要进行tradeoff或dualtest模式的测量,Listener就生成一个Speaker(说者监控线程,也可称为说者监控对象,见下文)实例来负责发起到客户的反向连接;如果请求来自一个已经存在的客户,Listener通过该客户对应的Audience实例的方法使该Audience实例生成一个听者线程进行处理该连接(这发生在多线程并行测量的情况下)。完成这些工作后,Listener线程返回继续监听。

Audience类

Audience类以Thread类和Notify为基类,属多重继承。一个Audience实例对应一个执行线程且具有监控其他线程的功能。Audience实例(线程)的主要功能是作为听者线程的监控线程。它首先生成一个听者线程(听者线程通过Server类实现,见下文),之后负责维护并报告所用听者线程测量结果的累计值,在对听者线程的结束事件同步之后,Audience线程退出。Listener线程在接收到一个已有客户的连接请求时,也会通过Audience实例生成新的Server实例。在服务器端,一个客户对应一个Audience实例,所有的Aucience实例组织成一个链表。

Speaker类

Speaker类同Audience类一样,也以Thread类和Notify类为基类。它实现了说者线程的监控线程。它首先生成若干说者线程(听者线程通过Client类实现,见下文),对所有说者线程的开始事件进行同步,之后负责维护并报告所用说者线程测量结果的累计值,在对说者线程的结束事件同步之后,Speaker线程退出。此外,在Speaker线程看是运行时,还会根据是否进行双向测试生成Listener生成实例,具体情况会在下文中介绍。在客户端程序开始运行后,Speaker线程就开始工作了。当服务器端收到双向测试的请求时,也会生成Speaker实例(线程)。

Server类

Server类以PerfSocket类和Thread类为基类,实现了听者进程。它接收对端的说者对象发来的数据,记录数据量、延迟抖动等网络参数,打印测试结果,修改听者监控进程(Audience实例)的累计数据区,并在收到说者的最后一个数据包时将测试结果回送给说者进程。

Client类

Client类以PerfSocket类和Thread类为基类,实现了说者进程。它不断的向对端的听者进程发送数据,直到发送了指定的数据量或达到了指定的发送时间。此后Server实例发送最后一个标记了测量结束标志的数据包,并等待来自对端听者的测量结果。在发送数据的同时,Server进程周期性的更新说者监控线程(Speaker实例)的累计数据区,并打印测试结果。

综上,Client类与Server类对应,分别实现了说者进程和听者进程。Speaker类和Audience类对应,分别实现了说者监控线程和听者监控线程。Listener类是监听线程。所有的Client实例是由Speaker实例生成的。而Server实例是由Listener实例触发Audience生成的。

Iperf 源代码分析(七)

下面以程序的执行为主线,简要分析一下Iperf源代码的实现。

main函数

main函数在文件main.cpp中定义,它是程序的入口点。

/* -------------------------------------------------------------------

* global variables

* ------------------------------------------------------------------- */

#define GLOBAL()

Condition gQuit_cond;

/* -------------------------------------------------------------------

* sets up signal handlers

* parses settings from environment and command line

* starts up server or client thread

* waits for all threads to complete

* ------------------------------------------------------------------- */

int main( int argc, char **argv ) {

Listener *theListener = NULL;

Speaker *theSpeaker = NULL;

// signal handlers quietly exit on ^C and kill

// these are usually remapped later by the client or server

my_signal( SIGTERM, sig_exit );

my_signal( SIGINT, sig_exit );

signal(SIGPIPE,SIG_IGN);

// perform any cleanup when quitting Iperf

atexit( cleanup );

ext_Settings* ext_gSettings = new ext_Settings;

Settings* gSettings = NULL;

Android源代码结构分析

目录 一、源代码结构 (2) 第一层次目录 (2) bionic目录 (3) bootloader目录 (5) build目录 (7) dalvik目录 (9) development目录 (9) external目录 (13) frameworks目录 (19) Hardware (20) Out (22) Kernel (22) packages目录 (22) prebuilt目录 (27) SDK (28) system目录 (28) Vendor (32)

一、源代码结构 第一层次目录 Google提供的Android包含了原始Android的目标机代码,主机编译工具、仿真环境,代码包经过解压缩后,第一级别的目录和文件如下所示: . |-- Makefile (全局的Makefile) |-- bionic (Bionic含义为仿生,这里面是一些基础的库的源代码) |-- bootloader (引导加载器),我们的是bootable, |-- build (build目录中的内容不是目标所用的代码,而是编译和配置所需要的脚本和工具) |-- dalvik (JAVA虚拟机) |-- development (程序开发所需要的模板和工具) |-- external (目标机器使用的一些库) |-- frameworks (应用程序的框架层) |-- hardware (与硬件相关的库) |-- kernel (Linux2.6的源代码) |-- packages (Android的各种应用程序) |-- prebuilt (Android在各种平台下编译的预置脚本) |-- recovery (与目标的恢复功能相关) `-- system (Android的底层的一些库)

词法分析器实验报告及源代码

数学与软件科学学院实验报告 学期:13至14__ 第_2 学期 2014年3月17 日 课程名称:编译原理专业:2011级5_班 实验编号:01 实验项目:词法分析器指导教师_王开端 姓名:张世镪学号: 2011060566 实验成绩: 一、目的 学习编译原理,词法分析是编译的第一个阶段,其任务是从左至右挨个字符地对源程序进行扫描,产生一个个单词符号,把字符串形式的源程序改造成单词符号串形式的中间程序。执行词法分析的程序称为词法分析程序,也称为词法分析器或扫描器。词法分析器的功能是输入源程序,输出单词符号 做一个关于C的词法分析器,C++实现 二、任务及要求 1.词法分析器产生下述C的单词序列 这个C的所有的单词符号,以及它们的种别编码和内部值如下表: -* / & <<=>>===!= && || , : ; { } [ ] ( ) ID和NUM的正规定义式为: ID→letter(letter | didit)* NUM→digit digit* letter→a | … | z | A | … | Z

digit→ 0 | … | 9 如果关键字、标识符和常数之间没有确定的算符或界符作间隔,则至少用一个空格作间隔。空格由空白、制表符和换行符组成。 三、大概设计 1. 设计原理 词法分析的任务:从左至右逐个字符地对源程序进行扫描,产生一个个单词符号。 理论基础:有限自动机、正规文法、正规式 词法分析器又称扫描器:执行词法分析的程序 2. 词法分析器的功能和输出形式 功能:输入源程序、输出单词符号 程序语言的单词符号一般分为以下五种:关键字、标识符、常数、运算符、界符。3. 输出的单词符号的表示形式: (单词种别,单词符号的属性值) 单词种别用整数编码,关键字一字一种,标识符统归为一种,常数一种,各种符号各一种。 4. 状态转换图实现

yaffs2文件系统制作

交叉编译器ARM-Linux-gcc4.1.2 开发板TX2440A Busybox-1.15.1.tar.bz2(在Linux中被称为瑞士军刀) mkyaffs2image工具 首先创建一个名字为root_2.6.31的文件夹,在其中创建如下文件夹 etc bin var dev home lib mnt proc root sbin sys tmp usr opt共14个文件夹 解压Busybox tar xjvf busybox 进入源目录,修改Makefile 第164行,CROSS_COMPILE=arm-linux- 第190行,ARCH=arm 执行#make men onfig进行配置 配置选项大部分都是保持默认的,只需要注意选择以下这几个选项,其他的选项都不用动:Busybox Setting---> Build Options---> [*]Build Busybox as a static binary(no shared libs) [*]Build with Large File Support(for accessing files>2GB) Installation Options--->

(./_install)Busybox installation prefix 进入这个选项,输入busybox的安装路径,如:../rootfs Busybox Library Tuning---> [*]vi-style line editing commands [*]Fancy shell prompts 要选择这个选项:“Fancy shell prompts”,否则挂载文件系统后,无法正常显示命令提示符:“[\u@\h\W]#” 配置完成以后 执行#make #make install 然后就会在上一级目录下生成rootfs文件夹,里面包含几个文件夹/bin/sbin/usr linuxrc 把这些文件全部复制到刚建好的root_2.6.31目录下, #cp–rf*../root_2.6.31 在dev目录下,创建两个设备节点: #mknod console c51 #mknod null c13 然后进入自己建立的etc目录 拷贝Busybox-1.15.2/examples/bootfloopy/etc/*到当前目录下。 #cp-r../../busybox-1.15.2/examples/bootfloopy/etc/*./ 包括文件:fstab init.d inittab profile

企业大数据采集、分析与管理系统设计报告(配图版)

企业大数据采集、分析与管理 系 统 设 计 报 告

目录 一、市场需求信息挖掘 (4) 1. 获取市场需求信息 (4) 2. 市场需求信息分析 (4) 二、工厂成本归集 (4) 1. 基于集成化系统的成本数据采集 (4) 2. 产品成本归集和核算 (5) 三、智能车间大数据采集、分析 (8) 1. 制造车间数据采集 (8) 2. 车间整体状态及计划执行情况分析 (11) 四、业务流程审批及进程监控 (11) 1. 业务流程管控 (12) 2. 采购、订单、物料管理与数据分析 (14) 3. 财务分析与统计 (16) 4. 需求、设计、工艺、制造各环节信息管理 (17) 5. 移动端APP (18) 五、质量信息管理与追溯 (18) 1. 质量信息管理 (18) 2. 供应商评价优选 (19) 六、无纸化OA系统及图档管理 (19) 1. 无纸化OA办公系统 (19) 2. 图纸及技术文档安全管理 (20)

企业大数据采集、分析与管理系统设计报告智能制造是制造业转型升级、向中高端制造业迈进的重要举措。离散制造型企业,其本身具有零件种类多、加工工序复杂、生产过程不确定因素众多、工厂透明度不高、部门间存在信息孤岛等特点。本系统从清晰的状态感知、实时数据分析与展示、决策精准执行与审批、全生命周期产品信息管理、无纸化OA及图档管理五大方面着手解决企业痛点,可以实现产品全生命周期生产过程管理、产品成本管理、信息共享管理和项目远程管理,帮助企业打造透明的、全过程可控的、高感知度的、高柔性的智慧工厂。

一、市场需求信息挖掘 1. 获取市场需求信息 市场需求信息能从多方面反映市场活动的方向,是企业指定经营战略、进行市场竞争的重要依据。本系统在每次客户发起询价时,会要求填写详细的需求信息。通过语义网(Semantic Web),对需求信息进行特征抽取和模糊聚类,进行分类存储,并构建适合企业自身的“市场需求指标库”。 2. 市场需求信息分析 将市场信息转化为企业决策,必须经过复杂的数据处理过程。对市场需求信息大数据聚类之后的各簇,建立统一的预测模型,通过时间序列模型、多元线性回归、最小二乘支持向量机等方法,对行业发展趋势做出预测,并将结果进行图表化展示。 二、工厂成本归集 1. 基于集成化系统的成本数据采集 功能:要素耗费的初次分配、生产成本的分配、辅助生产成本的分配、制造费用的分配。 随着信息化的发展,企业采用了基于集成化的成本数据采集方式如图所示,该采集方式将库存管理、财务管理、资源管理和质量管理等系统之间数据传递和采集,获取成本的相关信息。

语法分析器源代码

#include #include #include #define HIGHER 1 #define LOWER -1 #define EQUAL 0 #define TRUE 1 #define FALSE 0 #define OPER_NUM 50 //默认算符数目 #define VN_NUM 50 //默认非终结符数目#define MAX_BUFFER 128 //每行输入行最大长度 #define MAX_GRA_NUM 20 //最大文法数目#define EMPTY -2 //算符优先表初始值,表示这对算符没有优先关系 #define STACK_SIZE 64 typedef struct { char c; //非终极符符号 int firstvt[OPER_NUM]; //firstvt集,保存算符在oper_list中的下标 int fir_n,last_n; int lastvt[OPER_NUM]; }vn_t; int prior_table[OPER_NUM][OPER_NUM]; char oper_list[OPER_NUM]; int oper_num = 0; vn_t vn_list[VN_NUM]; int vn_num = 0; char *grammar[MAX_GRA_NUM][2]; int gra_num = 0; char start_vn; char stack[STACK_SIZE]; int top = 0; void get_input(char *buf); int buf_deal(char* buf); void get_FIRVT_LASTVT(int vn_n);

linux-2.6.18移植

Linux-2.6.18移植 有了我们的交叉编译环境和我们先前学的内核基础知识,下面我们就开始我们的内核移植了,我们所用的是博创的 S3C2410 。 关于 linux-2.6.18.tar.bz2 的下载网站先前我们说过,我们要先到该官方网站上去下载一个全新的内核。 [root@Binnary ~ ]# tar –jxvf linux-2.6.18.tar.bz2 [root@Binnary ~ ]# make mrproper 如果你是新下载的内核,那这一步就不用了。但如果你用的是别人移植好的内核,那最好在编译内核之前先清除一下中间文件,因为你们用来编译内核的交叉编译工具可能不同。 第一步:修改Makefile文件 将 改为 第二步:修改分区设置信息 我们要先在BootLoader中查看相应的分区信息 vivi>help 然后修改内核源码中的分区信息。分区信息文件在 a rch/arm/mach-s3c2410/common-smdk.c 将其中的

改为如下内容:

第三步:内核通过 BootLoader把数据写入NAND Flash,而vivi的ECC效验算法和内核的不同,内核的效验码是由NAND Flash控制器产生的,所以在此必须禁用NAND Flash ECC。所以我们就要修改 drivers/mtd/nand/s3c2410.c 这个文件。将 中的 chip->ecc.mode = NAND_ECC_SOFT ,改为如下 chip->ecc.mode = NAND_ECC_NONE。

只此一处。 第四步:下面是devfs的问题,因为2.6.12内核以后取消了devfs的配置选项,缺少了它内核会找不到mtdblock设备。所以我们需要修改 fs/Kconfig 文件,或者是从2.6.12的fs/Kconfig中拷贝下面几项到2.6.18的fs/Kconfig中去,我们采用修改的方法来完成。 修改 fs/Kconfig支持devfs 。 在Pseudo filesystems 主菜单的最后添加我们所要的内容。 第五步:文件系统的支持 Yaffs 文件系统 YAFFS文件系统简介 YAFFS,Yet Another Flash File System,是一种类似于JFFS/JFFS2的专门为Flash设计 的嵌入式文件系统。与JFFS相比,它减少了一些功能,因此速度更快、占用内存更少。 YAFFS和JFFS都提供了写均衡,垃圾收集等底层操作。它们的不同之处在于: (1)、JFFS是一种日志文件系统,通过日志机制保证文件系统的稳定性。YAFFS仅仅 借鉴了日志系统的思想,不提供日志机能,所以稳定性不如JAFFS,但是资源占用少。 (2)、JFFS中使用多级链表管理需要回收的脏块,并且使用系统生成伪随机变量决定 要回收的块,通过这种方法能提供较好的写均衡,在YAFFS中是从头到尾对块搜索, 所以在垃圾收集上JFFS的速度慢,但是能延长NAND的寿命。 (3)、JFFS支持文件压缩,适合存储容量较小的系统;YAFFS不支持压缩,更适合存 储容量大的系统。 YAFFS还带有NAND芯片驱动,并为嵌入式系统提供了直接访问文件系统的API,用 户可以不使用Linux中的MTD和VFS,直接对文件进行操作。NAND Flash大多采用 MTD+YAFFS的模式。MTD( Memory Technology Devices,内存技术设备)是对Flash 操作的接口,提供了一系列的标准函数,将硬件驱动设计和系统程序设计分开。 Yaffs 文件系统内核没有集成,可以对其主页下载: https://www.360docs.net/doc/5113988968.html,/cgi-bin/viewcvs.cgi/#dirlist

大数据企业架构讨论

大数据企业架构讨论

案例研究:智慧交通
大数据实时处理和分析
目的:提高城市交通的科学管理和组织服务水平
业务目标
传感 器
? 压力传感器 ? 速度传感器 ? 生物传感器 ? 温度、湿度……
RFID
? 射频天线扫描 ? 电子标识
? 智能交通数据的有力支撑 ? 智能交通公共信息服务的实时传递和快速反 应的应急指挥 ? 智能交通业务联动快速应对变化 ? 可视化事件跟踪
摄像 头
挑战
? 高速拍照 ? 高清摄像头
? 近千万辆轿车、轨道交通、快速公交系统 ? 高并发事件及数据流的实时处理 ? 海量非结构化大数据的组织与分析

智能交通整体规划架构
信息服务
用户服务
政府
企业
公共
个人
ITS智能交通物联网平台
城市综合信息管理平台 铁路综合管理平台 水运综合管理平台
应用层/ 信息处理
公路可视化综合信息平台
公共交通运营管理平台
雷达测速 通信 监控 GIS 信号 电警 车次号识别 ETC CBTC 紧急救援 接处警 卡口 视频监控 PIS 事件检测 交通诱导 BRT 路径识别 信号控制 旅行时间 出行者信息系统 电子站牌 智能停车场 公交调度管理
车地双向实时无线通信网数传电台 政府专网 Internet
网络层/ 信息传输
GPRS/CDMA/3G/Wi-Fi/WiMax光纤TCP/IP
感知层/ 信息采集
交通行业
3

编译原理课程设计-词法分析器(附含源代码)

编译原理-词法分析器的设计 一.设计说明及设计要求 一般来说,编译程序的整个过程可以划分为五个阶段:词法分析、语法分析、中间代码生成、优化和目标代码生成。本课程设计即为词法分析阶段。词法分析阶段是编译过程的第一个阶段。这个阶段的任务是从左到右一个字符一个字符地读入源程序,对构成源程序的字符流进行扫描和分解,从而识别出一个个单词(也称单词符号或符号)。如保留字(关键字或基本字)、标志符、常数、算符和界符等等。 二.设计中相关关键字说明 1.基本字:也称关键字,如C语言中的 if , else , while , do ,for,case,break, return 等。 2.标志符:用来表示各种名字,如常量名、变量名和过程名等。 3.常数:各种类型的常数,如12,6.88,和“ABC” 等。 4.运算符:如 + ,- , * , / ,%, < , > ,<= , >= 等。5.界符,如逗点,冒号,分号,括号,# ,〈〈,〉〉等。 三、程序分析 词法分析是编译的第一个阶段,它的主要任务是从左到右逐个字符地对源 程序进行 扫描,产生一个个单词序列,用以语法分析。词法分析工作可以是独立的一遍,把字符流的源程序变为单词序列,输出在一个中间文件上,这个文件做为语法分析程序的输入而继续编译过程。然而,更一般的情况,常将

词法分析程序设计成一个子程序,每当语法分析程序需要一个单词时,则 调用该子程序。词法分析程序每得到一次调用,便从源程序文件中读入一 些字符,直到识别出一个单词,或说直到下一个单词的第一个字符为止。 四、模块设计 下面是程序的流程图 五、程序介绍 在程序当前目录里建立一个文本文档,取名为infile.txt,所有需要分析的程序都写在此文本文档里,程序的结尾必须以“@”标志符结束。程序结果输出在同一个目录下,文件名为outfile.txt,此文件为自动生成。本程序所输出的单词符号采用以下二元式表示:(单词种别,单词自身的值)如程序输出结果(57,"#")(33,"include")(52,"<")(33,"iostream") 等。 程序的功能:(1)能识别C语言中所有关键字(共32个)(单词种别分别为1 — 32 ,详情见程序代码相关部分,下同) (2)能识别C语言中自定义的标示符(单词种别为 33) (3)能识别C语言中的常数(单词种别为0) (4)能识别C语言中几乎所有运算符(单词种别分别为41 — 54) (5)能识别C语言中绝大多数界符(单词种别分别为 55 — 66)六、运行结果 输入文件infile.txt 运行结果(输出文件 outfile.txt)

Yaffs2文件系统中对NAND Flash磨损均衡的改进

Yaffs2文件系统中对NAND Flash磨损均衡的改进 摘要:针对以NAND Flash为存储介质时Yaffs2文件系统存在磨损均衡的缺陷,通过改进回收块选择机制,并在数据更新中引入冷热数据分离策略,从而改善NAND Flash的磨损均衡性能。实验借助Qemu软件建立Linux嵌入式仿真平台,从总擦除次数、最大最小擦除次数差值和块擦除次数标准差等方面进行对比。实验结果表明,在改进后的Yaffs2文件系统下NAND Flash的磨损均衡效果有明显提升,这有益于延长NAND Flash的使用寿命。 关键词: Yaffs2文件系统;NAND Flash;垃圾回收;冷热数据 0 引言 NAND Flash存储设备与传统机械磁盘相比,具有体积小、存储密度高、随机存储和读写能力强、抗震抗摔、功耗低等特点[1]。它被广泛用于智能手机、车载智能中心、平板电脑等智能终端中。近年来,以NAND Flash为存储介质的固态硬盘也得到越来越多的应用。目前Yaffs2文件系统(Yet Another Flash File System Two,Yaffs2)[1]是使用最多、可移植性最好的专用文件系统,在安卓、阿里云OS、Linux等嵌入式系统中都有使用。在Yaffs2文件系统下以NAND Flash为存储介质时存在磨损均衡的缺陷,可通过对回收块选择机制作改进和引入冷热数据分离策略来提高磨损均衡的效果。 1 Yaffs2和Nand Flash关系 这里以使用最多的Linux操作系统为实践,将Yaffs2文件系统移植到Linux操作系统中。Linux系统通常可以分为3层:应用层、内核层和设备层,其中支持NAND Flash设备的Yaffs2文件系统属于内核层,。 最上层用户应用程序通过VFS(Virtual File System)提供的统一接口,将数据更新等文件操作传递给Yaffs2。VFS代表虚拟文件系统,它为上层应用提供统一的接口。有了这些接口,应用程序只用遵循抽象后的访问规则,而不必理会底层文件系统和物理构成上的差异。然后Yaffs2通过MTD(Memory Technology Device)提供的统一访问接口对NAND Flash进行读、写和擦除操作,从而完成数据的更新或者存储操作。MTD代表内存技术设备,它为存储设备提供统一访问的接口。最终,在NAND Flash上以怎样的格式组织和存储数据由Yaffs2文件系统决定。 NAND Flash由若干块(block)组成,每个块又是由若干页(page)组成,页中含有数据区和附加区。NAND Flash的页根据状态不同,可以分为有效页、脏页、空闲页。有效页中存放有效数据,脏页中存放无效数据,空闲页是经过擦除后可以直接用于写入数据的页。NAND Flash在写入数据前需要执行擦除操作,因此数据不能直接在相同的位置更新。当一个页中数据需要更新时,必须将该页中有效数据拷贝到其他空闲页上再更新,并将原来页上的数据置为无效。随着时间的推移,许多无效页累积在存储器中使得空闲页逐渐减少。当存储器中的空闲空间不足时,启动垃圾回收操作,利用回收块选择机制从待回收块中选取满足要求的块来擦除,从而得到足够的空闲空间。NAND Flash中块的擦除次数有限,通常为10 000次~100 000次[2]。当某个块的擦除次数超过使用寿命时,该块将无法正常用于数据存储。因此,垃圾回收应利用合理的回收块选择机制,从待回收块中找到回收后能产生良好磨损均衡效果且付出较少额外代价的块来回收,从而获得足够的空闲空间用于数据更新操作。 2 Yaffs2在磨损均衡方面的缺陷 Yaffs2中回收块的选择机制[3]是从待回收块中找到有效数据最少的块来回收。回收过程中,Yaffs2能够减少有效数据的额外读和写操作。当数据更新处于均匀分布的情况下,Yaffs2表现出较好的磨损均衡效果。 但是,通常情况下数据的更新频率不同,有些数据经常更新,而有些数据很少更新。经

大数据处理技术的总结与分析

数据分析处理需求分类 1 事务型处理 在我们实际生活中,事务型数据处理需求非常常见,例如:淘宝网站交易系统、12306网站火车票交易系统、超市POS系统等都属于事务型数据处理系统。这类系统数据处理特点包括以下几点: 一就是事务处理型操作都就是细粒度操作,每次事务处理涉及数据量都很小。 二就是计算相对简单,一般只有少数几步操作组成,比如修改某行得某列; 三就是事务型处理操作涉及数据得增、删、改、查,对事务完整性与数据一致性要求非常高。 四就是事务性操作都就是实时交互式操作,至少能在几秒内执行完成; 五就是基于以上特点,索引就是支撑事务型处理一个非常重要得技术. 在数据量与并发交易量不大情况下,一般依托单机版关系型数据库,例如ORACLE、MYSQL、SQLSERVER,再加数据复制(DataGurad、RMAN、MySQL数据复制等)等高可用措施即可满足业务需求。 在数据量与并发交易量增加情况下,一般可以采用ORALCERAC集群方式或者就是通过硬件升级(采用小型机、大型机等,如银行系统、运营商计费系统、证卷系统)来支撑. 事务型操作在淘宝、12306等互联网企业中,由于数据量大、访问并发量高,必然采用分布式技术来应对,这样就带来了分布式事务处理问题,而分布式事务处理很难做到高效,因此一般采用根据业务应用特点来开发专用得系统来解决本问题。

2数据统计分析 数据统计主要就是被各类企业通过分析自己得销售记录等企业日常得运营数据,以辅助企业管理层来进行运营决策。典型得使用场景有:周报表、月报表等固定时间提供给领导得各类统计报表;市场营销部门,通过各种维度组合进行统计分析,以制定相应得营销策略等. 数据统计分析特点包括以下几点: 一就是数据统计一般涉及大量数据得聚合运算,每次统计涉及数据量会比较大。二就是数据统计分析计算相对复杂,例如会涉及大量goupby、子查询、嵌套查询、窗口函数、聚合函数、排序等;有些复杂统计可能需要编写SQL脚本才能实现. 三就是数据统计分析实时性相对没有事务型操作要求高。但除固定报表外,目前越来越多得用户希望能做做到交互式实时统计; 传统得数据统计分析主要采用基于MPP并行数据库得数据仓库技术.主要采用维度模型,通过预计算等方法,把数据整理成适合统计分析得结构来实现高性能得数据统计分析,以支持可以通过下钻与上卷操作,实现各种维度组合以及各种粒度得统计分析。 另外目前在数据统计分析领域,为了满足交互式统计分析需求,基于内存计算得数据库仓库系统也成为一个发展趋势,例如SAP得HANA平台。 3 数据挖掘 数据挖掘主要就是根据商业目标,采用数据挖掘算法自动从海量数据中发现隐含在海量数据中得规律与知识。

2-Linux

Linux-2.6.32.2内核在mini2440上的移植(二)---yaffs2文件系统移植 移植环境(红色粗字体字为修改后内容,蓝色粗体字为特别注意内容) 2.1, yaffs2文件系统移植 【1】获取yaffs2 源代码 现在大部分开发板都可以支持yaffs2 文件系统,它是专门针对嵌入式设备,特别是使用nand flash 作为存储器的嵌入式设备而创建的一种文件系统,早先的yaffs 仅支持小页(512byte/page)的nand flash,现在的开发板大都配备了更大容量的nand flash,它们一般是大页模式的(2K/page),使用yaffs2 就可以支持大页的nand flash,下面是yaffs2 的移植详细步骤。 在https://www.360docs.net/doc/5113988968.html,/node/346可以下载到最新的yaffs2 源代码,需要使用git工具( 安装方法见Git版本控制软件安装与使用),在命令行输入: [root@localhost ~]# cd ./linux-test [root@localhost linux-test]# git clone git://https://www.360docs.net/doc/5113988968.html,/ya ffs2 Cloning into yaffs2... remote: Counting objects: 6592, done. remote: Compressing objects: 100% (3881/3881), done. remote: Total 6592 (delta 5237), reused 3396 (delta 2642) Receiving objects: 100% (6592/6592), 3.34 MiB | 166 KiB/s, d one. Resolving deltas: 100% (5237/5237), done.

大数据分析及其在医疗领域中的应用-图文(精)

第7期 24 2014年4月10日 计算机教育 ComputerEducation ◆新视点 文章编号:1672.5913(2014)07—0024-06 中图分类号:G642 大数据分析及其在医疗领域中的应用 邹北骥 (中南大学信息科学与工程学院,湖南长沙410083) 摘要:互联网和物联网技术的快速发展给数据的上传与下载带来了前所未有的便利,使得互联网上 的数据量急剧增长,由此产生了针对大数据的存储、计算、分析、处理等新问题,尤其是对大数据的挖掘。文章分析当前大数据产生的背景,阐述大数据的基本特征及其应用,结合医疗领域,论述医疗 大数据分析的目的、意义和主要方法。 关键词:大数据;物联网;医疗;大数据挖掘 1 大数据早已存在,为何现在称之为大

数据时代 计算与数据是一对孪生姐妹,计算需要数据,数据通过计算产生新的价值。数据是客观事 物的定量表达,来自于客观世界并早已存在。例 如,半个世纪前,全球的人口数量就有数十亿,与之相关的数据就是大数据;但是在那个时代,由于技术的局限性,大数据的采集、存储和处理 还难以实现。 互联网时代之前,采集世界各地的数据并让它们快速地进入计算系统几乎是一件不可想象的 事情。20世纪80年代兴起的互联网技术在近30 年里发生了翻天覆地的变化,彻底地改变了人们的工作和生活方式【l】。通过互联网人们不仅可以下载到新闻、小说、论文等各类文字数据,而且可以轻而易举地下载到音乐、图像和视频等多媒体数据,这使得互联网上的数据流量急剧增长。据统计,现在互联网上每分钟流人流出的数 据量达到1 000 PB,即10亿 GBt21。 推动大数据产生的另一个重要因素是物联网技术。近几年发展起来的物联网技 术通过给每个物品贴上标签 并应用RFID等技术实现了

实验一、词法分析器(含源代码)

词法分析器实验报告 一、实验目的及要求 本次实验通过用C语言设计、编制、调试一个词法分析子程序,识别单词,实现一个C语言词法分析器,经过此过程可以加深对编译器解析单词流的过程的了解。 运行环境: 硬件:windows xp 软件:visual c++6.0 二、实验步骤 1.查询资料,了解词法分析器的工作过程与原理。 2.分析题目,整理出基本设计思路。 3.实践编码,将设计思想转换用c语言编码实现,编译运行。 4.测试功能,多次设置包含不同字符,关键字的待解析文件,仔细察看运行结果,检测该分析器的分析结果是否正确。通过最终的测试发现问题,逐渐完善代码中设置的分析对象与关键字表,拓宽分析范围提高分析能力。 三、实验内容 本实验中将c语言单词符号分成了四类:关键字key(特别的将main说明为主函数)、普通标示符、常数和界符。将关键字初始化在一个字符型指针数组*key[]中,将界符分别由程序中的case列出。在词法分析过程中,关键字表和case列出的界符的内容是固定不变的(由程序中的初始化确定),因此,从源文件字符串中识别出现的关键字,界符只能从其中选取。标识符、常数是在分析过程中不断形成的。 对于一个具体源程序而言,在扫描字符串时识别出一个单词,若这个单词的类型是关键字、普通标示符、常数或界符中之一,那么就将此单词以文字说明的形式输出.每次调用词法分析程序,它均能自动继续扫描下去,形成下一个单词,直到整个源程序全部扫描完毕,从而形成相应的单词串。 输出形式例如:void $关键字

流程图 、程序 流程图: 开始 输入源文件路径 路径是否有 效 是初始化文件指针 否 将字符加入字符数 组Word[] 是空格,空白或换 行吗 是字母吗是数字吗否否是界符吗否打开源文件 跳过该字符 是是 文件结束? 否 将字符加入字符数 组Word[] 否 将字符加入字符数组Word[] 是 指向下一字符识别指针内容 指向下一字符 是字母惑数字 吗 是 将word 与关键字表key 进行匹 配 否匹配?是输出word 为关键字 输出word 为普通标示符 否将字符加入字符数组Word[] 指向下一字符输出word 为常数 识别指针内容 回退 是数字吗 是 否输出word 为界符 指向下一字符 结束 是输出Word 内容为不可识别 将字符加入字符数组Word[]

词法分析器源代码

词法分析器源代码 #include #include #include #include /*单词种别码*/ #define _CHAR 1 #define _INT 2 #define _SHORT 3 #define _LONG 4 #define _SIGNED 5 #define _UNSIGNED 6 #define _FLOAT 7 #define _DOUBLE 8 #define _CONST 9 #define _VOID 10 #define _VOLATILE 11 #define _ENUM 12 #define _STRUCT 13 #define _UNION 14 #define _TYPEDEF 15 #define _AUTO 16 #define _EXTERN 17 #define _STATIC 18 #define _REGISTER 19 #define _IF 20 #define _ELSE 21 #define _SWITCH 22 #define _CASE 23 #define _DEFAULT 24 #define _WHILE 25 #define _DO 26 #define _FOR 27 #define _BREAK 28 #define _CONTINUE 29 #define _GOTO 30 #define _RETURN 31 #define _SIZEOF 32 #define _INCLUDE 33 #define _DEFINE 34 /* 以上为关键字的种别码 */ #define _ID 40 //标识符 #define _NUM 50 //数 #define _AS 51 //= #define _PLUS 52 //+ #define _SUB 53 //- #define _TIMES 54 // * #define _DIV 55 // / #define _LP 56 // ( #define _RP 57 // ) #define _LB1 58 // [ #define _RB1 59 // ] #define _LB2 60 // { #define _RB2 61 // } #define _COM 62 // , #define _COL 63 // : #define

使用yaffs2img工具制作Android刷机包教程

制作刷机包 打开‘yaffs2img浏览器’,点击左上角的‘选取yaffs2文件’选择你刚刚复制出来的 files文件夹里的system.img 先来认识一下这个软件 1.定制软件的提取(此部和制作刷机包没关系,可以不做,想用官方软件的同学可以 看看) 选择app,右键你想要提取软件,提取就可以了,我是把整个app文件夹提取出来了,不用 的软件直接删掉好了 2.定制软件的精简 在你不想要用的软件上直接右键,删除,就好了,你也可以右键添加你想要用的软件,得把

软件改成比较简短的英文名,否则有可能不能用 秀一下我精简后的列表,大家可以参照着精简 https://www.360docs.net/doc/5113988968.html,uncher文件的替换 下载好你想要用的桌面软件,改名为‘Launcher’,删掉app中的‘Launcher2’,添加进去你改好名字的‘Launcher’就好了,我比较喜欢ADW,所以我把ADW的文件名改为 Launcher,替换掉原来的Launcher2就好了 4.破音问题的解决 在左边导航点选‘etc’,右键添加文件,把附件中的声音配置文件解压出来 ‘AudioFilter.csv’添加进去就好了 AudioFilter.rar (355 Bytes)

5.字体的更改 下载字体文件,中文字体库一律把名字改名为‘DroidSans Fallback.ttf’,英文字体改为‘DroidSans.ttf ’,加粗的英文字体改为‘DroidSans-Bold.ttf ’然后再左边导航栏点选‘fonts’,把之前自带的字体删除,然后把你改好名字的字体添加进去就好了把国产机皇的字体也分享给大家,中文+英文+英文加粗 6.开机音乐和照相机音乐的删除 在导航栏点选‘media’,在audio/ui文件夹下,删除‘Bootsound.mp3’(开机音乐)和

企业大数据案例分析(公司大数据、集团大数据)

企业大数据案例分析

目录 1中国联通大数据平台 (4) 1.1项目概述 (4) 1.2项目实施情况 (5) 1.3项目成果 (10) 1.4项目意义 (11) 2恒丰银行大数据平台 (12) 2.1项目概述 (12) 2.2项目实施情况 (15) 2.3项目成果 (21) 2.4项目意义 (21) 3华通CDN运营商海量日志采集分析系统 (24) 3.1项目概述 (24) 3.2项目实施情况 (24) 3.3项目成果 (28) 3.4项目意义 (28) 4案例总结 (30)

1中国联通大数据平台 联通XX公司公司按照工信部的的要求(见《工业和信息化部、国务院国有资产监督管理委员会关于开展基础电信企业网络与信息安全责任考核有关工作的指导意见》和《工业和信息化部办公厅关于印发<2013年省级基础电信企业网络与信息安全工作考核要点与评分标准>的通知》),于2013年启动IDC/ISP日志留存系统的建设,其中XX 公司侧的集中留存系统软件由联通研究院负责开发。为了满足海量数据条件下的处理效率的要求,XX公司侧集中留存系统软件除研究院自主开发外,基于Hadoop的数据存储部分计划进行外包,通过软件技术服务,来进行系统优化和维护支撑。 1.1项目概述 目前,联通XX公司公司全国IDC出口的访问日志预计两个月产生的数据量约20 PB至30PB,每秒写入大概6千万至7千万条数据,在如此巨大的数据量下,原有Ter adata和Oracle已经不能满足快速读写的性能要求了。同时为了实现快速检索以及分析处理的性能要求,需要引入分布式大数据平台,利用分布式文件存储系统,提高数据的存储入库能力,利用Hadoop/HBase架构克服磁盘I/O瓶颈导致的数据读写延迟;基于联通IDC出口流量详单数据进行快速存储和检索以及分析处理,同样要求数据处理平台具备快速读写的高性能。 中国联通公司全国IDC日至留存项目对分布式集群的要求非常高: (1)日志数据量非常大,存储的总日志数据量将达到20PB-30PB。 (2)要求集群的数据吞吐量非常高,每秒的日志写入量将达到6千万至七千万条,

编译原理 LL(1)语法分析器java版 完整源代码

public class Accept2 { public static StringBuffer stack=new StringBuffer("#E"); public static StringBuffer stack2=new StringBuffer("i+i*#"); public static void main(String arts[]){ //stack2.deleteCharAt(0); System.out.print(accept(stack,stack2)); } public static boolean accept(StringBuffer stack,StringBuffer stack2){//判断识别与否 boolean result=true; outer:while (true) { System.out.format("%-9s",stack+""); System.out.format("%9s",stack2+"\n"); char c1 = stack.charAt(stack.length() - 1); char c2 = stack2.charAt(0); if(c1=='#'&&c2=='#') return true; switch (c1) {

case'E': if(!E(c2)) {result=false;break outer;} break; case'P': //P代表E’if(!P(c2)) {result=false;break outer;} break; case'T': if(!T(c2)) {result=false;break outer;} break; case'Q': //Q代表T’if(!Q(c2)) {result=false;break outer;} break; case'F': if(!F(c2)) {result=false;break outer;} break; default: {//终结符的时候 if(c2==c1){ stack.deleteCharAt(stack.length()-1); stack2.deleteCharAt(0); //System.out.println(); } else{

第2天 linux系统的编译及镜像文件的制作

第2天linux系统的编译及镜像文件的制作 一般来说,linux系统分为几个映像。 一:bootload :一般常用的是 U-boot 二:内核映像:主要是linux内核编译成的映像比如TQ210开发板使用的zImage.bin 三:文件系统:有很多格式,比如 下面根据TQ210说明书进行讲解,大部分参考官方手册。以后自己修改源码可以在此基础上进行修改,修改完以后按照此步骤进行编译,编译完成后进行下载到开发板进行运行 1编译bootloader 1.1光盘中的 u-boot 源码的解压 先将光盘中的 u-boot 源码 ( 在光盘的“ TQ210_CD\bootloader\ ” 目录下 , 名为 uboot_TQ210_1.3.4_V1.1.tar.bz2)拷贝到 PC 的linux系统的根目录(这里说的根目录是本手册编写者的操 作和截图所拷贝的地方, 实际操作可以拷贝到任意目录下) 下, 然后使用命令#tar xvfjuboot_TQ210_1.3.4_V1.1.tar.bz2 -C /,解压源码,如下图所示 源码解压后,会在“/opt/EmbedSky/TQ210/uboot_TQ210_1.3.4/”目录下得到刚刚解压的源码。 1.2 光盘中的u-boot源码的编译 解压完成后,使用命令#make TQ210_config,配置u-boot,如下图所示:

使用命令#make,编译u-boot。编译结束后,在/opt/EmbedSky/TQ210/uboot_TQ210_1.3.4/目录下会得 到一个名字u-boot.bin的镜像,将其拷贝到Windows 或者拷贝到TFTP 服务器发送文件指定的目录中,就 可以烧写到开发板上面进行测试了(或者制作成SD 启动卡也可以测试)。如下两图所示:

大数据分析系统项目方案

大数据分析系统 方案

目录 第1章项目概述 (5) 1.1项目背景 (5) 1.2项目必要性 (5) 1.3建设目标 (6) 第2章需求分析 (8) 2.1功能及性能需求 (8) 2.2系统集成需求 (9) 2.3运行环境 (10) 2.4安全需求 (10) 第3章总体设计 (12) 3.1总体设计原则 (12) 3.2总体目标 (13) 3.3系统总体结构 (13) 3.4系统逻辑结构 (15) 第4章详细设计方案 (16) 4.1信息资源规划和数据库设计 (16) 4.1.1数据模型概述 (16) 4.1.2数据建模方法论 (17) 4.1.3数据建模基本原则 (18) 4.1.4数据库架构设计 (19) 4.2数据应用支撑系统设计 (21) 4.2.1大数据平台关键技术 (21) 4.2.2云平台数据共享功能 (26) 4.3数据服务层计 (33) 4.3.1模型的应用 (33) 4.3.2平台基础应用 (33) 4.4数据处理和存储系统设计 (34) 4.4.1大数据处理核心技术 (35) 4.4.2数据存储采用MPP与hadoop融合架构 (35) 4.5网络系统设计 (35) 4.6安全系统设计 (36) 4.6.1系统安全满足情况 (36) 4.6.2系统安全配置管理功能 (37) 4.6.3系统无安全漏洞保障 (40) 4.6.4软件自身安全 (43) 4.6.5性能和可靠性 (44) 4.7运行维护系统设计 (46)

4.7.2网络设备管理 (46) 4.7.3进程管理 (46) 4.7.4服务管理 (46) 4.7.5数据库管理 (46) 4.7.6中间管理 (46) 4.7.7集群管理 (47) 4.7.8故障管理 (47) 4.7.9性能管理 (47) 4.7.10配置文件管理 (47) 4.7.11SYSLOG管理 (47) 4.8其他系统设计 (47) 4.9系统配置及软硬件选型原则 (48) 4.9.1软硬件部署 (48) 4.9.2数据要求 (48) 4.9.3技术要求 (49) 4.10系统软硬件物理部署方案 (49) 第5章项目建设与运行管理 (51) 5.1项目领导机构 (51) 5.2项目管理机构 (51) 5.3项目承建机构 (53) 5.4运行维护机构 (53) 5.5相关管理制度 (54) 5.6项目测试 (55) 5.6.1单元测试 (55) 5.6.2集成测试 (55) 5.6.3系统测试 (56) 5.6.4性能测试 (56) 5.6.5验收测试 (57) 5.6.6安装测试 (57) 5.7安全性测试 (58) 5.7.1功能验证 (58) 5.7.2漏洞扫描 (58) 5.7.3模拟攻击实验 (58) 5.8项目验收 (60) 5.8.1项目验收要求 (60) 5.8.2项目验收的目的和原则 (61) 5.8.3项目验收的组织和实施 (61) 5.8.4项目验收的步骤和程序 (61) 5.8.5项目验收的测试方案 (61) 5.8.6项目验收的文档清单 (61) 第6章项目培训计划 (62) 6.1培训对象和培训目标 (62)

相关文档
最新文档