C语言编程常见错误及技巧

1. 使用close函数出现bad file descriptor错误

出现这种错误一般是两次close导致,仔细检查代码,调用close关闭文件描述符后,应该将文件描述符置为0,避免重复调用close,切记切记。

2. 守护程序闪退问题

出现这个问题一般是两次释放同一块内存,仔细检查释放内存与指针赋值相关代码,释放内存后,应将指针置为NULL,避免重复调用close,切记切记。

3. 四字节对齐

4. 什么是四字节对齐

现代计算机中,内存空间按照字节划分,理论上可以从任何起始地址访问任意类型的变量。但实际中在访问特定类型变量时经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序一个接一个地存放,这就是对齐。

4.1 四字节对齐作用

定义结构体成员时应当注意四字节对齐,保证内存利用最大化。但要注意,在32位机中使用1字节或2字节对齐,反而会降低变量访问速度。因此需要考虑处理器类型。还应考虑编译器的类型。在VC/C++和GNU GCC中都是默认是4字节对齐。

5. 柔性数组

定义结构体时,最后一个成员定义为

struct test {
    int a;
    char b[0];
}  

b[0]就称为柔性数组,使用

sizeof(struct test);

返回值为4,即b[0]不占用内存空间。

6. 数字转字符

C语言书籍上可能出现过如下代码将数字转为字符

int a = 0;
char b = a + '0';

这个操作有局限性,当a的值超过9的时候就会得到你意向不到的结果,

int a = 10;
char b = a + '0';     

这个时候字符b的值为’:’

7. fork函数注意事项

fork函数用来产生一个子进程,而且会复制父进程的资源(包括打开的文件描述符,变量等),无论是在子进程还是父进程对打开的文件描述符都需要谨慎对待,调用close函数关闭文件描述符可能不会报错,但是删除文件的时候就会出问题。

8. 变参宏定义

C99定义了一种变参宏定义

#define LOG(format, ...) fprintf(stdout, format, __VA_ARGS__) 

…表示可变参数列表,__VA_ARGS__在预处理中,会被实际的参数集(实参列表)所替换。
同时gcc还支持带可以变参数名的方式(注意:VC不支持,即在vs环境下无法使用):

#define LOG(format, args...) fprintf(stdout, format, args)  

同样,args在预处理过程中,会被实际的参数集所替换。其用法和上面的方式一样,只是参数的符号有变。

9. 整数相除

C语言中,两个int型相除只能得到int型,若结果分子小于分母,则结果为0。必要时,将分子或者分母强转为float型,则结果为float型。

10. 屏蔽gcc编译函数未使用错误

函数定义了未使用在visual studio中,不会报错,但是使用gcc编译时就要报错,如下所示:

1
warning: 'xxx_handler' defined but not used [-Wunused-function]

这个时候需要添加一个编译标志如下

1
CFLAGS="$CFLAGS -Wno-unused-function"

添加上这个标志以后,就不会报错了,常见标志可以参考gcc官网,要特别注意的是no这个标志,如果编译出现其他类似错误,又不想改代码,就可以加对应的标志,不同版本的gcc对于同一份代码可能出现错误。

11. 大端机与小端机

所谓大端就是指高位值在内存中放低位地址,所谓小端是指低位值在内存中放低位地址。比如0x12345678在大端机上是12345678,在小端机上是78563412,而一个主机是大端还是小端要看cpu类型以及运行在上面的操作系统。同一款cpu在不同的操作系统使用的大小端情况是不同的。个人理解0x1234是我们的书写习惯,转成二进制就是‭0001001000110100‬,读数从右往左读,所以0100是低位值,0001是高位值,在计算机中,内存地址增长方向是从左到右,所以左边是低位地址,右边是高位地址,如果计算机的低位地址存的是书写习惯的高位值,那么就是小端机,大小端机对单字节没影响。

12. 主机字节序与网络字节序

主机字节序就是大端序与小端序,网络字节序与大端序相同。在编写网络相关代码时,需要使用函数转换字节序,比如htons,htonl,ntohs,ntohl。

13. 管道

管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,pipe(无名管道)和fifo(命名管道)两种。管道为半双工通信方式,也就是说只能一端读,一端写。

