app界面设计属于什么设计,辽宁seo推广,哪个企业做网站,免费网站登陆模板给出一个客户/服务器程序开发的案例#xff0c;实现从服务器状态的远程监视功能。同时#xff0c;客户端采用图形界面来显示数据。这个案例涵盖了网络编程和GUI编程的相关知识#xff0c;读者应该注意其中的结合点。具体内容包括#xff1a; 服务器端程序设计 客户端程序设…给出一个客户/服务器程序开发的案例实现从服务器状态的远程监视功能。同时客户端采用图形界面来显示数据。这个案例涵盖了网络编程和GUI编程的相关知识读者应该注意其中的结合点。具体内容包括 服务器端程序设计 客户端程序设计 一 服务器端程序设计 服务器端程序的功能是接受客户端的连接然后提供其状态信息例如CPU的使用率、内存的分配情况等这些信息我们通过访问/proc文件系统来获得。 1 /* server.c */2 3 #include stdlib.h4 5 #include stdio.h6 7 #include unistd.h8 9 #include errno.h10 11 #include string.h12 13 #include sys/types.h14 15 #include netinet/in.h16 17 #include sys/socket.h18 19 #include arpa/inet.h20 21 #define SERV_PORT 2395 /* 服务器监听端口号 */22 23 #define BACKLOG 10 /* 请求队列的长度数 */24 25 #define BUF_SIZE 1024 /* 缓冲区的长度 */26 27 #define MEM_SIZE 3228 29 30 31 struct cpustatus32 33 {34 35 long total; /* 系统从启动到现在的总时间 */36 37 float user; /* 系统从启动到现在用户态的CPU时间百分比 */38 39 float nice; /* 系统从启动到现在nice值为负的进程所占用的CPU时间百分比 */40 41 float system; /* 系统从启动到现在核心态的CPU时间百分比 */42 43 float idle; /* 系统从启动到现在除I/O等待以外的其他等待时间百分比 */44 45 };46 47 struct meminfo48 49 {50 51 char total[MEM_SIZE]; /* 系统的总内存空间 */52 53 char free[MEM_SIZE]; /* 系统的空闲内存空间 */54 55 };56 57 void get_cpu_status(struct cpustatus *); /* 获取处理器信息 */58 59 void get_mem_info(struct meminfo *); /* 获取内存信息 */60 61 62 63 int main()64 65 {66 67 int ret;68 69 int pid; /* 定义进程标识符 */70 71 int sockfd; /* 定义监听Socket描述符 */72 73 int clientsfd; /* 定义数据传输Socket描述符 */74 75 struct sockaddr_in host_addr; /* 本机IP地址和端口号信息 */76 77 struct sockaddr_in client_addr; /* 客户端IP地址和端口号信息 */78 79 unsigned int addrlen;80 81 char buf[BUF_SIZE]; /* 定义缓冲区 */82 83 struct cpustatus cpu_stat;84 85 struct meminfo mem_info;86 87 int cnt;88 89 int size;90 91 /* 创建套接字 */92 93 sockfd socket(AF_INET, SOCK_STREAM, 0);94 95 if(sockfd -1) /* 如果套接字创建失败输出错误信息并退出 */96 97 {98 99 printf(socket error\n);
100
101 exit(1);
102
103 }
104
105 /* 将套接字与IP地址和端口号进行绑定 */
106
107 host_addr.sin_family AF_INET; /* TCP/IP协议 */
108
109 host_addr.sin_port htons(SERV_PORT); /* 让系统随机选择一个未被占用的端口号 */
110
111 host_addr.sin_addr.s_addr INADDR_ANY; /* 本机IP地址 */
112
113 bzero((host_addr.sin_zero), 8); /* 清零 */
114
115 ret bind(sockfd, (struct sockaddr *)host_addr, sizeof(struct sockaddr)); /* 绑定 */
116
117 if(ret -1) /* 如果套接字绑定失败输出错误信息并退出 */
118
119 {
120
121 printf(bind error\n);
122
123 exit(1);
124
125 }
126
127 /* 将套接字设为监听模式以等待连接请求 */
128
129 ret listen(sockfd, BACKLOG);
130
131 if(ret -1) {
132
133 perror(listen error\n);
134
135 exit(1);
136
137 }
138
139 printf(Waiting for the client connection.\n );
140
141 /* 循环处理客户端的请求 */
142
143 while(1)
144
145 {
146
147 addrlen sizeof(struct sockaddr_in);
148
149 clientsfd accept(sockfd, (struct sockaddr *)client_addr, addrlen); /* 接受一个客户端连接 */
150
151 if(clientsfd -1)
152
153 {
154
155 perror(accept error\n);
156
157 continue;
158
159 }
160
161 pid fork(); /* 创建子进程 */
162
163 if(pid0) /* 如果进程创建失败输出错误信息并退出 */
164
165 {
166
167 perror(fork error\n);
168
169 exit(1);
170
171 }
172
173 if(pid0) /* 子进程处理客户端的请求*/
174
175 {
176
177 close(sockfd); /* 关闭父进程的套接字 */
178
179 printf(Client IP : %s\n, inet_ntoa(client_addr.sin_addr)); /* 输出客户端IP地址 */
180
181 /* 获取处理器信息 */
182
183 get_cpu_status(cpu_stat);
184
185 size sizeof(struct cpustatus);
186
187 memcpy(buf, cpu_stat, size);
188
189 /* 获取内存信息 */
190
191 get_mem_info(mem_info);
192
193 memcpy(bufsize, mem_info, sizeof(struct meminfo));
194
195 /* 发送数据 */
196
197 cnt send(clientsfd, buf, BUF_SIZE, 0);
198
199 if(cnt -1)
200
201 {
202
203 perror(send error\n);
204
205 exit(1);
206
207 }
208
209 printf(Done!\n, buf); /* 程序测试使用 */
210
211 close(clientsfd); /* 关闭当前客户端连接 */
212
213 exit(0); /* 子进程退出 */
214
215 }
216
217 close(clientsfd); /* 父进程关闭子进程的套接字准备接受下一个客户端连接 */
218
219 }
220
221 return 0;
222
223 }
224
225
226
227 void get_cpu_status(struct cpustatus *cpu_stat)
228
229 {
230
231 long total;
232
233 float user, nice, system, idle;
234
235 long cpu[21];
236
237 char text[201];
238
239 FILE *fp;
240
241 fp fopen(/proc/stat, r); /* 如果/proc/stat文件 */
242
243 if (fp NULL) /* 如果文件打开失败输出错误信息并退出 */
244
245 {
246
247 printf(open stat failed\n);
248
249 exit(1);
250
251 }
252
253 while(fgets(text, 200, fp) ! NULL) /* 提取处理器信息 */
254
255 {
256
257 if(strstr(text, cpu))
258
259 {
260
261 sscanf(text, %s %f %f %f %f, cpu, user, nice, system, idle);
262
263 }
264
265 }
266
267 fclose(fp); /* 关闭文件 */
268
269 /* 进行各类CPU时间 */
270
271 total user nice system idle;
272
273 user (user / total) * 100;
274
275 nice (nice / total) * 100;
276
277 system (system / total) * 100;
278
279 idle (idle / total) * 100;
280
281 /* 对结构体各成员进行赋值 */
282
283 cpu_stat-total total;
284
285 cpu_stat-user user;
286
287 cpu_stat-nice nice;
288
289 cpu_stat-system system;
290
291 cpu_stat-idle idle;
292
293 return;
294
295 }
296
297
298
299 void get_mem_info(struct meminfo *minfo)
300
301 {
302
303 int i, j;
304
305 char total[MEM_SIZE];
306
307 char free[MEM_SIZE];
308
309 char temp[MEM_SIZE*2];
310
311 FILE *fp;
312
313 fp fopen(/proc/meminfo, r); /* 如果/proc/meminfo文件 */
314
315 if (fp NULL) /* 如果文件打开失败输出错误信息并退出 */
316
317 {
318
319 printf(open meminfo failed\n);
320
321 exit(1);
322
323 }
324
325 fgets(temp, MEM_SIZE*2, fp);
326
327 strcpy(total, temp); /* 系统的总内存空间信息字符串 */
328
329 fgets(temp, MEM_SIZE*2, fp);
330
331 strcpy(free, temp); /* 系统的空闲内存空间信息字符串 */
332
333 fclose(fp); /* 关闭文件 */
334
335 if(strlen(total) 0) /* 提取总内存空间信息字符串中的数值部分 */
336
337 {
338
339 for(i0,j0; istrlen(total); i)
340
341 {
342
343 if(isdigit(total[i]))
344
345 minfo-total[j] total[i];
346
347 }
348
349 minfo-total[j] \0; /* 字符串结束符 */
350
351 }
352
353 if(strlen(free) 0) /* 提取空闲内存空间信息字符串中的数值部分 */
354
355 {
356
357 for(i0,j0; istrlen(free); i)
358
359 {
360
361 if(isdigit(free[i]))
362
363 minfo-free[j] free[i];
364
365 }
366
367 minfo-free[j] \0; /* 字符串结束符 */
368
369 }
370
371 return;
372
373 } 二 客户端程序设计 客户端程序我们分两步来实现首先在字符界面下设计实现调试通过后再设计开发图形界面。 1 字符界面客户端程序 首先开发字符界面下的客户端程序该程序的功能比较简单连接服务器获取其状态信息并输出到屏幕。 1 /* client.c */2 3 #include stdlib.h4 5 #include stdio.h6 7 #include unistd.h8 9 #include errno.h10 11 #include string.h12 13 #include sys/types.h14 15 #include netinet/in.h16 17 #include sys/socket.h18 19 #define SERV_PORT 2395 /* 服务器监听端口号 */20 21 #define BUF_SIZE 1024 /* 缓冲区的长度 */22 23 #define MEM_SIZE 3224 25 26 27 struct cpustatus /* 处理器信息 */28 29 {30 31 long total;32 33 float user;34 35 float nice;36 37 float system;38 39 float idle;40 41 };42 43 struct meminfo /* 内存信息 */44 45 {46 47 char total[MEM_SIZE];48 49 char free[MEM_SIZE];50 51 };52 53 54 55 int main(int argc, char **argv)56 57 {58 59 int ret;60 61 int sockfd; /* 定义Socket描述符 */62 63 struct sockaddr_in serv_addr; /* 服务器IP地址和端口号信息 */64 65 char buf[BUF_SIZE]; /* 定义缓冲区 */66 67 struct cpustatus cpu_stat;68 69 struct meminfo mem_info;70 71 int cnt;72 73 int size;74 75 if(argc ! 2) /* 检查命令行参数个数是否正确 */76 77 {78 79 printf(arguments error.\n);80 81 exit(1);82 83 }84 85 /* 创建套接字 */86 87 sockfd socket(AF_INET, SOCK_STREAM, 0);88 89 if(sockfd -1) /* 如果套接字创建失败输出错误信息并退出 */90 91 {92 93 printf(socket error\n);94 95 exit(1);96 97 }98 99 /* 向服务器发出连接请求 */
100
101 serv_addr.sin_family AF_INET; /* TCP/IP协议 */
102
103 serv_addr.sin_port htons(SERV_PORT); /* 服务器端口号并转换为网络字节顺序 */
104
105 serv_addr.sin_addr.s_addr inet_addr(argv[1]); /* 服务器的IP地址 */
106
107 bzero((serv_addr.sin_zero), 8); /* 清零 */
108
109 ret connect(sockfd, (struct sockaddr *)serv_addr, sizeof(struct sockaddr)); /* 连接 */
110
111 if(ret -1) /* 如果连接失败输出错误信息并退出 */
112
113 {
114
115 printf(connect error\n);
116
117 exit(1);
118
119 }
120
121 /* 接收数据 */
122
123 cnt recv(sockfd, buf, BUF_SIZE, 0);
124
125 if(cnt -1)
126
127 {
128
129 perror(recv error\n);
130
131 exit(1);
132
133 }
134
135 size sizeof(struct cpustatus);
136
137 memcpy(cpu_stat, buf, size);
138
139 memcpy(mem_info, bufsize, sizeof(struct meminfo));
140
141 /* 输出接收到数据 */
142
143 printf(CPU Information\n);
144
145 printf(------------------------\n);
146
147 printf(user :\t\t %.2f\%\n, cpu_stat.user);
148
149 printf(nice :\t\t %.2f\%\n, cpu_stat.nice);
150
151 printf(system :\t %.2f\%\n, cpu_stat.system);
152
153 printf(idle :\t\t %.2f\%\n, cpu_stat.idle);
154
155 printf(Memory Information\n);
156
157 printf(------------------------\n);
158
159 printf(total :\t\t %s kB\n,mem_info.total);
160
161 printf(free :\t\t %s kB \n,mem_info.free);
162
163 close(sockfd); /* 关闭套接字 */
164
165 return 0;
166
167 } 2 图形界面客户端程序 接下来设计开发客户端的图形界面这里使用Glade来协助完成。 1 /*2 3 * Initial main.c file generated by Glade. Edit as required.4 5 * Glade will not overwrite this file.6 7 */8 9 10 11 #ifdef HAVE_CONFIG_H12 13 # include config.h14 15 #endif16 17 18 19 #include gtk/gtk.h20 21 22 23 #include interface.h24 25 #include support.h26 27 28 29 GtkWidget *entry1;30 31 GtkWidget *textview1;32 33 int34 35 main (int argc, char *argv[])36 37 {38 39 GtkWidget *window1;40 41 42 43 #ifdef ENABLE_NLS44 45 bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);46 47 bind_textdomain_codeset (GETTEXT_PACKAGE, UTF-8);48 49 textdomain (GETTEXT_PACKAGE);50 51 #endif52 53 54 55 gtk_set_locale ();56 57 gtk_init (argc, argv);58 59 60 61 add_pixmap_directory (PACKAGE_DATA_DIR / PACKAGE /pixmaps);62 63 64 65 /*66 67 * The following code was added by Glade to create one of each component68 69 * (except popup menus), just so that you see something after building70 71 * the project. Delete any components that you dont want shown initially.72 73 */74 75 window1 create_window1 ();76 77 gtk_widget_show (window1);78 79 80 81 gtk_main ();82 83 return 0;84 85 }86 87 88 89 /*90 91 * DO NOT EDIT THIS FILE - it is generated by Glade.92 93 */94 95 96 97 #ifdef HAVE_CONFIG_H98 99 # include config.h
100
101 #endif
102
103
104
105 #include sys/types.h
106
107 #include sys/stat.h
108
109 #include unistd.h
110
111 #include string.h
112
113 #include stdio.h
114
115
116
117 #include gdk/gdkkeysyms.h
118
119 #include gtk/gtk.h
120
121
122
123 #include callbacks.h
124
125 #include interface.h
126
127 #include support.h
128
129
130
131 #define GLADE_HOOKUP_OBJECT(component,widget,name) \
132
133 g_object_set_data_full (G_OBJECT (component), name, \
134
135 gtk_widget_ref (widget), (GDestroyNotify) gtk_widget_unref)
136
137
138
139 #define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \
140
141 g_object_set_data (G_OBJECT (component), name, widget)
142
143
144
145 GtkWidget*
146
147 create_window1 (void)
148
149 {
150
151 GtkWidget *window1;
152
153 GtkWidget *fixed1;
154
155 GtkWidget *fixed2;
156
157 extern GtkWidget *entry1;
158
159 extern GtkWidget *textview1;
160
161 GtkWidget *label2;
162
163 GtkWidget *label1;
164
165 GtkWidget *button1;
166
167
168
169 window1 gtk_window_new (GTK_WINDOW_TOPLEVEL);
170
171 gtk_window_set_title (GTK_WINDOW (window1), _(remote monitor));
172
173
174
175 fixed1 gtk_fixed_new ();
176
177 gtk_widget_show (fixed1);
178
179 gtk_container_add (GTK_CONTAINER (window1), fixed1);
180
181 gtk_widget_set_size_request (fixed1, 264, 302);
182
183
184
185 fixed2 gtk_fixed_new ();
186
187 gtk_widget_show (fixed2);
188
189 gtk_fixed_put (GTK_FIXED (fixed1), fixed2, 136, 104);
190
191 gtk_widget_set_size_request (fixed2, 50, 50);
192
193
194
195 entry1 gtk_entry_new ();
196
197 gtk_widget_show (entry1);
198
199 gtk_fixed_put (GTK_FIXED (fixed1), entry1, 104, 224);
200
201 gtk_widget_set_size_request (entry1, 144, 24);
202
203 gtk_entry_set_invisible_char (GTK_ENTRY (entry1), 8226);
204
205
206
207 textview1 gtk_text_view_new ();
208
209 gtk_widget_show (textview1);
210
211 gtk_fixed_put (GTK_FIXED (fixed1), textview1, 16, 42);
212
213 gtk_widget_set_size_request (textview1, 232, 172);
214
215
216
217 label2 gtk_label_new (_(Server Information :));
218
219 gtk_widget_show (label2);
220
221 gtk_fixed_put (GTK_FIXED (fixed1), label2, 10, 16);
222
223 gtk_widget_set_size_request (label2, 160, 24);
224
225
226
227 label1 gtk_label_new (_(IP Address:\n));
228
229 gtk_widget_show (label1);
230
231 gtk_fixed_put (GTK_FIXED (fixed1), label1, 16, 227);
232
233 gtk_widget_set_size_request (label1, 80, 20);
234
235 gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_CENTER);
236
237
238
239 button1 gtk_button_new_with_mnemonic (_(Update));
240
241 gtk_widget_show (button1);
242
243 gtk_fixed_put (GTK_FIXED (fixed1), button1, 176, 256);
244
245 gtk_widget_set_size_request (button1, 70, 29);
246
247
248
249 g_signal_connect ((gpointer) entry1, activate,
250
251 G_CALLBACK (on_entry1_activate),
252
253 NULL);
254
255 g_signal_connect ((gpointer) button1, clicked,
256
257 G_CALLBACK (on_button1_clicked),
258
259 NULL);
260
261
262
263 /* Store pointers to all widgets, for use by lookup_widget(). */
264
265 GLADE_HOOKUP_OBJECT_NO_REF (window1, window1, window1);
266
267 GLADE_HOOKUP_OBJECT (window1, fixed1, fixed1);
268
269 GLADE_HOOKUP_OBJECT (window1, fixed2, fixed2);
270
271 GLADE_HOOKUP_OBJECT (window1, entry1, entry1);
272
273 GLADE_HOOKUP_OBJECT (window1, textview1, textview1);
274
275 GLADE_HOOKUP_OBJECT (window1, label2, label2);
276
277 GLADE_HOOKUP_OBJECT (window1, label1, label1);
278
279 GLADE_HOOKUP_OBJECT (window1, button1, button1);
280
281
282
283 return window1;
284
285 }
286
287
288
289 #ifdef HAVE_CONFIG_H
290
291 # include config.h
292
293 #endif
294
295
296
297 #include gtk/gtk.h
298
299
300
301 #include callbacks.h
302
303 #include interface.h
304
305 #include support.h
306
307
308
309 #include stdlib.h
310
311 #include stdio.h
312
313 #include unistd.h
314
315 #include errno.h
316
317 #include string.h
318
319 #include sys/types.h
320
321 #include netinet/in.h
322
323 #include sys/socket.h
324
325 #define SERV_PORT 2395 /* 服务器监听端口号 */
326
327 #define BUF_SIZE 1024 /* 缓冲区的长度 */
328
329 #define MEM_SIZE 32
330
331
332
333
334
335
336
337 struct cpustatus /* 处理器信息 */
338
339 {
340
341 long total;
342
343 float user;
344
345 float nice;
346
347 float system;
348
349 float idle;
350
351 };
352
353 struct meminfo /* 内存信息 */
354
355 {
356
357 char total[MEM_SIZE];
358
359 char free[MEM_SIZE];
360
361 };
362
363
364
365
366
367
368
369
370
371 void
372
373 on_button1_clicked (GtkButton *button,
374
375 gpointer user_data)
376
377 {
378
379
380
381 int ret;
382
383 int sockfd; /* 定义Socket描述符 */
384
385 struct sockaddr_in serv_addr; /* 服务器IP地址和端口号信息 */
386
387 char buf[BUF_SIZE]; /* 定义缓冲区 */
388
389 struct cpustatus cpu_stat;
390
391 struct meminfo mem_info;
392
393 int i, cnt;
394
395 int size;
396
397 char out[6][MEM_SIZE];
398
399 sockfd socket(AF_INET, SOCK_STREAM, 0);
400
401 if(sockfd -1) /* 如果套接字创建失败输出错误信息并退出 */
402
403 {
404
405 printf(socket error\n);
406
407 exit(1);
408
409 }
410
411
412
413 extern GtkWidget *entry1;
414
415
416
417 const gchar *entry_text;
418
419 entry_text gtk_entry_get_text(entry1);
420
421
422
423
424
425 /* 向服务器发出连接请求 */
426
427 serv_addr.sin_family AF_INET; /* TCP/IP协议 */
428
429 serv_addr.sin_port htons(SERV_PORT); /* 服务器端口号并转换为网络字节顺序 */
430
431 serv_addr.sin_addr.s_addr inet_addr(entry_text); /* 服务器的IP地址 */
432
433 bzero((serv_addr.sin_zero), 8); /* 清零 */
434
435 ret connect(sockfd, (struct sockaddr *)serv_addr, sizeof(struct sockaddr)); /* 连接 */
436
437 if(ret -1) /* 如果连接失败输出错误信息并退出 */
438
439 {
440
441 printf(connect error\n);
442
443 exit(1);
444
445 }
446
447 /* 接收数据 */
448
449 cnt recv(sockfd, buf, BUF_SIZE, 0);
450
451 if(cnt -1)
452
453 {
454
455 perror(recv error\n);
456
457 exit(1);
458
459 }
460
461 size sizeof(struct cpustatus);
462
463 memcpy(cpu_stat, buf, size);
464
465 memcpy(mem_info, bufsize, sizeof(struct meminfo));
466
467 /* 输出接收到数据 */
468
469
470
471 printf(Entry contents : %s\n, entry_text);
472
473 extern GtkWidget *textview1;
474
475 // const gchar *entry_text;
476
477 GtkTextBuffer *buffer;
478
479 buffer gtk_text_view_get_buffer(GTK_TEXT_VIEW (textview1));//取得文本缓冲区
480
481
482
483 //PangoFontDescription *fontDesc;
484
485 // fontDesc pango_font_description_from_string(Courier Pitch Bold 12);
486
487
488
489 // gtk_widget_modify_font(textview1, fontDesc);
490
491 GtkTextIter start, end;
492
493 //gtk_text_buffer_get_start_iter(buffer,end);
494
495 //gtk_text_buffer_get_end_iter(buffer,end);
496
497 //end start;
498
499
500
501 sprintf(out[0],user :\t\t %.2f\%\n, cpu_stat.user);
502
503 sprintf(out[1],nice :\t\t %.2f\%\n, cpu_stat.nice);
504
505 sprintf(out[2],system :\t %.2f\%\n, cpu_stat.system);
506
507 sprintf(out[3],idle :\t\t %.2f\%\n, cpu_stat.idle);
508
509
510
511 sprintf(out[4],total :\t\t %s kB\n,mem_info.total);
512
513 sprintf(out[5],free :\t\t %s kB \n,mem_info.free);
514
515 gtk_text_buffer_get_start_iter(buffer,end);
516
517
518
519 for(i5; i0; i--)
520
521 {
522
523
524
525 gtk_text_buffer_get_start_iter(buffer,end);
526
527
528
529 if(i 3)
530
531 {
532
533 gtk_text_buffer_insert(buffer,end, Memory\n-------------------------\n, -1);
534
535 gtk_text_buffer_get_start_iter(buffer,end);
536
537 }
538
539 gtk_text_buffer_insert(buffer,end, out[i], -1);/* gtk_text_insert (GTK_TEXT_VIEW(textview1), NULL, textview1-style-black, NULL, Supports , -1);*/
540
541
542
543 }
544
545 gtk_text_buffer_get_start_iter(buffer,end);
546
547 gtk_text_buffer_insert(buffer,end, CPU\n-------------------------\n, -1);
548
549 close(sockfd); /* 关闭套接字 */
550
551 return 0;
552
553
554
555
556
557 }
558
559 void
560
561 on_entry1_activate (GtkEntry *entry, gpointer user_data)
562
563 {
564
565 const gchar *entry_text;
566
567 entry_text gtk_entry_get_text(entry);
568
569 printf(Entry contents : %s\n, entry_text);
570
571 } 三小结 这里基于网络编程和GUI编程技术设计实现了一个客户/服务器程序。总的来说程序还是比较简单、容易理解的。感兴趣的同学还可以在该程序的基础上进行优化例如将界面加工得更为美观或添加一些新的功能。转载于:https://www.cnblogs.com/zeedmood/archive/2012/04/19/2457753.html