前言

TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:https://sourceforge.net/projects/tinyhttpd/,因为是学习型的代码,已经有好多年没更新了,也没什么更新必要,整个代码才500多行,10多个函数,对于学习HTTP服务器的原理来说非常有帮助,把代码读一遍,再按照执行处理流程调试一下,基本上可以搞清楚Web服务器在收到静态页面请求和CGI请求的一些基本处理逻辑。源代码的注释我这里就不讲了,本身代码比较简单,而且网上这样的文章汗牛充栋,可以去后面的参考文档阅读下。

本文主要是将TinyHTTPd进行一些简单移植,使其可以在Windows上面运行调试,让只有Windows开发调试环境的小伙伴也能够学习学习。

修改明细

支持Windows部分

1、  Windows下的socket支持(微小改动,变量,宏调整等等)。

2、  接入基于Windows平台的一个微型线程池库(项目在用),对于每个新的http请求抛到线程池处理,源代码使用的是基于Linux的pthread。

3、  部分字符串比较函数修改为windows平台的对应支持函数,以及其它一些相应兼容性修改。

4、  CGI部分支持了Python脚本和Windows批处理脚本,其它的如果需要支持可以进行修改,另外,CGI部分目前实现比较粗糙,完全是为了体现一下CGI请求的原理(POST的CGI处理仅仅是把提交的数据返回给客户端显示)。

5、  CGI部分使用了匿名管道,匿名管道不支持异步读写数据,因此需要控制读写匿名管道的次数(建议仅读一次,并且CGI的返回字符长度不要超过2048字节)。

优化

1、  给客户端返回数据时,合并了需要发送的数据,使用send一次发送,而不是每次发送几个字符,不过这里没有进行send失败的错误处理,学习代码吧,如果是商用代码,send失败是需要重试的(当然,商用代码一般都是使用异步socket),这个地方之前作者分成多次发送的目的可能是为了体现网络数据传输的原理。

2、  合并了一些公用代码。

3、  代码里面直接写死了绑定80端口,如果需要由系统自动分配端口就把这句代码:u_short port = 80修改为u_short port = 0,绑死80端口是为了使用浏览器测试时比较方便。

bug修改

1、  cat函数里面使用fgets读取文件进行数据发送时,有可能发送不完整。

资源补充

1、  补充批处理的cgi支持和py脚本的cgi支持(都比较简单,需要注意的是py脚本支持需要本地安装python2.x的环境)。

测试情况

主页

URL:http://127.0.0.1/

其它静态页面

URL:http://127.0.0.1/detect.html

python CGI

URL:http://127.0.0.1/cgipy?p.py

批处理 CGI

URL:http://127.0.0.1/cgibat?p.bat

POST CGI

URL:http://127.0.0.1/index.html

源代码

