文档库 最新最全的文档下载
当前位置:文档库 › 云计算的关键技术与应用实例

云计算的关键技术与应用实例

第一篇从并行计算到云计算
第1章并行计算与云计算................ 2
1.1 并行计算到云计算的演变...................... 2
1.2 云计算需要定义吗?....... 4
1.3 云计算是否是新瓶装旧酒...................... 5
1.4 MPI与Hadoop,不同学科
学者的选择...... 6
1.5 云计算与浏览器..................... 8
第2章MPI并行计算环境的建立..................... 10
2.1 配置前的准备工作.......... 10
2.2 挂载NFS文件系统......... 11
2.3 配置ssh实现MPI节点间
用户的无密码访问.......... 12
2.4 安装MPICH2......................... 12
2.5 建立并行计算环境时的
注意事项......... 14
第3章并行计算时代的程序设计方法....... 15
3.1 最简单的并行程序.......... 15
3.2 获取进程标志和机器名......................... 18
3.3 有消息传递功能的并行程序........... 20
3.4 Monte Carlo法在并行程序设计
中的应用......... 23
3.5 并行计算中节点间的
Reduce操作. 25
3.6 用MPI的6个基本函数实现
Reduce函数功能................ 28
3.7 计算与通信的并行.......... 30
3.8 节点间自定义复杂数据
结构的传输.. 34
3.9 MPI与MySQL数据库的
结合应用......... 37
3.10 设计MPI并行程序时的
注意事项..... 41
第4章从MPI走向云计算........... 43
4.1 MPI没有分布式文件系统支持.... 43
4.2 MPI无法应对节点的失效.................. 44
4.3 假如用MPI来构建云计算系统... 44
第二篇云计算的关键技术
第5章Map/Reduce是云计算的选择吗..... 48
5.1 Map/Reduce跨越50年的
历史....................... 48
5.2 实现Map/Reduce的C语言实例. 49
5.3 采用MPI实现并行化的
Map/Reduce功能................ 51
第6章Hadoop技术.. 58
6.1 Hadoop与MPI在数据处理上的
对比....................... 58
6.2 Hadoop的主从式结构. 59
6.2.1 主从式文件系统HDFS....... 59
6.2.2 主从式计算系统
Map/Reduce.......... 60
6.2.3 文件分块策略分析.................... 61
6.3 Hadoop文件系统HDFS的
前辈GFS......... 64
6.4 构建云文件系统需要解决的
关键问题......... 66
6.5 云计算不相信节点服务器.................. 67
6.6 揭密云计算架构下的典型
服务器——Google服务器................. 68
6.6.1 Google服务器概述................... 68
6.6.2 揭开Google服务器的
神秘面纱................ 69
6.6.3 Google服务器的配置
情况. 69
6.6.4 Google服务器的性能
评测. 73
第7章Hadoop环境的建立.......... 75
7.1 Hadoop配置环境............... 75
7.2 配置ssh实现Hadoop结点间
用户的无密码访问.......... 76
7.3 JDK的安装配置................. 76
7.4 Hadoop的安装配置........ 77
7.5 Hadoop中的Hello World..................... 81
7.6 C语言程序在Hadoop
上运行................ 82
第8章动手做自己的云计算
V0.01系统..... 86
8.1 系统总体分析........................ 86

8.1.1 系统架构................... 86
8.1.2 文件分布式存储流程............. 88
8.1.3 计算与存储的整合流程...... 88
8.2 管理节点程序设计与分析.................. 89
8.2.1 管理节点服务器程序
主函数........................ 90
8.2.2 管理节点各线程函数的
设计.. 93
8.2.3 主服务器中其他函数的
设计.. 95
8.3 子节点程序分析................. 98
8.3.1 子节点主函数..... 99
8.3.2 子节点各线程函数设计.. 102
8.4 客户端API设计............. 107
8.4.1 客户端文件的存储................ 108
8.4.2 客户端启动子节点计算... 113
8.4.3 客户端应用的简单实例... 114
8.5 客户端应用开发实例115
第三篇云计算应用实例
第9章基于不可信服务器节点的云计算
基础架构....... 118
9.1 云计算基础架构的应用场景........ 118
9.2 云计算基础架构.............. 120
9.3 基于单向指针目录映射的分层
用户隔离...... 121
9.4 云文件系统的物理存储管理........ 123
9.5 云存储的安全级别划分...................... 124
9.6 计算和存储的整合....... 125
9.7 计算和存储的迁移....... 126
9.8 任务的可并行性和分类分析........ 127
9.9 简化的服务器级粗粒度计算和
存储资源分配方案... 130
9.10 数据的云计算系统之旅.................. 133
第10章云计算与智能.................... 135
10.1 云计算的智能与人类
智能的比较........................ 135
10.2 云计算提升终端智能......................... 136
10.3 云计算智能与Monte Carlo
方法................ 138
10.4 云计算时代不确定性智能算法
示例——模拟谐振子算法........... 138
10.4.1 简谐振动的描述.................... 139
10.4.2 模拟谐振子算法描述...... 141
10.4.3 模拟谐振子算法流程...... 144
10.4.4 模拟谐振子算法分析...... 146
10.4.5 模拟谐振子算法应用于
旅行商问题..... 149
10.4.6 模拟谐振子算法在连续
和非线性优化问题中的
应用.......................... 161
10.4.7 模拟谐振子算法的隐含
并行性................... 162
10.5 云计算中的人工智能......................... 162
第11章云计算企业之间的竞争性
分析................... 164
11.1 云计算技术流派分析.......................... 164
11.1.1 存储型—数据密集云
计算平台............ 164
11.1.2 计算型—计算密集云
计算平台............ 165
11.1.3 综合云计算平台.................... 165
11.2 国际云计算公司分析.......................... 165
11.2.1 云计算技术的提出者
Google.................... 166
11.2.2 “端”的霸主微软............. 166
11.2.3 蓝色巨人IBM的蓝云.... 167
11.2.4 云计算的市场先行者
Amazon公司. 168
11.2.5 Salesforce从SaaS走入
云中.......................... 168
11.2.6 热爱白皮书的Sun............... 169
11.2.7 EMC云计算的核心是
虚拟

化................... 170
11.2.8 渔翁得利的思科.................... 170
11.3 国内云计算公司分析.......................... 171
11.3.1 拥有基础设施的
世纪互联............ 171
11.3.2 阿里巴巴下决心
入云.......................... 172
11.3.3 中国移动的BigCloud...... 172
11.3.4 国产旗帜友友云计算
平台.......................... 173
11.3.5 曙光高性能与云计算...... 173
11.3.6 展览也要云..... 173
11.4 开源云计算平台分析.......................... 174
11.5 国际国内云计算平台提供商
对比研究.. 175
11.6 产业综合分析.................. 179
11.6.1 云计算与网络设备商的
关系.......................... 179
11.6.2 云计算与移动通讯运
营商的关系..... 180
11.6.3 云计算与服务器提
供商的关系..... 180
11.6.4 云计算与应用程序开
发商的关系..... 181
后记:未来的计算机—不确定性和
隐含并行计算...................... 182
附录:计算力的标准Linpack测试详细
指南..................... 186
参考文献196
面对云计算,有的人越来越糊涂,经常听到有人用云里雾里来形容现在的云计算。云计算系统
确实是一个庞大和综合的系统,即使是国际大公司也不敢贸然进军云计算领域,大量的企业不
是将自己的传统技术优势称为云计算,就是雷声大雨点小的观望。一般开发者更是不适应在机
群的环境下工作,所以本章将用一个简单的例子来展现云计算的基本特点和技术开发方式,我
们并不保证这个系统是一个完善的系统,但它具备了云计算的一些基本特点如计算和存储的整
合、计算向存储的迁移、文件的分布式存储、计算的并行化等,我们对这些功能采用了最简单
的实现方法以使大多数读者能从中体会到云计算技术的核心理念,所以我们命名这个系统为云
计算V0.01,运行环境为Windows。
8.1
系统总体分析
我们进行系统总体结构设计时主要着眼于云计算基本特征的实现,不考虑系统中很多细节性的
要求和高级要求,并采用中等水平的读者能完成的难度设计。
设计需要实现的基本功能如下。
(1)向开发云应用的客户提供可以调用的API函数,利用API函数实现对云计算系统的访问。
(2)实现分布式的文件存储。
(3)实现计算向存储的迁移,使计算和存储在同一个节点完成,避免数据在网络中的传送。
(4)向用户隔离计算的并行性和存储的分布性,用户无需关心系统具体的操作过程。
(5)初步实现对数据求和及求最大值的处理,演示云计算的基本特点。读者可以通过增加处理
函数实现更多的计算功能。
8.1.1
系统架构
云计算V0.01系统是一个完全模型化的实验用系统,开发和运行环境为Windows系统,通过对该
系统的