14. 隐式类型转换

unsigned char类型变量左移8位,编译器会进行类型提升。

15. 屏蔽格式转换错误

ubuntu gcc编译报错:

1
format '%llu' excepts argument of type 'long long unsigned int', but argument 2 has type 'uint_64'[-Wformat=]

解决方法:在编译时添加-Wformat=0

Nginx学习笔记

1. 编译步骤

1.1 configure原理

configure本质上是个shell脚本,所以如果要完全理解configure需要熟悉shell基本语法,除此之外,脚本中大量运用了test、sed、cat、echo、grep等命令以及重定向符,所以也需要了解这些命令的用法。

1.2 auto脚本

auto脚本由一系列脚本组成,他们有一些是实现一些通用功能由其它脚本来调用(如have),有一些则是完成一些特定的功能(如option)。脚本之间的主要执行顺序及调用关系如下图所示(由上到下,表示主流程的执行):

上图中的脚本都位于auto目录下,所以省略,而configure与auto同级目录,故完整执行脚本如.auto/options所示。

1.2.1 auto/options脚本

auto/options主是处理用户输入的configure选项,以及输出帮助信息等。auto/options的目的主要是处理用户选项,并由选项生成一些全局变量的值,这些值在其它文件中会用到。该文件也会输出configure的帮助信息。
应当注意如下代码

1
2
3
for option
do
opt="$opt `echo $option | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"

for后面可以省略要遍历的变量,这时,表示在遍历$@,即用户传入的所有变量组合

1.2.2 auto/init脚本

该文件的目录在于初始化一些临时文件的路径,检查echo的兼容性,并创建最原始的Makefile文件。

注意:这里生成的Makefile文件与configure文件在同一目录,真正执行编译指令的Makefile在objs目录下

1.2.3 auto/sources脚本

该文件从文件名中就可以看出,它的主要功能是跟源文件相关的。它的主要作用是定义不同功能或系统所需要的文件的变量。根据功能,分为CORE/REGEX/EVENT/UNIX/FREEBSD/HTTP等。每一个功能将会由四个变量组成,”_MODULES”表示此功能相关的模块,最终会输出到ngx_modules.c文件中,即动态生成需要编译到nginx中的模块;”INCS”表示此功能依赖的源码目录,查找头文件的时候会用到,在编译选项中,会出现在”-I”中;”DEPS”显示指明在Makefile中需要依赖的文件名,即编译时,需要检查这些文件的更新时间;”SRCS”表示需要此功能编译需要的源文件。

根据上面的描述,所以如果需要对nginx进行功能扩展,添加相应的C文件后,修改这个脚本就可以将自己的功能编译进nginx。

2. 源码解析

2.1 模块相关

ngx_module.h中引用了两个至关重要的外部变量

1
2
extern ngx_module_t  *ngx_modules[];
extern char *ngx_module_names[];

ngx_modulesngx_module_names定义在ngx_modules.c中,这个文件并不存在于nginx源码中,在编译时执行configure动态生成。ngx_modules数组包含所有的Nginx模块,Nginx启动时会调用ngx_cycle_modules函数,原型如下:

1
ngx_int_t ngx_cycle_modules(ngx_cycle_t *cycle)

此函数的目的是将ngx_modules中的数据复制到cycle中,函数调用顺序如下:

main->ngx_init_cycle->ngx_cycle_modules

2.2 数据结构

2.2.1 ngx_str_t

Nginx开发从入门到精通已经对ngx_str_t的定义以及相关api进行了非常详细的讲解,以下为定义:

1
2
3
4
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;

注意事项:

  1. 不要试图复制char *到data所指向的内存,可能导致coredump
  2. 初始化相关api都是基于常量字符串,char *类型慎用
  3. 如果非要复制char *到data所指向的内存,一定先分配内存

2.2.2 ngx_array_t

Nginx开发从入门到精通已经对ngx_array_t的定义以及相关api进行了非常详细的讲解,以下为定义:

1
2
3
4
5
6
7
8
typedef struct ngx_array_s       ngx_array_t;
struct ngx_array_s {
void *elts;
ngx_uint_t nelts;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
};