本来不想帖代码的,还是贴一点吧,工程下载请点这里

  1. /* -------------------------------------------------------------------------
  2. //  文件名     :   tinyhttp.cpp
  3. //  创建者     :   magictong
  4. //  创建时间    :   2016/11/16 17:13:55
  5. //  功能描述    :   support windows of tinyhttpd, use mutilthread...
  6. //
  7. //  $Id: $
  8. // -----------------------------------------------------------------------*/
  9. /* J. David's webserver */
  10. /* This is a simple webserver.
  11. * Created November 1999 by J. David Blackstone.
  12. * CSE 4344 (Network concepts), Prof. Zeigler
  13. * University of Texas at Arlington
  14. */
  15. /* This program compiles for Sparc Solaris 2.6.
  16. * To compile for Linux:
  17. *  1) Comment out the #include <pthread.h> line.
  18. *  2) Comment out the line that defines the variable newthread.
  19. *  3) Comment out the two lines that run pthread_create().
  20. *  4) Uncomment the line that runs accept_request().
  21. *  5) Remove -lsocket from the Makefile.
  22. */
  23. #include "stdafx.h"
  24. #include "windowcgi.h"
  25. #include "ThreadProc.h"
  26. #include <stdio.h>
  27. #include <ctype.h>
  28. #include <stdlib.h>
  29. #include <string.h>
  30. #include <sys/stat.h>
  31. #include <sys/types.h>
  32. #include <WinSock2.h>
  33. #pragma comment(lib, "wsock32.lib")
  34. #pragma warning(disable : 4267)
  35. #define ISspace(x) isspace((int)(x))
  36. #define SERVER_STRING "Server: tinyhttp /0.1.0\r\n"
  37. // -------------------------------------------------------------------------
  38. // -------------------------------------------------------------------------
  39. // 类名       : CTinyHttp
  40. // 功能       :
  41. // 附注       :
  42. // -------------------------------------------------------------------------
  43. class CTinyHttp
  44. {
  45. public:
  46. typedef struct tagSocketContext
  47. {
  48. SOCKET socket_Client;
  49. tagSocketContext() : socket_Client(-1) {}
  50. } SOCKET_CONTEXT, *PSOCKET_CONTEXT;
  51. /**********************************************************************/
  52. /* A request has caused a call to accept() on the server port to
  53. * return.  Process the request appropriately.
  54. * Parameters: the socket connected to the client */
  55. /**********************************************************************/
  56. void accept_request(nilstruct&, SOCKET_CONTEXT& socket_context)
  57. {
  58. printf("Tid[%u] accept_request\n", (unsigned int)::GetCurrentThreadId());
  59. #ifdef _DEBUG
  60. // 测试是否可以并发
  61. ::Sleep(200);
  62. #endif
  63. char buf[1024] = {0};
  64. int numchars = 0;
  65. char method[255] = {0};
  66. char url[255] = {0};
  67. char path[512] = {0};
  68. int i = 0, j = 0;
  69. struct stat st;
  70. int cgi = 0;      /* becomes true if server decides this is a CGI program */
  71. char* query_string = NULL;
  72. SOCKET client = socket_context.socket_Client;
  73. numchars = get_line(client, buf, sizeof(buf));
  74. // 获取HTTP的请求方法名
  75. while (j < numchars && !ISspace(buf[j]) && (i < sizeof(method) - 1))
  76. {
  77. method[i] = buf[j];
  78. i++; j++;
  79. }
  80. method[i] = '\0';
  81. if (_stricmp(method, "GET") != 0 && _stricmp(method, "POST"))      // 只处理GET请求
  82. {
  83. if (numchars > 0)
  84. {
  85. discardheaders(client);
  86. }
  87. unimplemented(client);
  88. closesocket(client);
  89. return;
  90. }
  91. if (_stricmp(method, "POST") == 0)
  92. cgi = 1; // POST请求,当成CGI处理
  93. // 获取到URL路径,存放到url字符数组里面
  94. i = 0;
  95. while (ISspace(buf[j]) && (j < sizeof(buf)))
  96. {
  97. j++;
  98. }
  99. while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
  100. {
  101. url[i] = buf[j];
  102. i++;
  103. j++;
  104. }
  105. url[i] = '\0';
  106. if (_stricmp(method, "GET") == 0)
  107. {
  108. query_string = url;
  109. while ((*query_string != '?') && (*query_string != '\0'))
  110. query_string++;
  111. if (*query_string == '?')
  112. {
  113. // URL带参数,当成CGI处理
  114. cgi = 1;
  115. *query_string = '\0';
  116. query_string++;
  117. }
  118. }
  119. sprintf_s(path, 512, "htdocs%s", url);
  120. if (path[strlen(path) - 1] == '/')
  121. {
  122. // 补齐
  123. strcat_s(path, 512, "index.html");
  124. }
  125. if (stat(path, &st) == -1)
  126. {
  127. // 文件不存在
  128. if (numchars > 0)
  129. {
  130. discardheaders(client);
  131. }
  132. not_found(client);
  133. }
  134. else
  135. {
  136. // 如果是文件夹则补齐
  137. if ((st.st_mode & S_IFMT) == S_IFDIR)
  138. strcat_s(path, 512, "/index.html");
  139. if (st.st_mode & S_IEXEC)
  140. cgi = 1; // 具有可执行权限
  141. if (!cgi)
  142. {
  143. serve_file(client, path);
  144. }
  145. else
  146. {
  147. execute_cgi(client, path, method, query_string);
  148. }
  149. }
  150. closesocket(client);
  151. }
  152. /**********************************************************************/
  153. /* Execute a CGI script.  Will need to set environment variables as
  154. * appropriate.
  155. * Parameters: client socket descriptor
  156. *             path to the CGI script */
  157. /**********************************************************************/
  158. void execute_cgi(SOCKET client, const char *path, const char* method, const char* query_string)
  159. {
  160. char buf[1024] = {0};
  161. int cgi_output[2] = {0};
  162. int cgi_input[2] = {0};
  163. int i = 0;
  164. char c = 0;
  165. int numchars = 1;
  166. int content_length = -1;
  167. buf[0] = 'A'; buf[1] = '\0';
  168. if (_stricmp(method, "GET") == 0)
  169. {
  170. discardheaders(client);
  171. }
  172. else    /* POST */
  173. {
  174. numchars = get_line(client, buf, sizeof(buf));
  175. while ((numchars > 0) && strcmp("\n", buf))
  176. {
  177. buf[15] = '\0';
  178. if (_stricmp(buf, "Content-Length:") == 0)
  179. {
  180. content_length = atoi(&(buf[16]));
  181. }
  182. numchars = get_line(client, buf, sizeof(buf));
  183. }
  184. if (content_length == -1)
  185. {
  186. bad_request(client);
  187. return;
  188. }
  189. }
  190. CWinCGI cgi;
  191. if (!cgi.Exec(path, query_string))
  192. {
  193. bad_request(client);
  194. return;
  195. }
  196. //SOCKET client, const char *path, const char* method, const char* query_string
  197. if (_stricmp(method, "POST") == 0)
  198. {
  199. for (i = 0; i < content_length; i++)
  200. {
  201. recv(client, &c, 1, 0);
  202. cgi.Write((PBYTE)&c, 1);
  203. }
  204. c = '\n';
  205. cgi.Write((PBYTE)&c, 1);
  206. }
  207. cgi.Wait();
  208. char outBuff[2048] = {0};
  209. cgi.Read((PBYTE)outBuff, 2047);
  210. send(client, outBuff, strlen(outBuff), 0);
  211. }
  212. /**********************************************************************/
  213. /* Put the entire contents of a file out on a socket.  This function
  214. * is named after the UNIX "cat" command, because it might have been
  215. * easier just to do something like pipe, fork, and exec("cat").
  216. * Parameters: the client socket descriptor
  217. *             FILE pointer for the file to cat */
  218. /**********************************************************************/
  219. void cat(SOCKET client, FILE *resource)
  220. {
  221. char buf[1024] = {0};
  222. do
  223. {
  224. fgets(buf, sizeof(buf), resource);
  225. size_t len = strlen(buf);
  226. if (len > 0)
  227. {
  228. send(client, buf, len, 0);
  229. }
  230. } while (!feof(resource));
  231. }
  232. /**********************************************************************/
  233. /* Print out an error message with perror() (for system errors; based
  234. * on value of errno, which indicates system call errors) and exit the
  235. * program indicating an error. */
  236. /**********************************************************************/
  237. void error_die(const char *sc)
  238. {
  239. perror(sc);
  240. exit(1);
  241. }
  242. /**********************************************************************/
  243. /* Get a line from a socket, whether the line ends in a newline,
  244. * carriage return, or a CRLF combination.  Terminates the string read
  245. * with a null character.  If no newline indicator is found before the
  246. * end of the buffer, the string is terminated with a null.  If any of
  247. * the above three line terminators is read, the last character of the
  248. * string will be a linefeed and the string will be terminated with a
  249. * null character.
  250. * Parameters: the socket descriptor
  251. *             the buffer to save the data in
  252. *             the size of the buffer
  253. * Returns: the number of bytes stored (excluding null) */
  254. /**********************************************************************/
  255. int get_line(SOCKET sock, char *buf, int size)
  256. {
  257. int i = 0;
  258. char c = '\0';
  259. int n;
  260. while ((i < size - 1) && (c != '\n'))
  261. {
  262. n = recv(sock, &c, 1, 0);
  263. /* DEBUG printf("%02X\n", c); */
  264. if (n > 0)
  265. {
  266. if (c == '\r')
  267. {
  268. n = recv(sock, &c, 1, MSG_PEEK);
  269. /* DEBUG printf("%02X\n", c); */
  270. if ((n > 0) && (c == '\n'))
  271. {
  272. recv(sock, &c, 1, 0);
  273. }
  274. else
  275. {
  276. c = '\n';
  277. }
  278. }
  279. buf[i] = c;
  280. i++;
  281. }
  282. else
  283. {
  284. c = '\n';
  285. }
  286. }
  287. buf[i] = '\0';
  288. return(i);
  289. }
  290. /**********************************************************************/
  291. /* Return the informational HTTP headers about a file. */
  292. /* Parameters: the socket to print the headers on
  293. *             the name of the file */
  294. /**********************************************************************/
  295. void headers(SOCKET client, const char *filename)
  296. {
  297. (void)filename;
  298. char* pHeader = "HTTP/1.0 200 OK\r\n"\
  299. SERVER_STRING \
  300. "Content-Type: text/html\r\n\r\n";
  301. send(client, pHeader, strlen(pHeader), 0);
  302. }
  303. /**********************************************************************/
  304. /* Give a client a 404 not found status message. */
  305. /**********************************************************************/
  306. void not_found(SOCKET client)
  307. {
  308. char* pResponse = "HTTP/1.0 404 NOT FOUND\r\n"\
  309. SERVER_STRING \
  310. "Content-Type: text/html\r\n\r\n"\
  311. "<HTML><TITLE>Not Found</TITLE>\r\n"\
  312. "<BODY><P>The server could not fulfill\r\n"\
  313. "your request because the resource specified\r\n"\
  314. "is unavailable or nonexistent.\r\n"\
  315. "</BODY></HTML>\r\n";
  316. send(client, pResponse, strlen(pResponse), 0);
  317. }
  318. /**********************************************************************/
  319. /* Inform the client that the requested web method has not been
  320. * implemented.
  321. * Parameter: the client socket */
  322. /**********************************************************************/
  323. void unimplemented(SOCKET client)
  324. {
  325. char* pResponse = "HTTP/1.0 501 Method Not Implemented\r\n"\
  326. SERVER_STRING \
  327. "Content-Type: text/html\r\n\r\n"\
  328. "<HTML><HEAD><TITLE>Method Not Implemented\r\n"\
  329. "</TITLE></HEAD>\r\n"\
  330. "<BODY><P>HTTP request method not supported.</P>\r\n"\
  331. "</BODY></HTML>\r\n";
  332. send(client, pResponse, strlen(pResponse), 0);
  333. }
  334. /**********************************************************************/
  335. /* Inform the client that a CGI script could not be executed.
  336. * Parameter: the client socket descriptor. */
  337. /**********************************************************************/
  338. void cannot_execute(SOCKET client)
  339. {
  340. char* pResponse = "HTTP/1.0 500 Internal Server Error\r\n"\
  341. "Content-Type: text/html\r\n\r\n"\
  342. "<P>Error prohibited CGI execution.</P>\r\n";
  343. send(client, pResponse, strlen(pResponse), 0);
  344. }
  345. /**********************************************************************/
  346. /* Inform the client that a request it has made has a problem.
  347. * Parameters: client socket */
  348. /**********************************************************************/
  349. void bad_request(SOCKET client)
  350. {
  351. char* pResponse = "HTTP/1.0 400 BAD REQUEST\r\n"\
  352. "Content-Type: text/html\r\n\r\n"\
  353. "<P>Your browser sent a bad request, such as a POST without a Content-Length.</P>\r\n";
  354. send(client, pResponse, strlen(pResponse), 0);
  355. }
  356. /**********************************************************************/
  357. /* Send a regular file to the client.  Use headers, and report
  358. * errors to client if they occur.
  359. * Parameters: a pointer to a file structure produced from the socket
  360. *              file descriptor
  361. *             the name of the file to serve */
  362. /**********************************************************************/
  363. void serve_file(SOCKET client, const char *filename)
  364. {
  365. FILE *resource = NULL;
  366. discardheaders(client);
  367. fopen_s(&resource, filename, "r");
  368. if (resource == NULL)
  369. {
  370. not_found(client);
  371. }
  372. else
  373. {
  374. headers(client, filename);
  375. cat(client, resource);
  376. }
  377. fclose(resource);
  378. }
  379. // -------------------------------------------------------------------------
  380. // 函数       : discardheaders
  381. // 功能       : 清除http头数据(从网络中全部读出来)
  382. // 返回值  : void
  383. // 参数       : SOCKET client
  384. // 附注       :
  385. // -------------------------------------------------------------------------
  386. void discardheaders(SOCKET client)
  387. {
  388. char buf[1024] = {0};
  389. int numchars = 1;
  390. while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  391. {
  392. numchars = get_line(client, buf, sizeof(buf));
  393. }
  394. }
  395. /**********************************************************************/
  396. /* This function starts the process of listening for web connections
  397. * on a specified port.  If the port is 0, then dynamically allocate a
  398. * port and modify the original port variable to reflect the actual
  399. * port.
  400. * Parameters: pointer to variable containing the port to connect on
  401. * Returns: the socket */
  402. /**********************************************************************/
  403. SOCKET startup(u_short* port)
  404. {
  405. SOCKET httpd = 0;
  406. struct sockaddr_in name = {0};
  407. httpd = socket(AF_INET, SOCK_STREAM, 0);
  408. if (httpd == INVALID_SOCKET)
  409. {
  410. error_die("startup socket");
  411. }
  412. name.sin_family = AF_INET;
  413. name.sin_port = htons(*port);
  414. name.sin_addr.s_addr = inet_addr("127.0.0.1");
  415. if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
  416. {
  417. error_die("startup bind");
  418. }
  419. if (*port == 0)  /* if dynamically allocating a port */
  420. {
  421. int namelen = sizeof(name);
  422. if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
  423. {
  424. error_die("getsockname");
  425. }
  426. *port = ntohs(name.sin_port);
  427. }
  428. if (listen(httpd, 5) < 0)
  429. {
  430. error_die("listen");
  431. }
  432. return httpd;
  433. }
  434. }; // End Class CTinyHttp
  435. int _tmain(int argc, _TCHAR* argv[])
  436. {
  437. SOCKET server_sock = INVALID_SOCKET;
  438. //u_short port = 0;
  439. u_short port = 80;
  440. struct sockaddr_in client_name = {0};
  441. int client_name_len = sizeof(client_name);
  442. typedef CMultiTaskThreadPoolT<CTinyHttp, CTinyHttp::SOCKET_CONTEXT, nilstruct, 5, CComMultiThreadModel::AutoCriticalSection> CMultiTaskThreadPool;
  443. CTinyHttp tinyHttpSvr;
  444. // init socket
  445. WSADATA wsaData = {0};
  446. WSAStartup(MAKEWORD(2, 2), &wsaData);
  447. server_sock = tinyHttpSvr.startup(&port);
  448. printf("httpd running on port: %d\n", port);
  449. CMultiTaskThreadPool m_threadpool(&tinyHttpSvr, &CTinyHttp::accept_request);
  450. while (1)
  451. {
  452. CTinyHttp::SOCKET_CONTEXT socket_context;
  453. socket_context.socket_Client = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);
  454. if (socket_context.socket_Client == INVALID_SOCKET)
  455. {
  456. tinyHttpSvr.error_die("accept");
  457. }
  458. printf("Tid[%u] accetp new connect: %u\n", (unsigned int)::GetCurrentThreadId(), (unsigned int)socket_context.socket_Client);
  459. m_threadpool.AddTask(socket_context);
  460. }
  461. // can not to run this
  462. m_threadpool.EndTasks();
  463. closesocket(server_sock);
  464. WSACleanup();
  465. return 0;
  466. }
  467. // -------------------------------------------------------------------------
  468. // $Log: $