学习使读者对云计算技术的基本要点有一定的了解,云计算V0.01将云计算设备分为3个
角色:管理节点、子节点和客户端。管理节点和子节点构成了云计算的服务器端,客户端通过
对API的调用实现对云计算系统的访问,并通过API整合为不同的应用程序。为了简化系统的设
计难度,我们在做云计算V0.01时限定所做的计算任务包括对大数据量数组求和、求最大值等操
作,读者可通过实际的系统体会存储的分布化与计算的并行化的关系,并理解计算向存储迁移
的作用。云计算V0.01没有实现存储的副本策略,因此暂时不能处理节点失效的问题,这也是为
了降低系统难度的需要。以下的系统架构方法仅供参考和学习,并且不代表我们赞成这一架构,
不同的读者可以设计不同的系统架构。
系统的整个架构如图8.1所示,这种架构方式是一个以客户端为核心的架构方法,系统中的所有
操作指令均由客户端发出,管理节点不和任一子节点作数据和指令的通信,管理节点的作用主
要是维护root.dat和node.dat两个系统文件。root.dat文件存储着现在系统中已注册的用户名及该用
户所对应的文件分块描述文件所在节点的IP地址,系统利用这一文件可实现用户的注册、认证
及用户登录后获得文件分块描述文件所在节点的IP地址。node.dat文件则维护着整个云计算系统
所有子节点的IP地址、端口、最大空间、剩余空间等信息,客户端通过该文件能够获得整个机
群的信息,从而实现向各子节点的直接连接。客户端从管理节点获得了相关的系统信息后将根
据这一信息直接向各个子节点发起连接,完成文件存储及计算的功能,这大大提高了数据传输
的速率,减轻了管理节点的负荷。各用户文件的具体分块和存储方式被系统用该用户的用户名
(username)作为文件名的文件分块描述文件存储于其中的一个子节点,这一子节点的IP可在
root.dat文件中找到。
图8.1 云计算V0.01的系统结构
在云计算V0.01系统中不同角色间存在两类数据的传送:一类是命令数据CMD,管理节点和子
节点通过命令数据判断自己下一步所要完成的任务;一类是信息数据,这类数据是系统要完成
相关任务所需要数据,如系统描述信息、文件信息等,这类数据的数据量相对较大。由于采用
了计算向存储的迁移策略,系统中出现用户文件数据传输的情况很少,这大大提高了系统的运
行效率。
8.1.2
文件分布式存储流程
系统在进行文件存储时先通过客户端连接管理节点,读取root.dat文件数据,检验是否有该用户
存在,并获取用户数据块文件所在节点的IP地址。通过读取node.dat文件从管理节点读取子