注意事项

  1. 如果使用ngx_array_t作为字符串容器,在调用ngx_array_create时,使用字符串长度最后一个参数
  2. ngx_array_push返回第n个元素首地址,要注意指针运算

3. handler模块的编写步骤

  1. 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等,即分别初始化如下变量:
1
2
3
static ngx_command_t ngx_http_module_name_commands[] = {};
static ngx_http_module_t ngx_http_module_name_module_ctx = {};
ngx_module_t ngx_http_module_name_module = {};
  1. 实现handler的挂载函数。根据模块的需求选择正确的挂载方式,即实现如下函数:
1
static ngx_int_t ngx_http_module_name_init(ngx_conf_t *cf)
  1. 编写handler处理函数。模块的功能主要通过这个函数来完成,即实现如下函数:
1
static ngx_int_t ngx_http_module_name_handler(ngx_http_request_t *r);

3.1 模块编译

对于开发一个模块,我们是需要把这个模块的C代码组织到一个目录里,同时需要编写一个config文件。这个config文件的内容就是告诉nginx的编译脚本,该如何进行编译。

1
2
3
4
 
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"

执行如下命令进行编译

1
./configure –add-module=/home/jizhao/open_source/book_module        

4. 部分功能简介

4.1 日志系统

Nginx的日志系统分为两个部分,一个是Nginx核心的错误日志(包括debug日志),一个是各模块的访问日志,整个Nginx框架中主要以核心的错误日志为主,各模块的访问日志根据模块调用顺序使用,下面分别介绍两种日志系统。

4.1.1 错误日志

关于错误日志的配置指令可以参考error_log,分析Nginx源码可以发现,源码中多处使用ngx_log_debug0,ngx_log_debug1等类似的宏定义,启用这些宏定义需要在configure的时候执行–with-debug。
错误日志初始化分为两个阶段,一个是ngx_log_init函数中初始化日志对象,另外一个就是在解析配置文件的时候会新建一个日志对象。

4.1.2 访问日志

目前实现了访问日志的官方模块有http模块,stream模块,其中http模块的配置指令可以参考ngx_http_log_module。http模块的日志系统是以http第三方模块的形式存在,所以编写的时候遵循第3节的规则,Nginx将HTTP请求划分为11个阶段,访问日志是最后一个阶段,所以访问日志在HTTP请求完成时写入。

4.2 upstream机制

upstream机制使得nginx能够作为代理服务器,或者负载均衡服务器,将来自客户端的请求转发到上游,并将上游响应转发到客户端。参考配置如下

#位于http上下文中
upstream local {
    server 127.0.0.1:8080 weight=5;
    server 127.0.0.1:80 weight=2;
}

4.3 反向代理

nginx反向代理实际上是一个upstream模块,实现upstream机制的几个回调函数。参考配置如下:

location / {
    proxy_pass http://local
}
    

5. 编码过程中遇到的问题

5.1 使用ngx_sprintf遇到的问题

ngx_sprintf函数用于向缓冲区中复制格式化字符串,跟标准C的sprintf函数一样,Nginx增加了一些格式,具体可以看ngx_string.c的源码,接下来结合代码说明一些问题:

1
2
3
4
5
6
7
         
size_t a;
off_t b;
u_char c[20] = {"0"};

ngx_sprintf(c, "a:%z,b:%O", a, b);

上面这段代码是正确示范,若a也为off_t类型,在执行ngx_sprintf时一定要使用%O,否者b的值会受影响。

6. 热更新

6.1 热更新步骤

  1. 替换nginx程序
  2. 向master进程发送信号 kill -USR2 PID,启动新程序
  3. 向旧master进程发送信号 kill -WINCH PID,告诉旧worker进程不再接受新请求

结语

此博文只粗略记录笔记,在后续会写详细的博客对nginx进行分析

libxml2使用心得

背景

由于当前项目以xml文件作为通信媒介,故需要对xml文档进行解析与生成。但是libxml2文档写得不是很好,也没有例程,我在实际工作中主要参考C++的XML编程经验――LIBXML2库使用指南,这篇博文讲得很详细,我在这里只是做出一点补充。

1. 中文问题

