- 论坛徽章:
- 0
|
300 行代码 打磨自己的搜索引擎 myso (示例源码下载)
关键字:
myso 搜索引擎 C语言 socket proxy CSP eybuild
功能概述:
A. 本示例以 "五笔编码&汉语拼音查询" 和 "IP地址所在地和域名" 为例.
B. 适合 OS 平台包含: Linux/Unix, Windows
C. Windows 平台支持自动 IE 代理搜索, 以适应连接 intenet.
D. 直接调用系统的 socket 相关 C 接口API.
E. 支持自动域名解析.
示例抓图:
http://www.eybuild.com/develop/images/myso.jpg
![]()
1. 问题提出:
我们要打磨一个自己的搜索引擎,
(1) 需要有数据源. 数据源从哪里来?
(2) 需要用动态页面处理用户请求, 采用什么动态网页技术?
(3) 如何提高搜索引擎的并发性能, 减小代码量和运行时的内存尺寸?
(3) 我的服务器放在哪里, 如何使我的引擎适应更广泛的连接方式和应用平台?
即: 如何支持直接连接/代理连接连接方式, 同时支持 Windows/Unix 等多种 OS 平台.
2. 分析与方案:
1. 数据源我可以直接动态从 intenet 上获取. 这样既能减小我们对数据源的维护量,
也能保证数据是最新. 如本例采用的来自 http://www.ip138.com/ 的数据源.
2. 支持动态网页的技术很多, 如 CGI(CSP/eybuild), PHP, ASP, JSP, ...
3. 考虑到并发性能, 代码尺寸, 我们采用能用 C 语言编写 CGI 的 CSP 技术.
CSP/eybuild 不需要安装任何脚本解释器, 运行最低只要 64K 的动态内存即可,
具有更泛的 C 语言平台支撑, 易学易用.
4. CSP 直接以 C 为基础, 可以在 Unix/Windows 上移植使用, 可以直接调用系统中的
任何系统 API, 集成功能强大. 编写代理连接方式等功能十分容易(见示例源码).
3. 打磨原理:
如下图所示, 展示了一个整个处理流程的意示图:
- | +---------+ +----------+ +---------+
- O | | |---HTTP--->| MySo |---HTTP--->| DATA |
- --- |------>| Browser | |WEB Server| | Server |
- / \ | | PC |<--HTTP--- | (CGI) |<--HTTP----| |
- (man) +---------+ +----------+ +---------+
- -- 1 ------------- 2 --------------------- 3 ------------------- 4 ----
复制代码
(1) 最左是一个用户, 向浏览器的表单中输入数据.
(2) 中间偏左是浏览器, 它把用户请求通过 http 数据提交给 myso.cgi 所在 webserver.
(3) Web服务器执行 myso.cgi. 然后 myso.cgi 会连接远程的数据服务器(能提供搜索功能),
myso.cgi 收到查询结果后, 分析查询结果. 重新构造自己的查询结果.
(4) Data Server 其实就是个能提供搜索功能的服务器, 它本身就是个搜索引擎.
我们这里是借用它的数据源, "打磨" 自己的搜索引擎.
好了, 经过上面的分析可以已经能看出整个处理过程了.
可能有人会说, 这是一种 "盗链". 是的, 必须承认这是一种盗链, 不过本示例还解决了
防 "盗链" 的相关技术(见下文). 这里就是为什么标题中称为"打磨"的本意.
4. 原码分析:
(1) 源码中设及 API 说明等的相关说明:
1) <% %> - 用于在 HTML 等模板文件中嵌入 C 程序 (即 CSP 语句)
2) @g - 标记块, 用于在 HTML 等模板文件中 嵌入 C 函数等全局性信息.
3) @b - 将被 CSP 页开始处执行.
4) G() - 函数, 从用户提交的表单中获取输入的值, 相关于 getParameter() 函数.
5) 其它 API 请参阅相关手册.
(2) 源码可以分为三部分理解:
1) html 部分 (1-71 行), 这部分主要是 html 模板
2) @b 部分 (73-165 行), 这部分是当请求提交给该页面时, 被首先执行. 即: begin
3) @g 部分 (167-328行), 这部分定义了与页面相关的全局的函数,
可以在程序任意处调用. 即: global.
(3) @b 部分(73-165 行):
1) 74 - 87 行, 说明了该函数需要包含的 C 程序的头文件, 注意用 @include 不是 #include
2) 88 - 94 定义了, C 的局变量
3) 96 - 123 用于处理五笔编码和汉语拼音的查询.
4) 128- 164 用于处理五 IP地址所在地和域名 的查询, 与(3) 的流程类似.
5) 97 - 98 确认是用提交的数据不为空, 并且成功连接远程序数据服务器
6) 103-110 手工构造 HTTP 的 POST 请求, 以向远程服务器提交查询.
7) 112-116 发送 http 请求并接收应答到一个 buffer 中.
8) 117-121 从应答中分离出来我们需要的数据, 应答的是一个 html 文件.
9) 160-164 如果连接错误, 则根据错误类型构造错误信息并存入 errmsg 中.
(4) html 部分(1-71 行):
1) 56 - 60 行, 通过 <% %> 嵌入一断 C 程序的判断语句,
如果是错误则输出错误到当前处, 否则把查询结果输出到当前处.
2) 69 行的 <% //=buff %> 是调试用的, 已被 // 注释掉了.
可以把 // 去掉以把来自数据服务器的全部应答打印出来.
3) 其它的就不细说了.
(5) @g 部分 (167-328行)
1) 这里定义了 6 个函数
int SocketInit();
int get_proxy(char * proxy_ip, short * port);
char * make_error(char * errmsg);
int connect_query_host(char * hostname, char * errmsg);
int send_http_req(int sock, char * http_req);
int recv_http_req(int sock, char * http_req, int maxlen);
2) 前两个是 windows 专用.
SocketInit() 用于初始化 socket
get_proxy() 从注册表中读取 IE 的代理设置.
3) connect_query_host() 用于连接远程数据服务器,
当设置代理时, 先尝试使用代理连接.
4) send_http_req() 和 recv_http_req() 发送和接收 http 请求.
(6) 附源码
001 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
002 "http://www.w3.org/TR/html4/loose.dtd">
003 <!--
004 源码来自: http://www.eybuild.com
005 支持平台: Unix/Windows
006 Unix编译: make clean all
007 变更请通知作者: eybuild@hotmail.com
008 -->
009 <html>
010 <head>
011 <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
012 <title>五笔编码查询</title>
013 <link href="/css/tq.css" rel="stylesheet" type="text/css">
014 <script language="javascript" src="<% =romPrefix(NULL) %>/js/myso.js"></script>
015 </head>
016 <style><!--
017 body,td,a,p,{font-family:arial,sans-serif;font-size:14px}
018 --></style>
019 <body>
020 <p>
021 <center><img src="<% =romPrefix(NULL) %>/img/myso.jpg"></center>
022 <table width="430" border="0" align="center" cellspacing="0";">
023 <tr>
024 <td colspan="3"> </td>
025 </tr>
026 <tr>
027 <td colspan="3"><strong>五笔编码&汉语拼音查询</strong></td>
028 </tr>
029 <form name="form1" method="post" action="<% =thisCgiPrefix() %>" onsubmit="return check()">
030 <tr>
031 <td width=135>请输入汉字:</td>
032 <td><input name="querykey" type="text" size="20" value=""></td>
033 <td><input type="submit" name="wubiquery" value="开始查询""></td>
034 </tr>
035 </form>
036 <tr>
037 <td colspan="3"> </td>
038 </tr>
039 <tr>
040 <td colspan="3"><strong>IP地址所在地/域名查询</strong></td>
041 </tr>
042 <form name="ipform" method=post action="<% =thisCgiPrefix() %>" onsubmit="return checkIP();">
043 <tr>
044 <td>请输入IP或域名:</td>
045 <td><input type="text" name="ip" size="20">
046 <td><input type="submit" name="ipquery" value="开始查询">
047 <input TYPE="hidden" name="action" value="2">
048 </tr>
049 </form>
050 <tr>
051 <td colspan="3"> </td>
052 </tr>
053 </table>
054 <table width="430" border="0" align="center" cellspacing="0">
055 <tr>
056 <td><font color=red><% if (!isblankstr(errmsg)) /* report error */
057 print("<b>查询失败:</b> %s", errmsg);
058 else
059 print("%.*s", qlen, qstart); /* output query result */
060 %></font>
061 </td>
062 </tr>
063 <tr><td> </td></tr>
064 <tr><td><center><a href="http://www.eybuild.com" target=_blank>源码下载<A> |
065 <a href="maito=eybuild@hotmail.com" target=_blank>联系作者<A> |
066 <a href="http://www.eybuild.com" target=_blank>更多信息<A> </center></td></tr>
067 <tr><td><center>(来自: http://www.eybuild.com)</center></td></tr>
068 </table>
069 <xmp><% //=buff %></xmp>
070 </body>
071 </html>
072
073 <% @b
074 @include <undef.h>
075 @ifdef WIN32
076 @include <winsock2.h>
077 @define close closesocket
078 @else
079 @include <unistd.h>
080 @include <errno.h>
081 @include <sys/types.h>
082 @include <sys/socket.h>
083 @include <netinet/in.h>
084 @include <arpa/inet.h>
085 @include <netdb.h>
086 @endif /* WIN32 */
087 @include <ebdef.h>
088 int sock = 0;
089 char buff[4096] = "";
090 int maxlen = sizeof(buff);
091 char * qstart = ""; /* query result start address */
092 int qlen = 0; /* query result length */
093 char errmsg[256] = "";
094 int ret = OK;
095
096 /* wubi query */
097 if (!isblankstr(G("querykey")) &&
098 (sock = connect_query_host("qq.ip138.com", errmsg)) > 0)
099 {
100 char req_buf[1024] = "";
101 char query[256] = "";
102
103 /* make query and http header */
104 sprintf(query, "querykey=%s", urlEncode(G("querykey")));
105 sprintf(req_buf, "POST http://qq.ip138.com/wb/wb.asp? HTTP/1.0\r\n"
106 "Content-Type: application/x-www-form-urlencoded\r\n"
107 "Content-Length: %d\r\n"
108 "Host: qq.ip138.com\r\n"
109 "\r\n"
110 "%s", strlen(query), query);
111
112 /* send ==> receive ==> parse result */
113 if ((ret=send_http_req(sock, req_buf)) > 0)
114 {
115 if ((ret=recv_http_req(sock, buff, sizeof(buff))) > 0)
116 {
117 /* separate result */
118 if (NULL != (qstart=strstr(buff, "<p align=\"center\">\r\n")))
119 {
120 qlen = strstr(qstart, "</p>") - qstart;
121 }
122 }
123 }
124
125 close(sock);
126 }
127
128 /* ip query */
129 if (!isblankstr(G("ip")) &&
130 (sock = connect_query_host("www.ip138.com", errmsg)) > 0)
131 {
132 char req_buf[1024] = "";
133 char query[256] = "";
134
135 /* make query and http header */
136 sprintf(query, "ip=%s&action=2", G("ip"));
137 sprintf(req_buf, "POST http://www.ip138.com/ips8.asp HTTP/1.0\r\n"
138 "Referer: http://www.ip138.com/\r\n"
139 "Content-Type: application/x-www-form-urlencoded\r\n"
140 "Content-Length: %d\r\n"
141 "Host: www.ip138.com\r\n"
142 "\r\n"
143 "%s", strlen(query), query);
144
145 /* send ==> receive ==> parse result */
146 if ((ret=send_http_req(sock, req_buf)) > 0)
147 {
148 if ((ret=recv_http_req(sock, buff, sizeof(buff))) > 0)
149 {
150 if (NULL != (qstart=strstr(buff, "<ul class=\"ul1\">")))
151 {
152 qlen = strstr(qstart, "</td>") - qstart;
153 }
154 }
155 }
156
157 close(sock);
158 }
159
160 /* make error message */
161 if (sock < 0 || ret < 0)
162 {
163 make_error(errmsg);
164 }
165 %>
166
167 <% @g
168
169 #ifdef WIN32
170 char * make_error(char * errmsg)
171 {
172 int errcode;
173 LPVOID lpMsgBuf;
174
175 if (OK == (errcode=GetLastError()))
176 return "Ready";
177
178 FormatMessage(
179 FORMAT_MESSAGE_ALLOCATE_BUFFER |
180 FORMAT_MESSAGE_FROM_SYSTEM |
181 FORMAT_MESSAGE_IGNORE_INSERTS,
182 NULL,
183 errcode,
184 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
185 (LPTSTR) &lpMsgBuf,
186 0,
187 NULL
188 );
189
190 // Process any inserts in lpMsgBuf.
191 sprintf(errmsg, "%d: %s", errcode, lpMsgBuf);
192
193 // Free the buffer.
194 LocalFree( lpMsgBuf );
195
196 return errmsg;
197 }
198
199 int SocketInit()
200 {
201
202 WSADATA wsaData;
203
204 if ( WSAStartup( 0x101, &wsaData ) ) {
205 fprintf( stderr, "Could not initialize WinSock\n" );
206 return ERROR;
207 }
208
209 if ( 0x101 != wsaData.wVersion ) {
210 fprintf( stderr, "Version %x not supported\n", wsaData.wVersion );
211 return ERROR;
212 }
213
214 return OK;
215 }
216
217 /* try to get proxy from windows register */
218 int get_proxy(char * proxy_ip, short * port)
219 {
220 HKEY hkey;
221 DWORD d=sizeof(char);
222 char * path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
223 unsigned long type;
224 unsigned char data[256] = "";
225 unsigned long dlen = sizeof(data);
226 char * pstr = NULL;
227
228 proxy_ip[0] = '\0';
229 if (ERROR_SUCCESS==RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_READ, &hkey))
230 {
231 if (ERROR_SUCCESS!=RegQueryValueEx(
232 hkey, "ProxyServer", 0, &type, (unsigned char*)data, &dlen))
233 {
234 RegCloseKey(hkey);
235 return -1;
236 }
237
238 if (NULL != (pstr=strchr(data, ':')))
239 {
240 sprintf(proxy_ip, "%.*s", pstr-data, data);
241 *port = (short)atoi(pstr+1);
242 }
243
244 RegCloseKey(hkey);
245 return 0;
246 }
247
248 return -1;
249 }
250
251 #else /* WIN32 */
252 /* for unix */
253 char * make_error(char * errmsg)
254 {
255 strcpy(errmsg, strerror(errno));
256
257 return errmsg;
258 }
259 #endif /* WIN32 */
260
261
262 int connect_query_host(char * hostname, char * errmsg)
263 {
264 int sock = 0;
265 struct sockaddr_in sa;
266 char dstip[20] = "";
267 short port = 0;
268
269 #ifdef WIN32
270 if (SocketInit() < 0)
271 return ERROR;
272
273 memset(&sa, 0, sizeof(sa));
274 sa.sin_family=AF_INET;
275
276 /* by proxy */
277 if (!get_proxy(dstip, &port) && !isblankstr(dstip))
278 {
279 sa.sin_port = htons(port);
280 sa.sin_addr.s_addr = inet_addr(dstip);
281 }
282 /* by domain name */
283 else
284 #endif /* WIN32 */
285 {
286 struct hostent * ht = gethostbyname(hostname);
287
288 /* parse domain error */
289 if (NULL == ht)
290 return ERROR;
291
292 sa.sin_port = htons(80);
293 sa.sin_addr.s_addr = (*(struct in_addr *)ht->h_addr_list[0]).s_addr;
294 }
295
296 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
297 return ERROR;
298
299 if (connect(sock, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0)
300 return ERROR;
301
302 return sock;
303 }
304
305
306 int send_http_req(int sock, char * http_req)
307 {
308 int len;
309
310 len = send(sock, http_req, strlen(http_req), 0);
311
312 return len;
313 }
314
315
316 int recv_http_req(int sock, char * http_req, int maxlen)
317 {
318 int len;
319 int dlen = 0;
320
321 while(dlen < maxlen-1 && (len=recv(sock, http_req+dlen, maxlen-dlen-1, 0)) > 0)
322 {
323 dlen += len;
324 }
325
326 return dlen;
327 }
328 %>
附录:相关参考
1. CSP/eybuild 的主站点:http://www.eybuild.com
2.《eyBuild 中文手册》 下载地址:http://www.eybuild.com/develop/doc/manual/eybuild_manual_ch.pdf
3.《CSP/eybuild APIs参考》, 在线阅读:http://www.eybuild.com/develop/doc/API/libIndex.htm
4. CSP/eybuild API 函数列表, 在线阅读:http://www.eybuild.com/develop/doc/API/rtnIndex.htm
5.《CSP/eybuild FAQ(常见问题)》 下载地址:http://www.eybuild.com/develop/doc/manual/eybuild_faq_ch.pdf
~ 完 ~
2007.1.24 |
|