本篇主要讲述下 服务器主动向android手机端推送消息在Linux下的实现。
实现方案与前面的Windows下实现方案一样。首先将之前经过Wsbservice处理过的Androidpn服务器端导入eclipse中运行:
因为这个服务器端是已经经过Webservice处理过了,所以当服务器端运行起来后,在浏览器地址栏中输入:http://localhost:8080/androidpnservice/androidpush?wsdl可以直接得到WSDL文档:
有了WSDL文档后,我们就可以产生客户端代码了,但不再是用cxf工具。因为我们服务器端要求用C语言实现,gsoap可以说是Webservice在C语言中最好的工具之一了。
首先简单了解下gsoap这个工具:gSOAP编译工具提供了一个SOAP/XML 关于C/C++ 语言的实现,从而让C/C++语言开发web服务或客户端程序的工作变得轻松了很多。绝大多数的C++web服务工具包提供一组API函数类库来处理特定的SOAP数据结构,这样就使得用户必须改变程序结构来适应相关的类库。与之相反,gSOAP利用编译器技术提供了一组透明化的SOAP API,并将与开发无关的SOAP实现细节相关的内容对用户隐藏起来.gSOAP的编译器能够自动的将用户定义的本地化的C或C++数据类型转变为符合XML语法的数据结构,反之亦然。这样,只用一组简单的API就将用户从SOAP细节实现工作中解脱了出来,可以专注与应用程序逻辑的实现工作了。gSOAP编译器可以集成C/C++和Fortran代码(通过一个Fortran到C的接口),嵌入式系统,其他SOAP程序提供的实时软件的资源和信息;可以跨越多个操作系统,语言环境以及在防火墙后的不同组织。gSOAP一种跨平台的C和 C++软件开发工具包。生成C/C++的RPC代码,XML数据绑定,对SOAP Web服务和其他应用形成高效的具体架构解析器,它们都受益于一个XML接口。 这个工具包提供了一个全面和透明的XML数据绑定解决方案,Autocoding节省大量开发时间来执行SOAP/XML Web服务中的C/C++。此外,使用XML数据绑定大大简化了XML自动映射。应用开发人员不再需要调整应用程序逻辑的具体库和XML为中心的数据,如 交涉DOM。
上面是比较官方的描述,具体的用法可以参考:
、
。
有了对gsoap工具的简单了解后,下面我们来看下具体操作。
首先下载gsoap工具,我这里用的是gsoap-2.7。下载解压后在gsoap-2.7/gsoap/bin/linux386下有soapcpp2和wsdl2h这两个我们要使用的工具,为了使用方便我们将gsoap-2.7/gsoap/bin/linux386路径配到环境变量中。
在 Ubuntu 系统中有两种设置环境变量 PATH 的方法。第一种适用于为单一用户设置 PATH,第二种是为全局设置PATH。
第一种方法:
在用户主目录下有一个 .bashrc 文件,可以在此文件中加入 PATH 的设置如下:
export PATH=”$PATH:/your path1/:/your path2/…..”
注意:每一个 path 之间要用 “:“ 分隔。
注销重启系统就可以了。
第二种方法:
在 /etc/profile中增加你要配的路径。
然后 source /etc/profile 即可让配置生效。
PATH="$PATH:/home/zhengb66/bin"
export PATH
这里我用的是第二种,将gsoap-2.7/gsoap/bin/linux386加到export PATH=值的最后面,然后保存关闭,source /etc/profile就可以了。
然后新建一个文件夹用于存放gsoap工具产生的代码,然后在终端进入此文件夹,输入:wsdl2h -s -c -o sendnotification.h http://localhost:8080/androidpnservice/androidpush?wsdl,就会在终端中看到新产生的:sendnotification.h文件:
然后在终端输入:soapcpp2 -x -c -C sendnotification.h,就可以得到根据sendnotification.h产生的其他文件,然后将gsoap-2.7/gsoap目录下的stdsoap2.c和stdsoap2.h两个文件拷贝到本文件夹下,本文文件夹的内容为:
产生这些文件的最终目的都是为了供我们调用,下面写一个包含main函数的main.c文件:
1. #include <stdio.h>
2. #include "soapH.h"
3. #include "AndroidPushNotificationWsSoapBinding.nsmap"
4.
5. int
6. {
7. struct
8.
9. struct
10. struct
11.
12. struct
13. struct
14.
15. struct
16. struct
17.
18. "1234567890"; //apikey
19. "Hi"; //title
20. "OK Yes I am ok."; //message
21.
22. "1234567890"; //apikey
23. "e2b77f73bb2e410d8d869b7699d107d0"; //username
24. "Hello Hi"; //title
25. "Hello World! How are you?"; //message
26.
27. "1234567890"; //apikey
28. "hello"; //title
29. "How are you?"; //message
30.
31. "Hello World!\n");
32.
33. soap_init(&mysoap);
34. "http://localhost:8080/androidpnservice/androidpush","",&ns2sendbr,&ns2sendbrr);
35. // soap_call___ns1__sendNotifications(&mysoap,"http://localhost:8080/androidpnservice/androidpush?wsdl","", &ns2sendno, &ns2sendnor);
36. //soap_call___ns1__sendAllBroadcast(&mysoap,"http://localhost:8080/androidpnservice/androidpush?wsdl","", &ns2sendallbr, &ns2sendallbrr);
37.
38. soap_destroy(&mysoap);
39. soap_end(&mysoap);
40. soap_done(&mysoap);
41.
42. // system("pause");
43. return
44. }
在main函数中soap_call___ns1__sendBroadcast、soap_call___ns1__sendNotifications、soap_call___ns1__sendAllBroadcast分别对应Androidpn服务器端发送消息的三个方法,具体的调用形式可以在soapClient.c文件中查到。有了源文件后可以在终端用gcc来编译,但用Makefile更加方便,下面是Makefile:
1. main: main.o soapC.o soapClient.o stdsoap2.o
2. gcc main.o soapC.o soapClient.o stdsoap2.o -o main
3. main.o: main.c soapH.h AndroidPushNotificationWsSoapBinding.nsmap
4. gcc -c main.c
5. soapC.o: soapC.c soapH.h
6. gcc -c soapC.c
7. soapClient.o: soapH.h soapClient.c
8. gcc -c soapClient.c
9. stdsoap2.o: stdsoap2.h stdsoap2.c
10. gcc -c stdsoap2.c
11. .PHONY: clean
12. clean:
13. "cleanning project"
14. -rm main *.o
15. "clean completed"
然后在终端键入make编译可得:
显示在soapC.c文件中有错误,那么我们打开soapC.c文件,通过Ctrl+F查找soap_set_attr,找到后将其简单的注释掉:
然后,make clean,在make:
成功后,当前文件夹下就产生了可执行文件main,在终端输入./main执行main可得android模拟器中收到了我们发送的消息(消息内容在main函数的soap_call___ns1__sendBroadcast函数中设置的),
上面的Makefile也可以写成下面这种形式:
1. main: main.o soapC.o soapClient.o stdsoap2.o
2. gcc $^ -o $@
3. main.o: soapH.h AndroidPushNotificationWsSoapBinding.nsmap
4. soapC.o: soapH.h
5. soapClient.o: soapH.h
6. stdsoap2.o: stdsoap2.h
7. .PHONY: clean
8. clean:
9. "cleanning project"
10. -rm main *.o
11. "clean completed"
有关Makefile,Linux C编程一站式学习这本书的第22章讲的表较清晰、明了。
我们后面还要在此基础上做进一步开发,不可避免的会引入其他文件进来。这样以来,我们写的文件与通过gsoap工具产生的代码混在一起,就会比较混乱。所以更好的一种方式是我们将gsoap所产生的代码打包为静态库或动态库,我们在其他代码中需要推送功能时,只需要链接这个库就可以了,先面来看处理过程。
首先将gsoap产生的代码打包为静态库:
新建一个文件夹tmp2,然后将上面执行make命令后产生的所有.o文件拷贝到tmp2文件夹下,终端进入tmp2文件夹下执行: ar rs libsendnotification.a *.o,可以得到在tmp2文件夹下生成了一个sendnotification..a的静态库:
ar rs libsendnotification.a *.o;表示将当前目录下的所有.o文件打包为libsendnotification.a,从这点来看,ar命令有点类似与tar,起一个打包的作用。库文件名都是以lib开头,静态库以.a作为后缀,表示Archive。选项r标识将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。s是专用于生成静态库的,标识为静态库创建索引,这个索引被链接器使用。
然后将之前的main,c、AndroidPushNotificationWsSoapBinding.nsmap和所有的头文件拷贝到当前目录并删除当前目录中所有.o文件当前目录内容如下:
然后在终端输入:gcc main.c -L. -lsendnotification -I. -o main可以看到文件夹下多出了一个可执行文件main:
在终端执行main,可得到android模拟器中收到了发送的消息:
现在来解释下:gcc main.c -L. -lsendnotification -I. -o main。-L选项告诉编译器去哪里找需要的库文件,-L.标识在当前目录中找。-lsendnotification告诉编译器要链接libsendnotification库,-I告诉编译器去哪里找头文件。注意,即使是库文件就在当前目录,编译器默认也不会去找的,所以-L.选项不能少。而如果头文件就在当前目录,-I是可以省略的,上面-I选项就可以省略。用-l选项链接库时,编译器会首先找有没有共享库,如果有就链接它,如果没有就找有没有静态库,如果有就链接它。所以编译器是优先考虑共享库的,如果希望编译器只链接静态库,可以指定-static选项。
那么链接共享库和链接静态库有什么区别呢?在链接libc共享库时只是指定了动态连接器和改程序所需要的库文件,并没有真的做链接,可执行文件main中调用的libc库函数仍然是未定义符号,需要在运行时做动态链接。而在链接静态库时,链接器会把静态库中的目标文件取出来和可执行文件真正的链接在一起。
同样下面我们来看下,将gsoap产生的文件打包为共享库。
首先新建一个文件夹tmp3,然后将gsoap产生所有文件拷贝到tmp3中
终端中进入当前文件夹,执行gcc -c -fPIC *.c,-f后面跟一些编译选项,PIC是其中一种,表示生成位置无关代码。然后输入:gcc -shared -o libsendnotification.so *.o;
这时出现错误,是soapClientLib.c和soapClient.c中定义的函数冲突,将soapClientLib.c和soapClientLib.o删除,然后再执行:gcc -shared -o libsendnotification.so *.o,就好了。然后我们将此文件夹下除了libsendnotification.so和AndroidPushNotificationWsSoapBinding.nsmap和头文件留下外,其他文件都删除,然后将之前的main.c文件拷贝到当前文件夹,当前文件夹的内容为:
然后在终端输入:gcc main.c -g -L. -lsendnotification -o main,把main.c和共享库编译链接在一起,然后运行:./main。但出现问题,编译的时候没有问题,由于指定了-L.选项,编译器可以在当前目录下找到libsendnotification.so,而运行时却说找不到libsendnotification.so。那么运行时在那些路径下找共享库呢?从ld.so的Man Page可以查到共享库路径的搜索顺序:
1.首先在环境变量LD_LIBRARY_PATH所记录的路径中查找。
2.然后从缓存文件/etc/ld.so.cache中查找。这个缓存文件有ldconfig命令读取配置文件/etc/ld.so.conf之后生成。
3.如果上述步骤都找不到,则到默认的系统路径中查找,先是/usr/lib然后是/lib。
第一种方法,在运行main时通过环境变量LD_LIBRARY_PATH把当前目录添加到共享库的搜索路径:在终端输入:LD_LIBRARY_PATH = ../main。这种方法只适合在开发中临时用一下,通常LD_LIBRARY_PATH是不退推荐使用的,尽量不要设置这个环境变量。
第二种方法,这是最常用的方法。把libsendnotification所在的木路的绝对路径(比如/home/akaedu/somedir)添加到/etc/ld.so.conf中(该文件中每个路径占一行),然后终端运行:sudo ldconfig -v。ldconfig命令除了处理/etc/ld.so.conf中配置的目录外,还处理一些默认目录,入/lib,/usr/lib等,处理之后生成/etc/ld.so..cache缓存文件,动态链接器就从这个缓存文件中搜索共享库。
第三种方法最简单,就是把libsendnotification.so拷贝到/usr/lib或/lib目录中,这样可以确保动态链接器能找到这个共享库。
我用的是上面的第三种方法,将libsendnotification.so剪切拷贝到/usr/lib目录中:并且因为编译器在找不到头文件的情况下会到/usr/include目录下去找,所以我还将所有的头文件和AndroidPushNotificationWsSoapBinding.nsmap文件剪切拷贝到/usr/include目录中,这是当前文件夹下只剩下main.c文件了:
然后终端执行:gcc main.c -lsendnotification -o main,然后./main
模拟器中androidpn客户端接收到了消息:
同样我们可以将libsendnotification.a放入/usr/lib目录下,然后执行:gcc main.c -L. -lsendnotification -I. -o main。这样也可以。
下我用GTK写了一个简单的与用户交互的图形界面,保存为main.c文件:将其放入/home/yanlong/TMP目录下如下图所示:
文件内容为:
1. #include <gtk/gtk.h>
2. #include <stdio.h>
3. #include <string.h>
4. #include "soapH.h"
5. #include "AndroidPushNotificationWsSoapBinding.nsmap"
6.
7. GtkWidget *title_entry, *message_entry, *username_entry,*title2_entry, *message2_entry;
8.
9. void
10. {
11. gtk_main_quit();
12. }
13.
14. void
15. {
16. struct
17. struct
18. struct
19.
20. "1234567890"; //apikey
21. ns2sendallbr.arg1 = gtk_entry_get_text(GTK_ENTRY(title_entry));
22. ns2sendallbr.arg2 = gtk_entry_get_text(GTK_ENTRY(message_entry));
23.
24. soap_init(&mysoap);
25. "http://localhost:8080/androidpnservice/androidpush","", &ns2sendallbr, &ns2sendallbrr);
26.
27. soap_destroy(&mysoap);
28. soap_end(&mysoap);
29. soap_done(&mysoap);
30.
31. }
32.
33. void
34. {
35. struct
36.
37. struct
38. struct
39.
40. "1234567890"; //apikey
41. ns2sendbr.arg1 = gtk_entry_get_text(GTK_ENTRY(title_entry));
42. ns2sendbr.arg2 = gtk_entry_get_text(GTK_ENTRY(message_entry));
43.
44. soap_init(&mysoap);
45. "http://localhost:8080/androidpnservice/androidpush","",&ns2sendbr,&ns2sendbrr);
46.
47. soap_destroy(&mysoap);
48. soap_end(&mysoap);
49. soap_done(&mysoap);
50.
51. }
52.
53. void
54. {
55. struct
56.
57. struct
58. struct
59.
60. "1234567890"; //apikey
61. //username
62. //title
63. //message
64.
65. soap_init(&mysoap);
66. "http://localhost:8080/androidpnservice/androidpush","", &ns2sendno, &ns2sendnor);
67.
68. soap_destroy(&mysoap);
69. soap_end(&mysoap);
70. soap_done(&mysoap);
71.
72. }
73.
74.
75. int main( int argc, char
76. {
77. GtkWidget *window;
78. GtkWidget *notebook;
79. GtkWidget *table;
80. GtkWidget *label;
81. int
82. char
83. GtkWidget *title_label, *message_label, *username_label,*title2_label, *message2_label;
84. //GtkWidget *title_entry, *message_entry, *username_entry,*title2_entry, *message2_entry;
85. GtkWidget *hbox1, *hbox2, *hbox3, *hbox4, *hbox5;
86. GtkWidget *vbox1,*vbox2;
87. GtkWidget *pushall_button;
88. GtkWidget *pushonline_button;
89. GtkWidget *pushnotification_button;
90.
91. gtk_init(&argc, &argv);
92.
93. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
94. "AndroidPush");
95. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
96. gtk_window_set_default_size(GTK_WINDOW(window), 350, 300);
97. "destroy",
98. GTK_SIGNAL_FUNC ( closeApp), NULL);
99.
100. "title:");
101. "message:");
102. "title:");
103. "message:");
104. "username:");
105. title_entry = gtk_entry_new();
106. message_entry = gtk_entry_new();
107. title2_entry = gtk_entry_new();
108. message2_entry = gtk_entry_new();
109. username_entry = gtk_entry_new();
110.
111. "Push All");
112. "Push Oneline");
113. "SendNotification");
114.
115. "clicked",
116. GTK_SIGNAL_FUNC(pushallbutton_clicked), NULL);
117. "clicked",
118. GTK_SIGNAL_FUNC(pushonlinebutton_clicked), NULL);
119. "clicked",
120. GTK_SIGNAL_FUNC(pushnotification_clicked), NULL);
121.
122. hbox1 = gtk_hbox_new ( TRUE, 5 );
123. hbox2 = gtk_hbox_new ( TRUE, 5 );
124. hbox3 = gtk_hbox_new ( TRUE, 5 );
125. hbox4 = gtk_hbox_new ( TRUE, 5 );
126. hbox5 = gtk_hbox_new ( TRUE, 5 );
127.
128. vbox1 = gtk_vbox_new ( FALSE, 10);
129. vbox2 = gtk_vbox_new ( FALSE, 15);
130.
131. gtk_box_pack_start(GTK_BOX(hbox1), title_label, TRUE, FALSE, 5);
132. gtk_box_pack_start(GTK_BOX(hbox1), title_entry, TRUE, FALSE, 5);
133. gtk_box_pack_start(GTK_BOX(hbox2), message_label, TRUE, FALSE, 5);
134. gtk_box_pack_start(GTK_BOX(hbox2), message_entry, TRUE, FALSE, 5);
135. gtk_box_pack_start(GTK_BOX(hbox3), username_label, TRUE, FALSE, 5);
136. gtk_box_pack_start(GTK_BOX(hbox3), username_entry, TRUE, FALSE, 5);
137. gtk_box_pack_start(GTK_BOX(hbox4), title2_label, TRUE, FALSE, 5);
138. gtk_box_pack_start(GTK_BOX(hbox4), title2_entry, TRUE, FALSE, 5);
139. gtk_box_pack_start(GTK_BOX(hbox5), message2_label, TRUE, FALSE, 5);
140. gtk_box_pack_start(GTK_BOX(hbox5), message2_entry, TRUE, FALSE, 5);
141.
142. gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, FALSE, 5);
143. gtk_box_pack_start(GTK_BOX(vbox1), hbox2, FALSE, FALSE, 5);
144. gtk_box_pack_start(GTK_BOX(vbox1), pushall_button, FALSE, FALSE, 5);
145. gtk_box_pack_start(GTK_BOX(vbox1), pushonline_button, FALSE, FALSE, 5);
146.
147. gtk_box_pack_start(GTK_BOX(vbox2), hbox4, FALSE, FALSE, 5);
148. gtk_box_pack_start(GTK_BOX(vbox2), hbox5, FALSE, FALSE, 5);
149. gtk_box_pack_start(GTK_BOX(vbox2), hbox3, FALSE, FALSE, 5);
150. gtk_box_pack_end(GTK_BOX(vbox2), pushnotification_button, FALSE, FALSE, 5);
151.
152.
153.
154.
155. gtk_container_set_border_width (GTK_CONTAINER (window), 10);
156.
157. table = gtk_table_new (3, 6, FALSE);
158.
159. gtk_container_add (GTK_CONTAINER (window), table);
160.
161. /* 创建一个新的笔记本,将标签页放在顶部 */
162.
163. notebook = gtk_notebook_new ();
164.
165. gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
166.
167. gtk_table_attach_defaults (GTK_TABLE (table), notebook, 0, 6, 0, 1);
168.
169. gtk_widget_show (notebook);
170.
171. /* 在笔记本后面追加几个页面 */
172.
173. "Page %d", 1);
174. label = gtk_label_new (bufferl);
175. gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox1, label);
176.
177. "Page %d", 2);
178. label = gtk_label_new (bufferl);
179. gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox2, label);
180.
181.
182. gtk_widget_show_all(window);
183. gtk_main ();
184.
185. return
186. }
:使用命令:gcc main.c -g -lsendnotification -o main `pkg-config --cflags --libs gtk+-2.0`来编译我们的main.c文件产生可执行成许main,参数-g -lsendnotification是为了链接我们上面所产生共享库libsendnotification.so,参数`pkg-config --cflags --libs gtk+-2.0`是编译GTK+图形界面所必须的参数,其中``是键盘Tab键上面的那个键不是两个单引号。
GTK+来开发图形界面的所以必须在系统中安装GTK+所需要的软件包(这个百度就可以了)。
终端 执行可执行文件main: ./main 可得到如下的图形界面:
页面中title和message分别用于输入用户要发送信息的标题和信息内容,Push All按钮用来向所有用户发送信息,Push Online按钮用于向所有在线用户发送信息。
效果如下图所示:
Page2页面用于向指定的用户发送信息,title和message的意义与Page1页面中的相同,username用于输入想要发送的目标用户的用户名,当所有选项都填完了后,单击SendNotification就可以向指定的用户名为username的用户发送消息了。
效果如下图所示:
本系统实现存在一个问题,就是:不管是java 使用 androidpn.jar
包或者
C
程序使用
libsendnotification.
库文件来向目标设备发送信息,都必须首先在我们的程序运行前到
这个页面单击Submit来向目标设备发送信息,然后我们的程序发送的信息,目标设备才能收的到,否则我们的程序发送的信息目标设备接收不到。就好象必须得用这个页面发送下信息,来做个初始化的操作,打通渠道一样。
使用时,我们只需要将经过Webservice处理过的Androidpn服务器端部署到tomcat上,然后我们需要推送的程序只需要链接libsendnotification.so库就好了。