参考文档

[1] tinyhttp源码阅读(注释) http://www.cnblogs.com/oloroso/p/5459196.html

[2] 【源码剖析】tinyhttpd —— C 语言实现最简单的 HTTP 服务器http://blog.csdn.NET/jcjc918/article/details/42129311

[3] Tinyhttp源码分析http://blog.csdn.net/yzhang6_10/article/details/51534409

[4] tinyhttpd源码详解http://blog.csdn.Net/baiwfg2/article/details/45582723

[5] CGI介绍 http://www.jdon.com/idea/cgi.htm

http://blog.csdn.net/magictong/article/details/53201038

Tinyhttpd for Windows(学习型的项目,才500多行代码)的更多相关文章

  1. 安卓开发学习经历2--《第一行代码》coolweather项目SQL语句同一个“陷阱”掉两次 注意转义字符等特殊字符正确书写 关于Id字段自增加体会

    今天,在运行<第一行代码>coolweather第二阶段代码,又一次报错,还是神奇地与昨天相似,提示,city_id字段不存在,这里我有两种理解,一种是sql语句出错了,另外一种是没有获取 ...

  2. Tinyhttpd for Windows

    TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:h ...

  3. Tinyhttpd for Windows(500多行)

    TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.NET/,源代码下载:h ...

  4. git入门学习(一):github for windows上传本地项目到github

    Git是目前最先进的分布式版本控制系统,作为一个程序员,我们需要掌握其用法.Github发布了Github for Windows 则大大降低了学习成本和使用难度,他甚至比SVN都简单. 一.首先在g ...

  5. Windows服务、批处理项目实战

    一周一话题之三(Windows服务.批处理项目实战)   -->目录导航 一. Windows服务 1. windows service介绍 2. 使用步骤 3. 项目实例--数据上传下载服务 ...

  6. vue学习(1) vue-cli 项目搭建

    vue学习(1)  vue-cli 项目搭建 一.windows环境 1. 下载node.js安装包 官网:https://nodejs.org/en/download/ 选择LTS下载 2. 安装 ...

  7. 学习Coding-iOS开源项目日志(五)

    继续,接着前面第四篇<学习Coding-iOS开源项目日志(四)>讲解Coding-iOS开源项目. 前 言:作为初级程序员,想要提高自己的水平,其中一个有效的学习方法就是学习别人好的项目 ...

  8. 安卓开发学习历程1——《第一行代码》coolweather项目setOnItemClickListener函数,Sql语句修改对模拟app程序机影响

    今天,将<第一行代码>最后实战的coolweather项目,认真做了一遍. 今晚,在书中第一阶段开发代码认眞在Android studio敲完,发现setOnItemClickListen ...

  9. Android Testing学习02 HelloTesting 项目建立与执行

    Android Testing学习02 HelloTesting 项目建立与执行 Android测试,分为待测试的项目和测试项目,这两个项目会生成两个独立的apk,但是内部,它们会共享同一个进程. 下 ...