节点
的IP地址的列表,根据以上信息完成对数据的分割,启动多线程函数同时连接各子节点将数据
分别保存在各个节点上,最后更新username表以备访问时重新找到文件的分布情况。uesername
文件将被存储于某一节点上,管理节点会根据现有username文件的分布情况向用户分配一个节
点的IP地址存放username文件,文件名就是该用户的用户名,由于用户名在系统中是惟一的,
所以每个用户的username也是惟一的,不会造成混乱,如图8.2所示。
图8.2 文件的分布式存储流程
8.1.3
计算与存储的整合流程
如图8.3所示,在云计算V0.01系统中,我们利用获得的用户名、文件名、数据块号以及数据分
块信息文件的IP地址信息,可以惟一地确定任一数据块的位置和文件名,客户端同时向各个子
节点发送启动计算的命令,各节点就地读取数据块本地文件,并对其进行计算,计算完成后发
送回客户端汇总得到最后的结果。
图8.3 计算与存储的整合流程
这一计算过程不用移动任何数据,对于数据的处理就在存储数据块的节点完成,系统根据客户
端的指令,将计算迁移到节点上分布式的完成,大大提高了计算效率,避免了数据在网络中的
大量流动所造成的效率下降,对于海量数据来说,这一做法的效果是相当明显的。对于不同的
数据,我们可以定义不同的数据处理方法,从而扩展系统的应用领域。
8.2
管理节点程序设计与分析
管理节点在云计算V0.01系统中只与客户端进行通信,存储系统中的节点信息和用户注册信息,
并不负责任务的调度工作。
在管理节点我们将保存root.dat和node.dat两个系统信息文件。root.dat文件保存用户名及该用户文
件系统所在的子节点的IP地址,采用userInfo数据结构来描述。客户端程序只有获得了子节点IP
地址才能和该子节点直接建立连接获取子节点上的用户存储情况文件,该文件的文件名就是用
户的用户名,通过这一个文件可以知道数据的具体存储情况,从而由客户端直接与数据块存储
节点建立连接。node.dat文件保存了云计算系统的所有子节点的IP地址、总空间和可用空间等信
息,由nodeInfo数据结构来描述。
管理节点各函数调用关系如图8.4所示。
图8.4 管理节点函数调用结构
8.2.1
管理节点服务器程序主函数
管理节点主程序为整个管理程序的主入口,通过4个线程函数来监听并完成客户端所提交的任
务。
程序8.1
/*文件名mainsever.cpp*/
// 定义管理节点服务器程序的入口点
void test();
int _tmain(int argc, _TCHAR* argv[])
{
openfile(); //获得root.dat、node.dat的文件指针
test(); //该函数完成对主服务器的配置工作
WSADATA wsa;
int ret = 0;
int

PORT = 5100;
SOCKET s_socket;
SOCKET c_socket;
struct sockaddr_in s_sockaddr;
struct sockaddr_in c_sockaddr;
int c_sockaddr_len = sizeof(c_sockaddr);
ret = WSAStartup(MAKEWORD(2, 2), &wsa);
if(ret != 0)
{
cout << "Init WinSock failed:" << ret << endl;
return 0;
}
if((s_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR)
{
cout << "Create Socket Failed:" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
s_sockaddr.sin_family = AF_INET;
s_sockaddr.sin_port = htons(PORT);
s_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(s_socket, (SOCKADDR *)&s_sockaddr, sizeof(s_sockaddr)) == SOCKET_ERROR)
{
cout << "bind failed:" << WSAGetLastError() << endl;
closesocket(s_socket);
WSACleanup();
return 0;
}
if(listen(s_socket, 5) == SOCKET_ERROR)
{
cout << "Listen Failed:" << WSAGetLastError() << endl;
closesocket(s_socket);
WSACleanup();
return 0;
}
CMD cmd;
//进入循环接收来自客户端的操作命令cmd
while(1)
{
c_socket = accept(s_socket, (SOCKADDR *)&c_sockaddr, &c_sockaddr_len);
recv(c_socket, (char *)&cmd, sizeof(CMD), 0);//从客户端获取操作命令
SOCKET *tsock = (SOCKET *)malloc(sizeof(SOCKET));
*tsock = c_socket;
switch(cmd)
{
case USERADD:
_beginthreadex(NULL, 0, &useraddthread, tsock, 0, NULL);//进入增加用户线程
break;
case USERGET:
_beginthreadex(NULL, 0, &usergetthread, tsock, 0, NULL);//进入获取用户信息
线程
case USERDEL:
_beginthreadex(NULL, 0, &userdelthread, tsock, 0, NULL);//删除用户,暂未定

case GETNODE:
_beginthreadex(NULL, 0, &getnodeaddrthread, tsock, 0, NULL);//进入获取节点
信息线程
default:
break;
}
}
cin.get();
cin.get();
return 0;
}
//test()函数用于配置各子节点的IP、端口等信息,并将信息存储到管理节点的node.dat文件
void test()
{
cout << "输入地址信息" << endl;
char ip[16];
int port = 5101;
while(1)
{
cout << "输入IP" << endl;
scanf("%s",ip);
if(strcmp(ip,"0") == 0)
break;
if(nodeadd(ip, port, 1024*10) == 0)
cout << "该记录已经存在" << endl;
}
cout << "主服务器配置完成" << endl;
}
上面的管理服务器主程序在5100端口监听来自客户端的连接及操作命令,其命令由cmd定义,
根据从客户端接收到的命令进入不同的处理线程,在本系统已实现的功能有添加用户功能、获
取用户信息功能和获取节点信息功能。管理节点服务器在启动时就调用test()函数完成对子节点
信息的添加及配置。节点信息被存到node.dat文件,用户信息被存到root.dat。在用户需要进行数
据访问时将读取这两个文件获得当前云计算系统所有已接入系统的子节点,并判断当前用户是
否存在,如存在则返回其文件分配信信息存储位置。
我们要注意的是在现在这个系统中管理节点并不和任何的子节点进行通信,也不直接协调子节
点的工作。用户在获取相关信息

后将直接与子节点联系进行操作和数据访问,这样可以大大减
轻主节点的负载。
在管理节点程序中我们定义了一些常用的数据结构主要有userInfo,该数据结构与root.dat文件有
关,包括用户名、用户数据配置文件所在节点的IP地址等信息,因为用户数据文件的信息没有
存放在管理节点而是放在某一个子节点上的,客户端要访问文件数据首先要从管理节点的
root.dat文件中获取该用户数据配置文件所在节点的IP地址,再通过该IP地址获取数据的存储信
息。另一个数据结构是nodeInfo,该数据结构与node.dat文件有关,包括节点IP、端口、节点总
容量、可用空间等,系统中所有节点的相关内容都采用这一数据结构存入node.dat文件。
主节点服务器程序部分参数及数据结构的定义如程序8.2所示。
程序8.2
/*文件名melem.h*/
#define USERADD 1 //添加用户
#define USERGET 2 //获取用户信息
#define USERDEL 3
#define GETNODE 4 //获取节点信息
#define BLOCKSIZE 1024//定义文件块大小
typedef int CMD; //CMD为命令标识
/*此结构体写入root.dat文件*/
typedef struct
{
char user[20]; //用户名
char ip[16]; //用户数据配置文件所在节点的IP地址
int port;
}userInfo;
/*此结构体写入node.dat文件*/
typedef struct
{
char ip[16]; //存储节点的IP地址
int port; //端口号
int userNum; //用户数目
_off_t totalsize; //节点的总容量
_off_t freesize; //节点的可用空间
}nodeInfo;
typedef struct
{
char ip[16];
int port;
}nodeaddr;
8.2.2
管理节点各线程函数的设计
管理节点程序的功能基本是由其定义的4个线程函数(其中删除用户未定义)提供的,这些函数
的功能主要是完成用户信息的注册、服务器节点信息的管理和用户数据配置信息的管理。管理
节点可以看作是一个信息的中转站,其自身并不完成任何的数据操作和处理工作。
各线程的函数定义如下。
1
.增加新用户线程函数
程序8.3
/*文件名mthread.cpp*/
/*增加新的用户,用于新用户注册功能*/
unsigned _stdcall useraddthread(LPVOID p)
{
SOCKET* tsock = (SOCKET *)p;
struct sockaddr_in taddr;
int len = 20;
char* username = (char *) malloc (len * sizeof(char));
recv(*tsock, username, len, 0);//从客户端接收用户名
int result = useradd(username);
send(*tsock, (char *)&result, sizeof(int), 0);
closesocket(*tsock);
free(username);
free(tsock);
return 1;
}
函数useraddthread()接收来自于客户端的新注册用户名,调用useradd()函数将用户名及存储该用
户文件信息的节点IP地址写入主节点的root.dat文件,客户端访问该文件可以获得某一用户的文
件存储信息文件所在的子节点IP地址,该文件的文件名就是用户名。
2
.获取用户信息线程函数
程序8.4
/*文件名mthread.cpp*/
/*客

户端获取用户信息文件*/
unsigned _stdcall usergetthread(LPVOID p)
{
SOCKET* tsock = (SOCKET *)p;
struct sockaddr_in taddr;
int len = 20;
char* username = (char *) malloc (len);
userInfo *user = (userInfo *) malloc (sizeof(userInfo));
recv(*tsock, username, len, 0);
cout << "接收到的用户名:" << username << endl;
/*根据用户名读取root.dat中的用户及对应存储文件描述信息的子节点IP*/
if(userget(username, user) == 0)
{
cout << "get false" << endl;
strcpy(user->user, "0");
strcpy(user->ip,"0.0.0.0");
user->port = 0;
}
cout << "int_thread:" << user->user << " " << user->ip << ":" << user->port << endl;
send(*tsock, (char *)user, sizeof(userInfo), 0);
closesocket(*tsock);
free(username);
free(user);
return 1;
}
函数usergetthread()接收来自客户端的用户名,并在root.dat文件中查找该用户名对应的userInfo
数据结构,并传回给客户端。其中调用userget()函数从root.dat读取与username对应的userInfo信
息。通过调用这一函数客户端可以知道对应用户的文件描述文件存储在哪个子节点上,通过连
接该子节点获取文件的具体存储方式。客户端在调用一这函数后将通过返回的IP地址与存储了
数据分块描述情况文件的子节点进行连接。
3
.文件分块线程函数
程序8.5
/*文件名mthread.cpp*/
/*根据node.dat文件中的节点信息,将数据块分配到各节点*/
unsigned _stdcall getnodeaddrthread(LPVOID p)
{
SOCKET* tsock = (SOCKET *)p;
int blocknum = 0;
nodeInfo *node = (nodeInfo *) malloc (sizeof(nodeInfo));
nodeaddr *naddr = (nodeaddr *) malloc (sizeof(nodeaddr));
recv(*tsock, (char *)&blocknum, sizeof(int), 0); //获取数据块个数
for(int i = 0; i < blocknum; i++)
{
memset(node, 0, sizeof(nodeInfo));
memset(naddr,0, sizeof(nodeaddr));
datagetnode(node);//获取该数据块的存储位置
naddr->port = node->port;
strcpy(naddr->ip, node->ip);
send(*tsock, (char *)naddr, sizeof(nodeaddr), 0); //该数据块的存储位置发送回客户端
}
closesocket(*tsock);
free(node);
free(naddr);
free(tsock);
return 1;
}
函数getnodeaddrthread()接收来自客户端的数据块个数,并为每个数据块分配一个存储节点,数
据块的分配通过调用datagetnode()函数来完成,该函数按剩余空间最大的策略向各数据块分配存
储节点,并将分配结果发送回客户端,由客户端根据接收到的子节点IP直接连接子节点来完成
文件的存储工作,并将存储结果写入子节点中的数据分块描述文件中,以便系统在下次读取时
获得数据的存储情况。
8.2.3
主服务器中其他函数的设计
主服务器其他函数定义。
程序8.6
/*文件名mfile.cpp*/
/*向node.dat文件添加节点信息*/
int nodeadd(char* ip, int port, _off_t totalsize)
{
nodeInfo *temp = (nodeInfo *) malloc (sizeof(nodeInfo));
fseek(fp_node, 0, 0);
/*判断该节点是否已存

在*/
while( fread(temp, sizeof(nodeInfo), 1, fp_node) == 1)
{
if(strcmp(temp->ip, ip) == 0 && temp->port == port)
{
free(temp);
return 0;
}
}
strcpy(temp->ip, ip);
temp->port = port;
temp->userNum = 0;
temp->totalsize = totalsize;
temp->freesize = totalsize;
/*写入node.dat文件*/
fwrite(temp, sizeof(nodeInfo), 1, fp_node);
fseek(fp_node, 0, 0);
fflush(fp_node);
free(temp);
return 1;
}
/*分配用户数据描述文件存储节点的IP*/
int usergetnode(nodeInfo *node)
{
nodeInfo *temp = (nodeInfo *) malloc (sizeof(nodeInfo));
fseek(fp_node, 0, 0);
int num = 0;
int no = 0;
/*寻找已分配uesername文件最少的节点IP*/
if(fread(node, sizeof(nodeInfo), 1, fp_node) != 1)
return 0;
while( fread(temp, sizeof(nodeInfo), 1, fp_node) == 1)
{
++num;
if(temp->userNum < node->userNum)
{
*node = *temp;
no = num;
}
}
memset(temp, 0, sizeof(nodeInfo));
*temp = *node;
temp->userNum += 1;
fseek(fp_node, no*sizeof(nodeInfo), 0);
fwrite(temp, sizeof(nodeInfo), 1, fp_node);
fseek(fp_node, 0, 0);
fflush(fp_node);
free(temp);
return 1;
}
/*将username及对应存储数据描述文件的节点IP写入root.dat文件*/
int useradd(char* username)
{
userInfo *temp = (userInfo *) malloc (sizeof(userInfo));
nodeInfo *node = (nodeInfo *) malloc (sizeof(nodeInfo));
fseek(fp_user, 0, 0);
while( fread(temp, sizeof(userInfo), 1, fp_user) == 1)
/*检测该用户名是否存在*/
if(strcmp(temp->user, username) == 0)
{
free(temp);
free(node);
return 0; //该用户名已经存在
}
if(usergetnode(node) == 0)//获取该用户数据描述文件存储节点的IP,通过node参数传出
return -1; //获取节点失败
memset(temp, 0, sizeof(userInfo));
strcpy(temp->user, username);
strcpy(temp->ip, node->ip);
temp->port = node->port;
/*将userInfo信息写入root.dat文件,确认数据块描述文件的存储位置*/
fwrite(temp, sizeof(userInfo), 1, fp_user) ;
fseek(fp_user, 0, 0);
fflush(fp_user);
free(temp);
free(node);
return 1;
}
/*根据用户名,读取root.dat文件中的userInfo数据*/
int userget(char* username, userInfo *user)
{
userInfo *temp = (userInfo *) malloc (sizeof(userInfo));
fseek(fp_user, 0, 0);
while( fread(temp, sizeof(userInfo), 1, fp_user) == 1)
if(strcmp(temp->user, username) == 0)
{
*user = *temp;
return 1;
}
return 0;
}
/*根据node.dat文件获取系统中的节点信息nodeInfo,并按最大剩余空间优先策略分配给数据块
*/
/*分配完成后更新node.dat文件的内容*/
int datagetnode(nodeInfo *node)
{
nodeInfo *temp = (nodeInfo *) malloc (sizeof(nodeInfo));
fseek(fp_node, 0, 0);
int num = 0;
int no = 0;
if(fread(node, sizeof(nodeInfo), 1, fp_node) != 1)
return 0;
/*首先向剩余空间最大的节点分配数据块*/
while( fread(temp, sizeof(nodeInfo), 1, fp_node) == 1)
{
++num;
if(temp->freesize > node->freesize)
{
*node = *temp;
no = num;
}
}
memset(temp, 0, sizeof(nodeInfo));
*temp = *node;
temp->freesize -=

BLOCKSIZE;
fseek(fp_node, no*sizeof(nodeInfo), 0);
/*将分配后的信息写回node.dat文件,更新文件信息*/
fwrite(temp, sizeof(nodeInfo), 1, fp_node);
fseek(fp_node, 0, 0);
fflush(fp_node);
free(temp);
return 1;
}
/*获得rood.dat和node.dat的文件指针*/
/*fp_user指向root.dat文件*/
/*fp_node指向node.dat文件*/
void openfile()
{
if( (fp_user = fopen("root.dat", "rb+")) == NULL)
fp_user = fopen("root.dat", "wb+");
if( (fp_node = fopen("node.dat", "rb+")) == NULL)
fp_node = fopen("node.dat", "wb+");
}
8.3
子节点程序分析
云计算V0.01中子节点主要完成数据的存储及用户文件存储信息的保存,各用户文件存储信息的
文件名为该用户的用户名,子节点程序需要在所有的节点上同时运行。文件的存储和访问由客
户端与子节点直接连接来完成,子节点和管理节点不进行任何的数据通信工作。
各数据块存储的文件名为username_filename_blockNo,即用户名加文件名加数据块号,之间用
下划线连接。
子节点函数调用结构如图8.5所示。
图8.5 子节点函数调用结构
8.3.1
子节点主函数
子节点的主程序启动后监听来自于客户端发过来的命令cmd,根据不同的命令进入不同的线程
进行处理。
程序8.7
/*文件名blocksever.cpp*/
// blocksever.cpp : 定义控制台应用程序的入口点
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsa;
int ret = 0;
int PORT = 5101;
SOCKET s_socket;
SOCKET c_socket;
struct sockaddr_in s_sockaddr;
struct sockaddr_in c_sockaddr;
int c_sockaddr_len = sizeof(c_sockaddr);
ret = WSAStartup(MAKEWORD(2, 2), &wsa);
if(ret != 0)
{
cout << "Init WinSock failed:" << ret << endl;
return 0;
}
if((s_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR)
{
cout << "Create Socket Failed:" << WSAGetLastError() << endl;
WSACleanup();
return 0;
}
s_sockaddr.sin_family = AF_INET;
s_sockaddr.sin_port = htons(PORT);
s_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(s_socket, (SOCKADDR *)&s_sockaddr, sizeof(s_sockaddr)) == SOCKET_ERROR)
{
cout << "bind failed:" << WSAGetLastError() << endl;
closesocket(s_socket);
WSACleanup();
return 0;
}
if(listen(s_socket, 5) == SOCKET_ERROR)
{
cout << "Listen Failed:" << WSAGetLastError() << endl;
closesocket(s_socket);
WSACleanup();
return 0;
}
CMD cmd;
while(1)
{
c_socket = accept(s_socket, (SOCKADDR *)&c_sockaddr, &c_sockaddr_len);
recv(c_socket, (char *)&cmd, sizeof(CMD), 0);
SOCKET *tsock = (SOCKET *)malloc(sizeof(SOCKET));
*tsock = c_socket;
/*获取用户文件存储信息,该文件存储于其中一个子节点*/
/*用户根据管理节点提供的IP,直接连接该子节点获取*/
if(cmd == USERINFOGET)
_beginthreadex(NULL, 0, &userinfogetthread, tsock, 0, NULL);
else if(cmd == FILEINFOADD)
_beginthreadex(NULL, 0, &fileinfoaddthread, tsock, 0, NULL);//添加文件存储
信息
else if(cmd == STOREBLOCK)
_begin

threadex(NULL, 0, &storedatathread, tsock, 0, NULL); //存储数据块
else if(cmd == GETDATA)
_beginthreadex(NULL, 0, &getdatathread, tsock, 0, NULL); //完成计算工作
else
continue;
}
return 0;
}
子节点的主函数的功能是启动对客户端发出命令的监听和执行,主要是完成4项工作,分别由4
个不同的线程函数来完成。一是获取文件存储信息;二是在文件分块描述文件中添加信息;三
是实现数据块的存储工作;四是在各节点启动计算,完成存储与计算的整合工作。
子节点程序要用到的一些常用数据结构定义如下。
程序8.8
/*文件名belem.h*/
#ifndef BELEM_H
#define BELEM_H
#include "stdafx.h"
#define USERNAME_LEN 20
#define FILENAME_LEN 64
#define BLOCKNAME_LEN 128
#define LISTLEN 5
#define MAINIP "127.0.0.1"
#define USERADD 1
#define USERGET 2
#define USERDEL 3
#define USERINFOGET 5
#define FILEINFOADD 6
#define STOREBLOCK 7
#define GETDATA 8
typedef int CMD;
/*fileInfo数据结构为子节点上用户数据文件描述的内容*/
typedef struct
{
char filename[FILENAME_LEN]; //文件名
int blockNO; //数据块号
char ip[16]; //数据块存放节点的IP地址
int port;
}fileInfo;
/*数据块描述数据结构*/
typedef struct
{
int blockNO;
char ip[16];
int port;
}blockInfo;
typedef struct
{
fileInfo *e;
int length;
int size;
}fileInfoList;
#endif
8.3.2
子节点各线程函数设计
1
.获取文件存储信息线程函数
程序8.9
/*文件名bthread.cpp*/
unsigned _stdcall userinfogetthread(LPVOID p) //获取用户信息文件线程
{
SOCKET* tsock = (SOCKET *)p;
char username[USERNAME_LEN];
recv(*tsock, username, USERNAME_LEN, 0);
FILE *fp;
if((fp = fopen(username, "rb")) == NULL)
fp = fopen(username, "wb+"); //打开用户存储信息描述表
struct _stat info;
_stat(username, &info);//获取文件信息
_off_t filelen = info.st_size;
send(*tsock, (char *)& filelen, sizeof(_off_t), 0);
if(filelen > 0)
{
char *buf = (char *) malloc (filelen);
fread(buf, 1, filelen, fp);
send(*tsock, buf, filelen, 0);
free(buf);
}
closesocket(*tsock);
fclose(fp);
return 1;
}
用户从管理节点获取到数据块描述文件所在的节点IP地址后,即可连接该节点并调用此函数获
取文件内容,由于该文件的文件名就是用户名,所以子节点只要知道了用户名即可知道需要打
开的文件名,该节点通过socket接收用户名就是这个原因。
2
.添加数据块描述信息线程函数
程序8.10
/*文件名bthread.cpp*/
unsigned _stdcall fileinfoaddthread(LPVOID p)//添加文件信息线程
{
SOCKET* tsock = (SOCKET *)p;
int listlen = 0;
char filename[FILENAME_LEN];
char username[USERNAME_LEN];
fileInfo ftmp;//定义文件信息数据结构
blockInfo btmp;
fileInfoList L;
initlist(&L);
recv(*tsock, username, USERNAME_LEN, 0); //获取用户名
recv(*tsock, filename, FILENAME_LEN, 0); //获取文件


recv(*tsock, (char *)& listlen, sizeof(int), 0) //获取文件分块个数,即获取顺序表的长度
for(int i = 0; i < listlen; i++) //获取各个数据块的存储信息与在文件中的顺序号,顺序号以0
开始
{
recv(*tsock, (char *)&btmp, sizeof(btmp), 0);//接收数据块信息,包括块号、存储的IP地

strcpy(ftmp.filename, filename);
strcpy(ftmp.ip, btmp.ip);
ftmp.blockNO = btmp.blockNO;
ftmp.port = btmp.port;
addlist(&L, ftmp);//加入顺序表
}
FILE *fp;
if((fp = fopen(username, "rb+")) == NULL)
fp = fopen(username, "wb+");
fileadd(&L, fp);//将文件存储信息的顺序表添加到数据分块文件中,文件名就为用户名
fclose(fp);
int result = 1;
send(*tsock, (char *)&result, sizeof(int), 0);
destroylist(&L);
closesocket(*tsock);
free(tsock);
return 1;
}
添加数据块描述信息线程函数从客户端通过socket接收用户名、文件名及文件分块个数,并将每
个数据块的块号blockNO和存储位置所在IP地址等信息建立顺序表,将此顺序表存到该用户的文
件分块描述文件中。
这一线程函数调用fileadd()函数实现向username添加文件块存储信息,这个信息由fileInfo数据结
构来描述,fileadd()函数定义如下。
程序8.11
/*位于文件bfile.cpp中*/
int fileadd(fileInfoList *L, FILE * fp) //fp为username文件指针
{
char filename[20];
fileInfo file;
strcpy(filename, L->e[0].filename);
if(filesearch(filename, fp) == 0) //检测是否有同名文件
filedel(filename,fp); //如有同名文件则删除并覆盖该文件
fseek(fp, 0, SEEK_END);
for(int i = 0; i < L->length; i++)
{
file = L->e[i];
fwrite(&file, sizeof(fileInfo), 1, fp);
}
return 1;
}
/*在username文件中检测是否有同名文件*/
int filesearch(char* filename, FILE *fp)
{
fileInfo ftmp;
fseek(fp, 0, 0);
while( fread(&ftmp, sizeof(fileInfo), 1, fp) == 1)
if(strcmp(ftmp.filename, filename) == 0)
return 0;
return 1;
}
/*覆盖同名文件*/
int filedel(char* filename, FILE *fp)
{
fileInfo ftmp;
fseek(fp, 0, 0);
while(!feof(fp))
{
fread(&ftmp, sizeof(fileInfo), 1, fp);
if(strcmp(ftmp.filename, filename) == 0)
{
strcpy(ftmp.filename, "0");
fseek(fp, ftell(fp)-sizeof(fileInfo),0);
fwrite(&ftmp, sizeof(fileInfo), 1, fp);
fseek(fp,ftell(fp), 0);
}
}
return 1;
}
在文件存储信息的添加过程中我们采用顺序表来维护各数据块的存储信息,因此定义了几个基
本的顺序表操作。
程序8.12
/*位于文件blist.cpp*/
/*初始化顺序表函数*/
int initlist(fileInfoList *L)
{
L->e = (fileInfo *) malloc (LISTLEN * sizeof(fileInfo));
if(L->e == NULL)
{
printf("初始化表,分配内存失败");
return 0;
}
L->length = 0;
L->size = LISTLEN;
return 1;
}
/*向顺序表中加入一个数据块存储信息*/
int addlist(fileInfoList *L, fileInfo e)
{
if(L->length >= L->size)
{
fileInfo *newbase = (fileInfo *) realloc (L->e, (L->size + LISTLEN)*size

of(fileInfo));
if(newbase == NULL)
{
printf("向表添加数据,增加内存空间失败");
return 0;
}
L->e = newbase;
L->size += LISTLEN;
}
L->e[L->length] = e;
++ L->length;
return 0;
}
/*释放存储空间*/
int destroylist(fileInfoList *L)
{
L->length = 0;
L->size = 0;
free(L->e);
return 0;
}
3
.保存数据块线程函数
程序8.13
/*文件名bthread.cpp*/
unsigned _stdcall storedatathread(LPVOID p)//保存数据块
{
SOCKET* tsock = (SOCKET *)p;
int datalen = -1;
int blockNo = -1;
char filename[FILENAME_LEN];
char username[USERNAME_LEN];
recv(*tsock,username, USERNAME_LEN, 0);
recv(*tsock, filename, FILENAME_LEN, 0);
recv(*tsock, (char *)& blockNo, sizeof(int), 0);
recv(*tsock,(char *)& datalen, sizeof(int), 0);
char *buf = (char *) malloc (datalen);
recv(*tsock,buf, datalen, 0);
cout << "storedatathread blockNo:" << blockNo << endl;
cout << "storedatathread buflen:" << datalen << endl;
char blockname[BLOCKNAME_LEN]; //数据块文件名
sprintf(blockname, "%s_%s_%d", username, filename, blockNo);//生成数据块文件名
FILE *fp;
fp = fopen(blockname, "w+"); //打开数据块文件
fwrite(buf, 1, datalen, fp); //写入信息
fclose(fp);
free(buf);
closesocket(*tsock);
free(tsock);
return 1;
}
本函数完成对各数据块的存储工作,客户端根据管理节点的数据块存储安排,获取各数据块存
储节点的IP地址,由客户端直接连接各存储节点,调用storedatathread()函数,本函数获取数据
块的所属用户名、文件名、块号、块大小和数据块的具体内容,将数据块存储在当前节点,各
个数据块的命名原则为用户名_文件名_块号(username_filename_blockNO),这样任何一个数据
块将有一个惟一的文件名,该数据块的存储信息同时被写入了数据块描述文件。虽然文件被分
割为多个数据块并存储在不同的子节点上,但由于有数据块描述文件,我们可以在任何时候恢
复并访问该文件。
4
.获取数据计算结果线程函数
程序8.14
/*文件名bthread.cpp*/
unsigned _stdcall getdatathread(LPVOID p)//获取数据计算结果
{
SOCKET* tsock = (SOCKET *)p;
int datalen = 0;
int blockNo = 0;
_off_t result = 0;
char filename[FILENAME_LEN];
char username[USERNAME_LEN];
recv(*tsock,username, USERNAME_LEN, 0);
recv(*tsock, filename, FILENAME_LEN, 0);
recv(*tsock, (char *)& blockNo, sizeof(int), 0);
FILE *fp;
char blockname[BLOCKNAME_LEN];//数据块文件名
sprintf(blockname, "%s_%s_%d", username, filename, blockNo);
fp = fopen(blockname, "r"); //打开本节点上的数据块文件
getresult(&result, fp); //在本节点完成计算工作
fclose(fp);
cout << "num:" << result << endl;
send(*tsock, (char *)& result, sizeof(_off_t), 0);
closesocket(*tsock);
free(tsock);
return 1;
}
获取数据计算结果线程函数实现了在这个云计算系统中计算与存储的整合演示,我们以对一串