xml文档使用UTF-8编码,所以如果xml文档中包含中文,使用libxml2读取内容相关API时,在调试界面中看到读取出来的中文内容为乱码,这个时候就需要将内容转换为程序当前运行的编码格式。我使用visual studio2017,里面默认GBK编码,所以我在使用xml内容时就需要将UTF-8编码的内容转换为GBK,其他同理。

1.1 举例说明

我使用GLib2的g_locale_from_utf8将内容转换为本地程序默认的编码格式,在写入xml内容时使用g_locale_to_utf8将内容转换为UTF-8.

1.2 总结

总而言之,在读取xml相关内容时,首先将内容转换为程序默认的编码格式,写入xml内容时,将程序默认的编码格式转换为UTF-8。

2. 设置文档编码问题

GLib使用心得

前言

关于GLib的介绍这里就不赘述了,具体API介绍可以去GLib API Reference查阅,这里只是记录一下使用相关API所遇到的一些坑。

相关API

字符串相关API

1
2
3
4
5
 
GString *g_string_new(const gchar *init);
GString *g_string_append(GString *string, const gchar *val);
GString *g_string_prepend(GString *string, const gchar *val);
gchar *g_string_free(GString *string, gboolean free_segment);

如果需要对字符串进行拼接操作,使用GString相关API是非常方便的。注意 g_string_new跟g_string_free一定要配套使用,否则会内存泄漏。
g_string_appendg_string_prepend都是直接在原字符串上操作,这点要切记。

字符转换相关API

1
2
3
gchar *g_filename_display_basename(const gchar *filename);
gchar *g_locale_from_utf8(const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error);
gchar *g_locale_to_utf8(const gchar *opsysstring, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error);

g_filename_display_basename 此函数的参数字符编码必须是UTF-8,否则返回的字符串为乱码
g_locale_from_utf8 此函数将参数字符串从UTF-8编码转换为程序当前的编码,一般是GB2312,一般用于读取UTF-8编码的文件
g_locale_to_utf8 此函数将参数字符串从程序当前编码转换为UTF-8
下面看个例子, 比如我们有个含中文的文件名 /data/test/哈哈.txt如果想获得 _哈哈.txt_,则必须首先使用 g_locale_to_utf8函数转换一次, 然后再去获取basename

1
2
3
GString *string = g_string_new("/data/test/哈哈.txt");
gchar *str = g_locale_to_utf8(string->str, string->len, NULL, NULL, NULL);
gchar *basename = g_filename_display_basename(str);

如果想使用GString *类型保存basename,则必须重新申请内存块

1
GString *basename_string = g_string_new(basename); 

千万别像下面这样做

1
2
GString *string = g_string_new("/data/test/哈哈.txt");
string->str = g_locale_to_utf8(string->str, string->len, NULL, NULL, NULL);

如果像上面那样做,因为转换后的字符串长度跟转换前不一样,后续使用会出问题,g_locale_from_utf8 同理
注意 g_locale_to_utf8返回的char *需要手动释放内存

解析key-value文件API

1
2
3
4
5
GKeyFile *g_key_file_new(void);
gboolean g_key_file_load_from_file(GKeyFile *key_file, const gchar *file, GKeyFileFlags flags, GError **error);
gint g_key_file_get_integer(GKeyFile *key_file, const gchar *group_name, const gchar *key, GError **error);
gchar *g_key_file_get_string(GKeyFile *key_file, const gchar *group_name, const gchar *key, GError **error);
void g_key_file_free(GKeyFile *key_file);

这几个API很好理解,用来解析具有键值对特征的文件,g_key_file_newg_key_file_free配套使用;
g_key_file_load_from_file的第二个参数的字符编码格式为UTF-8,在使用时一定要先转换一次;

调用glib库出现0xc000007b解决办法

背景

之前一直在Linux环境下使用glib库,也一直没有遇到过这样的问题,这次在visual studio上配置使用glib,出现问题,如下图所示:

网上的解决办法都试过,没能解决问题。

出现转机

出现转机是在朋友推荐了一款名为depends的软件,可以分析执行程序所需要的库,关于depends的使用方法这里就不仔细介绍了。

开始分析

我使用depends分析libglib-2.0.dll,结果如下图所示