随机推荐

  1. MySQL的表空间管理

    表空间: MySQL没有真正意义上的表空间管理. MySQL的Innodb包含两种表空间文件模式,默认的共享表空间和每个表分离的独立表空间. 一般来说,当数据量很小的时候建议使用共享表空间的管理方式. ...

  2. 使用DOT语言和Graphviz绘图(翻译)

    Casa Taloyum About Me Blog Archives 使用DOT语言和Graphviz绘图(翻译) Date Wed 26 November 2014 Tags graphviz / ...

  3. ajax跳转到新的jsp页面(局部刷新)

    ajax可以实现局部刷新页面,即在不刷新整个页面的情况下更新页面的局部信息. 项目中遇到一个问题:在用户列表也,当点击某个按钮时需要去查询用户的信息,查询成功跳转到用户详情界面:查询失败,则在原页面弹 ...

  4. Go语言版黑白棋

    1.游戏说明 2.无边框窗口实现 3.背景图.最小化.关闭窗口 4.界面其它设计 5.黑白子提示闪烁效果 6.落子 7.初始化棋子.改变角色 8.倒计时 9.吃子 10.棋子个数统计.胜负判断 11. ...

  5. 【转】dbx用法讲解

    http://blog.chinaunix.net/uid-25544300-id-328735.html dbx 命令 用途 提供了一个调试和运行程序的环境. 语法 dbx [ -a Process ...

  6. Spring学习笔记之六(数据源的配置)

    1.前言 上一篇博客分析了,Spring中实现AOP的两种动态代理的机制,以下这篇博客.来解说一下Spring中的数据源的配置.  2.DAO支持的模板类 Spring提供了非常多关于Dao支持的模板 ...

  7. 【matlab】GPU 显卡版本与计算能力(compute capability)兼容性问题

    MathWorks - Bug Reports 1. 问题说明 当运行 alexnet 等卷积神经网络需要使用 GPU 加速时,matlab 如果提示如下的警告信息: GPUs of compute ...

  8. 【BZOJ 1016】 [JSOI2008]最小生成树计数(matrix-tree定理做法)

    [题目链接]:http://www.lydsy.com/JudgeOnline/problem.php?id=1016 [题意] [题解] /* 接上一篇文章; 这里用matrix-tree定理搞最小 ...

  9. JAVA SE回顾及思考(1)——面向对象的特点

    学习Java已经三年了,现在开始做Android开发,虽说还在用Java语言但本人现在才真真的意识到无论学什么基础才是最重要的,可能一些刚接触Java或者Android的朋友现在还体会不到基础的重要性 ...

  10. In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability.

    In partitioned databases, trading some consistency for availability can lead to dramatic improvement ...