数据求和操作为例,客户端根据数据块存储描述文件的描述向各个存储有数据块的节点发送用
户名、文件名和数据块号,这些信息实际上就确定了一个数据块文件的位置,存储每个数据块
的节点就在该节点就地启动对该数据块的计算工作,完成计算后将自己的局部计算结果发送到
客户端,由客户端完成数据的最后整合工作。这样就避免了大量数据在机群中的移动,大大提
高系统的工作效率,体现了“计算向存储迁移”的云计算理念。这一线程函数中的getresult()函数
就是完成计算工作的函数,根据不同的数据和工作需要我们定义不同的计算函数,这一函数必
需在所有的节点上都要驻留,这样系统才能将计算向该节点迁移。函数getresult()在本例中的定
义如下。
程序8.15
/*位于文件bfile.cpp中*/
/*完成对本节点数据块内数据的求和操作*/
int getresult(_off_t *num, FILE *fp)
{
fseek(fp, 0, 0);
char c;
int i = 0;
int j = 0;
while(!feof(fp))
{
c = fgetc(fp);
cout << c;
j = 0;
while(!feof(fp))
{
i = 0;
if(c >= '0'&& c <= '9')
{
i = atoi(&c);
j = 10*j +i;
}
c = fgetc(fp);
cout << c;
if(c < '0'|| c > '9')
break;
}
*num += j;
}
return 1;
}
我们的计算数据是按字符串存储,函数getresult()对本节点数据块实施求和操作,我们同样也定
义其他的数据处理函数,如类似wordcount函数等,通过这一函数我们实现对数据的本地化处理,
每个getresult()函数只处理本节点所存储数据块的数据。这一函数演示了计算向存储的迁移过程。
8.4
客户端API
设计
云计算V0.01为客户端提供了可以访问的API函数,从负载上我们给客户端增加了一些负载,将
文件的分块等工作交给了客户端来完成,只是将计算工作交给子节点来完成。客户端的主要工
作模式是从管理节点读取系统信息,根据这一信息与各子节点直接进行联系完成文件操作及计
算任务。
下面是系统提供的主要API函数,分别在capi.cpp和cfun.cpp中定义。
int cwrite(userInfo user, char* filename, FILE *fp, FILE *fp_tmp); //将数据写入节点
int getdata(char* username, char* filename, FILE *fp_tmp);
int creatconnect(char* ip, int port, SOCKET *csock); //链接服务器,成功返回1,失败返回0
int userregister(char* username); //用户注册
int userlogin(char* username, userInfo *user, FILE *fp); //用户登录
int getuserfile(userInfo user, FILE *fp); //从节点获取用户信息文件
int getuseraddr(userInfo *user); //从主节点获取用户信息文件存放地址
int getnode(nodeaddrList *L, int num); //从主节点分配节点地址
int userinfoadd(fileInfoList *L, userInfo user, FILE *fp_tmp);//用户信息文件中添加一个文件信息
8.4.1
客户端文件的存储
客户端文件的存储是通过调用API函数cwr