从图中左上方区域可以看出libglib-2.0.dll需要libintl-8.dll,而这个dll又缺失,解决办法就显而易见。
1、网上下载libintl-8.dll
2、将libintl-8.dll与libglib-2.0.dll存放在同一目录下

问题解决

上述步骤完成之后,再次使用depends检查,如下图所示:

程序也正常运行

注意

我这里只是说明解决问题的方法,图中dll版本问题自行忽略

安装vCenter Server出现1603错误的一种解决方法

背景

vCeter Server部署在一个Windows Server 2008 R2的虚拟机上,由于更改了虚拟机网卡配置,导致vCenter Server相关服务无法启动,查找解决方法无果后,就打算重装vCenter Server。

遇到的问题

部署过程主要参考VMware Vsphere 6.0安装部署 (三) vCenter Server安装。在这里我主要说明一下我所遇到的问题以及解决方法。在安装过程中遇到Encountered an internal error,Install-parameter rhttpproxy.ext.port1 not set具体错误信息如下图所示:

出现这个错误后,会退出安装,在最后会报‘安装组件VCSServiceManager失败并显示错误代码1603’,如下图所示:

出现这个错误,首先应当排查在安装vCenter Server 过程中配置的http端口是否被占用(使用命令netstat -nao),如果端口没有被占用,则使用如下方法:

  1. 删除C:\Program Files\VMware\vCenter Server目录;

  2. 重启虚拟机,重新安装

总结

这个方法不一定能解决所有这类问题,这只是其中一种解决方法。

VMware虚拟机备份与恢复

前言

本文主要是根据自己实际项目经验对云与备份之(1):VMware虚机备份和恢复这篇文章进行补充说明。本文将以部分引用,部分说明的方式进行组织,加粗部分为我自己总结的内容。
原文中提到两套SDK(vddk跟Web service SDK),整个虚拟机备份流程需要着两套SDK配合使用,我把这两套SDK整理了一下,使之能够在clion跟idea中运行,地址如下:VMwareProject
vix_disklib_sample

1. 与备份有关的VMWare基础知识

1.1 VMware 虚机磁盘在 ESXi 宿主机上的文件

简单来说,虚机的每个虚拟磁盘由ESXi 宿主机上的三个文件组成(这里的虚机名字是 sammy-target-win-small,下面是其第一个磁盘对应的三个文件):

  • sammy-target-win-small.vmdk (配置文件,大小 633 字节)
  • sammy-target-win-small-flat.vmdk (二进制文件,大小 12884901888 字节)
  • sammy-target-win-small-ctk.vmdk (二进制文件,大小 78694 字节)
    其中,

第一个文件保存的是该磁盘的元数据,其中包括另外两个文件的信息

# Extent description
RW 25165824 VMFS "sammy-target-win-small-flat.vmdk"

# Change Tracking File
changeTrackPath="sammy-target-win-small-ctk.vmdk"

第二个文件是 Extent description 文件,二进制数据保存在这个文件中。下面会介绍使用API获取该文件中数据的方法。
第三个文件是 CTK 文件。下面讲到 CTK 的时候再说。