ite()来实现的,它在实际调用时采用如下的形式:
cwrite(user, "data.txt", fp, fp_t);
函数参数中user为文件所属用户的用户名,data.txt为需要存储的数据文件,fp为该文件的指针,
fp_t是临时文件的指针。F数据结构存储有本次文件分割后所有数据块的存储信息,即username
文件中的信息。函数cwrite()同时把该信息写入子节点和本地的临时文件中。
API函数cwrite()的定义如下。
程序8.16
/*保存在文件capi.cpp中*/
/*实现文件分布式存储功能的API*/
int cwrite(userInfo user, char* filename, FILE *fp, FILE *fp_tmp)
{
cout << "In cwrite " << endl;
datalist D;
threadelemlist T;
threadlist H;
initdatalist(&D);
initthreadelemlist(&T);
initthreadlist(&H);
filecut(&D, fp);//文件分块
blockdatalist(&D, &T, https://www.wendangku.net/doc/d44868327.html,er, filename);
HANDLE hd;
for(int i = 0; i < T.length; i ++)
{
hd =(HANDLE) _beginthreadex(NULL, 0, &storedatathread, &(T.e[i]), 0, NULL);
addthreadlist(&H,hd);
}
WaitForMultipleObjects(H.length, H.e, true, INFINITE);
fileInfo finfo;
fileInfoList F;
initFInfolist(&F);
/*建立文件分块信息列表*/
for(int i = 0; i < T.length; i++)
{
strcpy(finfo.filename, filename);
strcpy(finfo.ip, T.e[i].ip);
finfo.port = T.e[i].port;
finfo.blockNO = T.e[i].blockNO;
addFInfolist(&F, finfo);
}
/*fileInfoList数据结构存储有该用户所有文件的分块信息*/
userinfoadd(&F,user, fp_tmp);
destroyFInfolist(&F);
destroydatalist(&D);
destroythreadelemlist(&T);
destroythreadlist(&H);
return 1;
}
文件存储API函数在完成对数据结构的初始化后,首先调用文件分块函数filecut(),分块函数对
fp所指向的文件进行分块,分块数据由datalist数据结构来维护,datalist数据结构描述如下。
程序8.17
/*保存在文件celem.h中*/
/*数据块数据结构*/
typedef struct
{
char buf[BLOCKSIZE]; //数据块缓冲区
int datalen; //数据块大小
int blockNO; //块号
}data;
/*数据块顺序表结构*/.
typedef struct
{
data *e; //数据块结构体数组指针
int length; //数据块个数
int size; //数据块大小
}datalist;
数据存储API调用filecut()函数后,实现将文件的分块信息写入datalist表,分块完成后datalist表的
内容将如图8.6所示。
图8.6 分块完成后datalist表的内容
程序8.18
/*保存在文件cfile.cpp中*/
/*实现文件分块功能*/
int filecut(datalist *L, FILE *fp)
{
char c;
data d;
memset(&d, 0, sizeof(data));
d.blockNO = 0; //对数据块的块号赋初值
d.datalen = BLOCKSIZE;
int i = 0;
fseek(fp, 0, 0);
/*生成数据块*/
while(!feof(fp))
{
++d.blockNO; //数据块号增加
d.datalen = BLOCKSIZE;
for(i = 0; i < BLOCKSIZE; i ++)
{
c = fgetc(fp);
d.buf[i] = c;
}
/*将分块信息写入datalist表*/
if(feof(fp))
{
d.datalen = i;
adddatalist(L, d); //向数据块表中增加一个数据块记录
break;
}
c = fgetc(fp);
fseek(fp, -1L, 1);
while(1)


{
if(c<'0'&& c> '9')
break;
if(d.buf[i-1]<'0'|| d.buf[i-1]> '9')
break;
else
{
--i;
--d.datalen;
fseek(fp, -1L, 1);
}
}
adddatalist(L, d);
}
return 1;
}
文件的分割完成后cwrite()函数调用storedatathread()函数启动向各子节点的写入操作,完成数
据的分块存储。storedatathread()函数的定义如下。
程序8.19
/*文件存放位置cthread.cpp*/
unsigned _stdcall storedatathread(LPVOID p)
{
threadelem *t = (threadelem *)p;
WSADATA wsa;
SOCKET csock;
int ret = 0;
ret = WSAStartup(MAKEWORD(2, 2), &wsa);
if(ret != 0)
{
cout << "Init WinSock failed:" << ret << endl;
return 0;
}
csock = socket(AF_INET, SOCK_STREAM, 0);
CMD cmd;
if(creatconnect(t->ip, t->port, &csock) == 0)
{
cout << "链接节点服务器失败" << endl;
closesocket(csock);
WSACleanup();
return 0;
}
cmd = STOREBLOCK;
send(csock, (char *)&cmd, sizeof(CMD), 0);
send(csock, t->username, USERNAME_LEN, 0);
send(csock, t->filename, FILENAME_LEN, 0);
send(csock, (char *)&(t->blockNO), sizeof(int), 0);
send(csock, (char *)&(t->datalen),sizeof(int), 0);
send(csock, t->data, t->datalen, 0);
closesocket(csock);
WSACleanup();
return 1;
}
函数storedatathread()向子节点发送的命令cmd为STOREBLOCK,该命令将启动子节点的存储
任务完成数据块的存储工作,同时客户端向子节点发送用户名、文件名等信息。
存储API函数cwrite()在最后调用userinfoadd()函数将文件分块信息写入节点服务器的usrename文
件并写入本地临时文件,从而完成整个文件分布式存储工作。
程序8.20
/*文件保存位置cfun.cpp*/
int userinfoadd(fileInfoList *L, userInfo user,FILE *fp_tmp)/*文件分块信息写入节点服务器的
usrename文件并写入本地临时文件*/
{
WSADATA wsa;
SOCKET blocksock;
int ret = 0;
ret = WSAStartup(MAKEWORD(2, 2), &wsa);
if(ret != 0)
{
cout << "Init WinSock failed:" << ret << endl;
return 0;
}
blocksock = socket(AF_INET, SOCK_STREAM, 0);
if(creatconnect(user.ip, user.port, &blocksock) == 0)
{
cout << "链接节点服务器失败" << endl;
closesocket(blocksock);
WSACleanup();
return 0;
}
CMD cmd = FILEINFOADD;
char filename[FILENAME_LEN];
strcpy(filename, L->e[0].filename);
int listlen = L->length;
blockInfo btmp;
send(blocksock, (char *)&cmd, sizeof(CMD), 0); //发送添加文件信息命令
send(blocksock, https://www.wendangku.net/doc/d44868327.html,er, USERNAME_LEN, 0); //发送用户名
send(blocksock, filename, FILENAME_LEN, 0); //发送文件名
send(blocksock, (char *)&listlen, sizeof(int), 0); //发送表长度
for(int i = 0; i < listlen; i++)
{
strcpy(btmp.ip, L->e[i].ip);
btmp.port = L->e[i].port;
btmp.blockNO = L->e[i].blockNO;
send(blocksock, (char *)& btmp, sizeof(blockInfo), 0);//向子节点发送数据分块信息
}
int result = 0;
recv(blocksock, (char *)&result, sizeof(int), 0);
if(result != 1)
{
cout << "在节点上添加文件信息失败" << endl;
return 0;
}
fileadd(L, fp_tmp);//将

文件信息添加到本地临时文件
return 1;
}
函数userinfoadd()写入节点上的username文件中的内容如图8.7所示,是各数据块所属文件、存储
位置的IP、文件块号等信息,根据这些信息我们能找到所有该文件的数据块并对其进行计算处
理。
图8.7 函数userinfoadd()写入节点上的username文件中的内容
8.4.2
客户端启动子节点计算
客户端启动子节点计算的API函数调用为getdata(),它实际调用时是如下的形式
getdata(username, "data.txt", fp_t);
参数username为用户名,data.txt为数据的文件名,fp_t是本地临时保存文件分块信息的文件指针。
函数getdata()的具体定义如下。
程序8.21
/*文件保存位置capi.cpp*/
int getdata(char* username, char* filename, FILE *fp_tmp)
{
fileInfo file;
fileInfoList L;
threadlist H;
threadelemlist_r T;
initFInfolist(&L);
initthreadlist(&H);
initthreadelemlist_r(&T);
fileget(filename, &L, fp_tmp);
threadelem_r t;
for(int i = 0; i < L.length; i++)
{
strcpy(https://www.wendangku.net/doc/d44868327.html,ername, username);
strcpy(t.finfo.filename, L.e[i].filename);
strcpy(t.finfo.ip, L.e[i].ip);
t.finfo.port = L.e[i].port;
t.finfo.blockNO = L.e[i].blockNO;
addthreadelemlist_r(&T, t);
}
HANDLE hd;
for(int i = 0; i < T.length; i++)
{
hd =(HANDLE) _beginthreadex(NULL, 0, &getdatathread, &(T.e[i]), 0, NULL);
addthreadlist(&H,hd);
}
WaitForMultipleObjects(H.length, H.e, true, INFINITE);
destroyFInfolist(&L);
destroythreadelemlist_r(&T);
destroythreadlist(&H);
return 1;
}
其中getdatathread()函数向各节点发送计算指令GETDATA,启动各子节点相应的计算程序工作,
并向各节点发送相应的文件信息。getdatathread()函数定义如下。
程序8.22
/*文件存放位置cthread.cpp*/
unsigned _stdcall getdatathread(LPVOID p)//获取计算结果
{
threadelem_r *t = (threadelem_r *)p;
WSADATA wsa;
SOCKET csock;
int ret = 0;
ret = WSAStartup(MAKEWORD(2, 2), &wsa);
if(ret != 0)
{
cout << "Init WinSock failed:" << ret << endl;
return 0;
}
csock = socket(AF_INET, SOCK_STREAM, 0);
CMD cmd;
if(creatconnect(t->finfo.ip, t->finfo.port, &csock) == 0)
{
cout << "链接节点服务器失败" << endl;
closesocket(csock);
WSACleanup();
return 0;
}
cmd = GETDATA;
_off_t result = 0;
send(csock, (char *)&cmd, sizeof(CMD), 0);
send(csock, t->username, USERNAME_LEN, 0);
send(csock, t->finfo.filename, FILENAME_LEN, 0);
send(csock, (char *)&(t->finfo.blockNO), sizeof(int), 0);
recv(csock, (char *)&result, sizeof(_off_t), 0);
NUM += result;
closesocket(csock);
WSACleanup();
return 1;
}
8.4.3
客户端应用的简单实例
下面的程序是一个以控制台程序实现的客户端系统,它实现了云计算V0.01的基本功能。
程序8.23
/*文件名client.cpp*/
#include "stdafx.h"
#include
#include
#include
#include
#include
#include "celem.h"
#include "cfun.h"
#include "cglobal

.h"
#include "cthread.h"
#include "clist.h"
#include "capi.h"
#pragma comment (lib, "wsock32.lib")
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char username[20];
userInfo user;
cout << "用户注册\n输入用户名:" << endl;
scanf("%s",username);
cout << username << endl;
FILE *fp_t;
fp_t = fopen("temp","wb+");//打开temp文件
cout << userlogin(username,&user, fp_t) << endl;
//产生需要计算的数据并写入文件
FILE *fp;
fp = fopen("data.txt","w+");
int m = 0;
for(int i = 0; i < 500; i++)
{
fprintf(fp,"%d ",i);
m += i;
}
fclose(fp);
cout << "" << m << endl;
fp = fopen("data.txt","r+");
fseek(fp_t, 0, 0);
cwrite(user, "data.txt", fp, fp_t);//实现文件的分布式存储
fclose(fp);
/*向各子节点发出计算指令,并汇总结果*/
getdata(username, "data.txt", fp_t);
/*NUM为求和的结果*/
cout << NUM << endl;
cin.get();
cin.get();
return 0;
}
这一程序的输出为对所有数据求和的值,系统先由各节点计算存储在自己节点上的数据之和,
再发送到客户端完成数据的汇总求和工作,一旦文件已实现分布式的存储,中间将不会有任何
数据文件的传输工作,大大加快了计算的进程,实现了计算向存储的迁移。
8.5
客户端应用开发实例
通过客户端API的支持我们可以验证云计算V0.01的部分功能,在前面的讲述中我们已给出过一
个控制台程序的例子,下面这两个示例是我们做的一个带有用户界面的测试系统,实现对一组
数据的求和及求最大值的操作。数据我们采用自己生成的从0开始的一组连续整数,读者可以采
用自己生成的数据来实验。求最大值的操作我们前面没做讲解,读者可自行设计函数来实现这
一计算功能。相关的运行界面如图8.8和图8.9所示,该界面是采用Visual C++编写的C/S架构的对
话框程序。
图8.8 对20000个数据求和的用户界面
图8.9 在20000个数据中求最大值的用户界面
以上的实例只是我们云计算V0.01的一种应用方式,用户可根据自己的需要增加相应的功能,云
计算V0.01是个很不完善的系统,对于可并行化的问题云计算V0.01的这种做法应该是一种可行
的做法,我们更希望读者能在初步理解其结构的基础上以自己的思想开发出自己的云计算实验
系统,我们并不想以此来禁锢读者的思维。
现在很多的企业已经开始向用户提供云计算服务,一个可以实用化的云计算基础架构需用解决
许多关键的技术,云计算时代IT技术人员需要协调有史以来最大规模的服务器群,并保证整个
系统能够持续不断正常运行。云计算时代用户的大量关键数据、关键业务和关键应用被转移到
云端,因此系统的安全性、高可用性、高性能成为对云计算系统的基本要求。
现在的云计算企业良莠不齐,

由于云计算平台技术是一个复杂的系统,技术涉及面相当宽,对
从事云计算研发的企业的技术实力和资金实力有很高的要求,其中真正能在现阶段进行云计算
平台研发的企业并不是太多,现阶段大量号称能提供云计算服务的企业更多的都是将自己原有
的技术和资源中类似云计算的部分称为云计算:如主机租用、网络存储等。
要实现上万台甚至上百万台服务器的协调工作,并向开发者提供丰富的API,满足普通用户和企
业级用户多样化的需求是一件十分困难的问题,所以一个可以实用化的云计算系统是一个复杂
的系统工程,甚至不是一个企业能独立完成的,它需要服务器提供商、存储设备提供商、系统
平台提供商、网络设备提供商、网络带宽提供商、应用开发商共同努力。
从Google的经验和未来服务器群的庞大规模来看,将服务器失效作为云计算系统的服务器模型
是符合实际情况的,这种情况下单个服务器可以看作是不可信的节点,在系统设计时必须要将
不可信服务器节点的失效屏蔽在系统之内,不能向开发者和普通用户传递。
本章将以不可信节点为模型介绍一个云计算基础架构的模型,我们并不奢望这一模型是十全十
美的,只是希望能为读者提供一种构建系统的思路。
9.1
云计算基础架构的应用场景
我们要设计云计算的基础架构首先要确认这一系统的应用场景,应用场景的确认实际就确认了
对云计算系统的总体要求。一个可实用的云计算基础架构一定是面向真实场景的架构,云计算
的生命力就是面向用户、以用户为中心,本章中的云计算基础架构就是以实际的云计算中心为
设计场景,这一场景主要包括以下的要点。
(1)节点数量的规模是十分巨大的,单个节点的失效概率比较大,从而整个系统出现某个节点
失效的概率是相当大的。
这一场景要求系统能对所有节点进行有效监控和协调,及时对节点失效故障作出迅速的报警,
并将故障的详细情况向管理节点汇报,作出相应的数据和计算迁移操作,保证系统的连续运行。
由于系统节点数量十分巨大,所以系统中出现失效节点的概率是很大的,正如我们前面讲过的
原理如果一个节点在一年内的失效机率为0.1%,即每1 000年出现一次失效,在有10 000个节点
的机群中一年内出现有一个节点失效的概率是1 000%,在有1 000 000个节点的机群中一年内出
现有一个节点失效的概率是100 000%,即一年内会出现1 000个节点失效,这个概率就比较大了,
平均每天有3个节点会失效。其实一个节点一年内的失效机率为0.1%是相当低的了,没有哪台服
务器能运行1 000年才出现失效的,

如果这个值加大那每天将有数以百计的节点会失效。
(2)云计算中心可能跨区域地在多个中心之间整合。
由于云计算中心可能会位于不同的地方,中心之间的协调和通讯是系统必须要考虑的问题,而
且由于存在跨区域的云计算中心为数据存储提供了一个比跨机柜更为高级的跨区域数据安全保
证级别,对于安全性要求很高的数据可以提供跨区域级的数据备份,从而也就可以在重大节点
故障发生时实现跨区域的计算和存储的迁移,系统可以实现更高的可用性。
(3)用户在云计算系统上是要同时从事数据密集和计算密集的工作的,而不是单一的存储或计
算工作。
现有的很多云计算系统未实现数据密集应用和计算密集应用同时支持,而今后云计算系统在面
向多样化的用户时,不同需求的用户将在同一个系统运行和工作,有从事科学计算的、有从事
信息挖掘的、有从事简单办公的、有从事图像处理的,这些应用既有数据密集的也有计算密集
的,还有计算和数据同时密集的,所以云计算系统必须要灵活应对这种多样化的需求。
(4)一个云计算基础架构应该具备适应大量不同应用的能力,个人用户、企业级用户、开发者
都能在这个系统中工作。
大量的应用在系统中运行,系统必须要对不同的应用、不同的用户进行有效的软硬件隔离,从
而保证这些业务之间不能出现相互影响,不同用户的数据之间不能相互覆盖。
(5)非云计算基础架构的设计者和提供者是不需要了解任何云计算中心的软硬件情况的,他们
只需要对计算和存储资源按需使用,云计算中心对他们来讲只是一个随需应变的资源池。
对普通用户映射出按需提供的计算和存储资源池是云计算中的一项核心技术,虚拟化的方法是
解决这一问题的重要手段。
以上应用场景为云计算基础架构的设计提出相应的要求,在设计云计算系统时必须考虑其对应
用场景的适应性。当然我们所提供的应用场景并不是一个非常全面的场景,一个商业级的产品
可能需要考虑更为复杂的场景情况。
9.2
云计算基础架构
根据我们在上面提出的云计算应用场景模型,本节提出一个云计算的基本架构,为读者提供参
考。这一架构的框架如图9.1所示。
图9.1 不可信节点的云计算基础框架图
在这一框架下我们着力要解决以下几个问题。
(1)不可信节点出现故障时对用户的隔离。
(2)计算与存储的整合,以适应计算密集和数据密集的任务。
(3)计算资源和存储资源的虚拟化。
(4)应用层不同应用和不同用户之间数据存储和计算的隔离。
(5)应用层接口和操作系统的提供。
我们曾

相关文档