@刘世民 [云与备份之(1):VMware虚机备份和恢复](http://www.cnblogs.com/sammyliu/p/5661085.html)

1.2 虚拟磁盘类型

上一节中的磁盘组织形式并不是唯一的,早期版本的Esxi不是以这种方式组织。vddk文档中定义了8种磁盘类型,在创建虚拟磁盘时,使用不同磁盘类型,则虚拟磁盘的组织方式不同。8中磁盘类型如下:

  • VIXDISKLIB_DISK_MONOLITHIC_SPARSE – Growable virtual disk contained in a single virtual disk file. This is the default type for hosted disk, and the only setting in the Virtual Disk API Sample Code sample program.
  • VIXDISKLIB_DISK_MONOLITHIC_FLAT – Preallocated virtual disk contained in a single virtual disk file. This takes time to create and occupies a lot of space, but might perform better than sparse.
  • VIXDISKLIB_DISK_SPLIT_SPARSE – Growable virtual disk split into 2GB extents ( s sequence). These files can to 2GB, then continue growing in a new extent. This type works on older file systems.
  • VIXDISKLIB_DISK_SPLIT_FLAT – Preallocated virtual disk split into 2GB extents ( f sequence). These files start at 2GB, so they take a while to create, but available space can grow in 2GB increments.
  • VIXDISKLIB_DISK_VMFS_FLAT – Preallocated virtual disk compatible with ESX 3 and later. Also known as thick disk. This managed disk type is discussed in Managed Disk and Hosted Disk.
  • VIXDISKLIB_DISK_VMFS_SPARSE – Employs a copy-on-write (COW) mechanism to save storage space.
  • VIXDISKLIB_DISK_VMFS_THIN – Growable virtual disk that consumes only as much space as needed, compatible with ESX 3 or later, supported by VDDK 1.1 or later, and highly recommended.
  • VIXDISKLIB_DISK_STREAM_OPTIMIZED – Monolithic sparse format compressed for streaming. Stream optimized format does not support random reads or writes.
    只有vddk使用VIXDISKLIB_DISK_VMFS_FLAT类型创建虚拟磁盘时才会生成1.1节中所示的虚拟磁盘文件,使用Web Service SDK创建虚拟磁盘则不受此限制。

1.3 虚拟磁盘模式

虚拟磁盘模式分为持久跟非持久,以下为vddk中对持久的定义

  • 持久模式(persistent):In persistent disk mode, changes are immediately and permanently written to the virtual disk, so that they survive even through to the next power on.
  • 非持久模式(nonpersistent):In nonpersistent mode, changes to the virtual disk are discarded when the virtual machine powers off. The VMDK files revert to their original state.

使用vSphere client创建虚拟磁盘跟vddk的选项可能会有区别

  • 独立持久:持久模式磁盘的行为与物理机上常规磁盘的行为相似。写入持久模式磁盘的所有数据都会永久性地写入磁盘。
  • 独立非持久:关闭虚拟机电源或重置虚拟机时,对非持久模式磁盘的更改将丢失。使用非持久模式,您可以每次使用相同的虚拟磁盘状态重新启动虚拟机。对磁盘的更改会写入重做日志文件并从中读取,重做日志文件会在关闭虚拟机电源或重置虚拟机时被删除。

1.4 vCenter Server

vCenter Server相当于一套管理多台Esxi服务器的系统,如果需要对虚拟机进行备份,则需要部署这么一套系统,具体怎么部署可以参考VMware Vsphere 6.0安装部署 (三) vCenter Server安装。备份所使用的Web serviceSDK依赖这套系统,对于这套系统的使用可以参考我其他关于vSphere的文章。

1.5 vSphere Web Client

vSphere Web Client为vCenter Server提供一个web管理界面,文中关于对vCenter的操作都是在这界面上完成。

1.5 虚拟机注册

在vCenter Server中,Esxi服务器以及虚拟机以实体对象的形式存在,已有的虚拟机文件可以通过注册功能,将虚拟机文件激活成虚拟机,这里涉及到虚拟机的其他文件,可以参考什么是虚拟机?。在vCenter Server中将.vmx文件作为虚拟机,所以在vCenter Server中找到虚拟机文件,右键注册就可以了。

2. 备份要点

原文中对备份流程进行了详细说明,我这里就不再赘述,我将从以下几个方面说明备份要点。其中具体代码实现可以去我的csdn查看相关文章

2.1 如何确定虚拟机对象?

这个时候就需要使用Web service SDK去遍历vCenter Server中的虚拟机对象,并返回虚拟机的名称和在vCenter中的id,有了这两个变量,就可以确定虚拟机对象了。

2.2 如何获取虚拟机相关配置?

这一步中最重要的是拿到虚拟机磁盘对象,对虚拟机的备份,实际上是对虚拟磁盘的备份,这里还是使用Web ServiceSDk进行获取

2.3 如何获取变化数据?

原文中有提到一个API:QueryChangedDiskAreas

获取 CBT 变化块的函数的定义为:QueryChangedDiskAreas(snapshot, deviceKey, startOffSet, changeID)。其中,

  • snapshot 代表当前的快照,也就是“变化”时间段的后端点;
  • deviceKey 是目标虚拟磁盘的 device ID;
  • startOffSet 是开始获取变化块的offset;
  • changeID 是指“变化”时间段的前端点,即老的快照的 changeID。
    其结果类似 “(117768192, 65536),(132120576, 65536),(145096704, 43122688),(265289728, 65536),(958398464, 65536)”,每项的格式为 (offset,length),表示一个发生变化的数据块。
@刘世民 [云与备份之(1):VMware虚机备份和恢复](http://www.cnblogs.com/sammyliu/p/5661085.html)

使用QueryChangedDiskAreas获取到变化数据后,需要将这些数据保存下来,以供后续使用。

2.4 如何下载变化数据?

这个时候就需要vddk,vddk可以连接到远程的虚拟磁盘,并根据上一节保存的数据,将虚拟磁盘中的变化数据读取并保存下来,以供恢复使用。

3. 恢复要点

3.1 完全恢复

如果在完全备份的时候采用文件下载的形式进行备份,则在恢复的时候就可以直接将文件上传至vCenter Server然后注册虚拟机就可以了。如果备份的时候只备份了虚拟磁盘,则在恢复的时候需要使用Web Service SDK创建虚拟机,再将磁盘数据写入到新建的虚拟机中。

3.2 增量恢复

增量恢复以完全恢复为前提,每个增量点依次恢复。

3.3 其他办法

如果业务十分紧急,则可以将备份的虚拟机文件以NFS的形式挂载到vCenter Server上,通过注册虚拟机的方式进行恢复。

Java与PHP之间的Socket通信

Java作为服务端

Java端作为服务端,PHP端作为客户端,在之前一篇文章中说了下PHP作为客户端如何与Java端进行通信。由于业务需要,socket必须等待服务端处理完成,并返回处理结果给PHP端。在这里主要说明一下Java端如何处理的,不足之处,望指正。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ServerSocket serverSocket = new ServerSocket(port); //创建绑定到特定端口的服务器套接字
Socket socket = serverSocket.accept(); //侦听并接受到此套接字的连接

InputStream inputStream = socket.getInputStream();
StringBuilder sb = new StringBuilder();

byte[] packetLength = new byte[4];
inputStream.read(packetLength, 0, 4); //首先从套接字中读取4字节的数据长度
int target = byteToint(packetLength); //这里将字节数组转换成整数
System.out.println("message size:" + target);

byte[] bytes = new byte[target];
inputStream.read(bytes); //读取指定长度的数据
sb.append(new String(bytes, 0, target, "UTF-8"));
System.out.println("get message " + sb);

String result = "has receive message";
OutputStream outputStream = socket.getOutputStream();
int responseLength = result.getBytes().length; //这个地方的长度,一定要是字节数组的长度,否则如果字符串中包含中文,接收端接收数据会不完整
byte[] targets = intTobyte(responseLength);
outputStream.write(targets); //首先发送4个字节的数据大小
outputStream.write(result.getBytes()); //再发送真正的数据

outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();

上述代码需要处异常,而且代码中的byteToint和intTobyte这两个函数非原创,是其他博主的劳动成果,感谢这位作者,原文地址https://www.cnblogs.com/langren1992/p/4717241.html

PHP作为服务端

相关函数: socket_create、socket_set_block、socket_bind、socket_listen、socket_accept、socket_read、socket_write,这些函数具体参数说明在PHP文档上写很详细,这里就不再赘述,这里只是介绍服务端如何处理请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//确保在连接客户端时不会超时
set_time_limit(0);
//设置IP和端口号
$address = "127.0.0.1";
$port = 54321;
$socketServer = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() fail:" . socket_strerror(socket_last_error()) . "/n");
//设置为阻塞模式
socket_set_block($socketServer) or die("socket_set_block() fail:" . socket_strerror(socket_last_error()) . "/n");
//绑定端口
$result = socket_bind($socketServer, $address, $port) or die("socket_bind() fail:" . socket_strerror(socket_last_error()) . "/n");
//开始监听
$result = socket_listen($socketServer, 4) or die("socket_listen() fail:" . socket_strerror(socket_last_error()) . "/n");

do {
//接收连接请求并返回一个子Socket来处理客户端和服务器间的信息
$sock = socket_accept($socketServer) or die("socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n");
while($sock){
//读取客户端数据
echo "Read client data \n";

$length = socket_read($sock, 4);
$length = unpack('i', $length);
echo "length:$length[1] \n";

$request = socket_read($sock, $length[1]);
echo "$request:$request \n";


//数据传送 向客户端写入返回结果
$msg = "this is response message \n";
$msgLength = strlen($msg);
$msgLength = pack('i', $msgLength);
socket_write($sock, $msgLength);
socket_write($sock, $msg, strlen($msg)) or die("socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n");
break;
}

} while (true);
//根据需要关闭socket
socket_close($socketServer);

在接收到客户端的请求后,可以结合PHP的多线程进行处理,这里只是简单的返回字符串。

总结

各大语言实现socket通信的方式都是大同小异,在服务端的流程都是差不多的。在有现成高性能socket通信框架的情况下,建议不要自己去实现,但是得理解框架底层是怎样实现的。

VMware vSphere WebService SDK使用心得

善用mob(Managed Object Browser)

mob地址一般是https://vcenter的FQDN/mob,mob可以让你更好的理解SDK中的那些例子,以及根据自己的业务逻辑改写一些功能。下图为mob首页图,图中的content为整个Managed Object的顶层,从这里进去可以找到整个vcenter server中所有对象及其属性。

善用GetMOREF类

GetMOREF类具体位置在VMware-vSphere-SDK-6.0.0-2561048\SDK\vsphere-ws\java\JAXWS\samples\com\vmware\connection\helpers,这是VMware官方提供的一个工具类,里面的每个函数对于开发都很有用。例如

​List<VirtualDevice> listvd = ((ArrayOfVirtualDevice) getMOREFs
                                .entityProps(vmMor, new String[] { "config.hardware.device" })
                                .get("config.hardware.device")).getVirtualDevice();

这是VMReconfig.java中的一个片段,这段代码的作用就是去获取指定虚拟机对象的所有设备,vmMor可以通过GetMOREF类中的vmByVMname函数去获得。至于config.hardware.device,就需要用mob一层一层点进去看了,相信结合mob就能理解为什么要这么写。

​​
 ManagedObjectReference propCol = connection.getServiceContent().getPropertyCollector();
 ManagedObjectReference vmRef = getMOREFs.vmByVMname(virtualMachineName,propCol);

 

上面这段代码的作用就是如何根据虚拟机名去获取其对应的ManagedObjectReference

善用SDK里面的文档

文档首页为VMware-vSphere-SDK-6.0.0-2561048/SDK/vsphere-ws/docs/ReferenceGuide/index.html

根据需求,在左侧导航栏对应的去找。

对SDK我整理了一下,使其能够在idea中编译运行,项目地址在VMwareProject

Socket粘包处理

背景

这段时间刚忙完了一个项目,涉及到PHP与Java进行socket通信的问题,应用场景是PHP端向Java端发送数据,并等待Java的响应结果,数据格式为JSON。

遇到的问题

PHP端发送数据后,Java端从socket里读取数据,但是一直读不到结束符。

原因

PHP需要调用socket_close函数才会向socket里面写入结束符。

改进方法

PHP端与Java端之间确定每次发送数据包的长度,用4个字节表示数据长度,PHP端先发送数据包长度,再发送数据包,Java端先接收4个字节长度的数据包长度,再根据数据包长度接收数据包。PHP端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$socket = socket_create ( AF_INET, SOCK_STREAM, SOL_TCP  ) or die ( 'could not create socket'  );  
$connect = socket_connect ( $socket, 'xxx.xxx.xxx.xxx', xxxx);
//服务端发送数据
$arr = array("data" => "dadadada");
$strlen = pack('i', strlen(json_encode($arr)));
var_dump($strlen);
socket_write ($socket, $strlen);
socket_write ($socket, json_encode($arr), strlen(json_encode($arr)));
$response = socket_read($socket, 4);
$res = unpack('i', $response);
var_dump($res);
$response = socket_read($socket, $res[1]);
var_dump($response);
socket_close($socket);

packunpack函数使用说明见PHP官方文档