TinyHTTPd forWindows

前言

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. Tinyhttpd for Windows(学习型的项目,才500多行代码)

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

  2. Tinyhttpd for Windows

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

  3. 由于Windows和Linux行尾标识引起脚本无法运行的解决

    在所有的操作系统中,文本文件的结束或者换行都是有行尾符来标识的,C语言中经常使用\n作为换行,\r作为跳格TAB:实际上在计算机还没有真正出现之前,有种电传打字机的设备,每秒钟可以打印10个字符,但是 ...

  4. Windows的命令行怎么支持通配符

    摸索出一个小技巧,虽然Windows的命令行本身不支持通配符,但可以在脚本里把传进来的参数当通配符用 只要加上@ARGV = glob "@ARGV";就行了 @ARGV = gl ...

  5. windows下命令行模式中cd命令无效的原因

    当我们执行cmd 想切换当前工作目录时,会发现windows下命令行模式中cd命令没有生效,到底是什么原因呢? 例如: 当我们想切换到 D:\MySql\mysql-5.7.19-winx64\bin ...

  6. 【转】Python实现修改Windows CMD命令行输出颜色(完全解析)

    用Python写命令行程序的时候,单一的输出颜色太单调.其实我们可以加些色彩,比如用红色表示警告,绿色表示结果正常等.网上也有几篇类似的帖子,但是没有把问题讲清楚,贴的代码也不是太清晰.这里,对Win ...

  7. windows更改命令行cmd的字体为conlosas+微软雅黑

    windows更改命令行cmd的字体为conlosas+微软雅黑 动力来源于对美孜孜不倦的追求~ 下载conlosas+微软雅黑字体 谢谢支持. 将解压后的YaHei.Consolas.1.12.tt ...

  8. Python实现Windows CMD命令行彩色输出

    #! /usr/bin/env python #coding=utf-8   import ctypes,sys   STD_INPUT_HANDLE = -10 STD_OUTPUT_HANDLE ...

  9. Windows cmd 命令行基本操作

    Windows cmd 命令行基本操作 1. 进入到指定根目录 注意:不区分大小写 例如进入到 D 盘 2. 进入到指定的目录 例如 (如果目录文件名太长,可以使用 tab 键来自动补全.重复按可以进 ...

随机推荐

  1. XSS攻击之基础篇:HTML标签与字符串的渲染

    <body> XSS攻击之基础篇:HTML标签与字符串的渲染 测试 <div class="a1"> </div> <div class= ...

  2. 【29.41%】【codeforces 724D】Dense Subsequence

    time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  3. apply plugin: 'idea' --- gradle idea

    如果你的项目使用了Gradle作为构建工具,那么你一定要使用Gradle来自动生成IDE的项目文件,无需再手动的将源代码导入到你的IDE中去了. 如果你使用的是eclipse,可以在build.gra ...

  4. Springmvc案例1----基于spring2.5的採用xml配置

    首先是项目和所须要的包截图: 改动xml文件: <?xml version="1.0" encoding="UTF-8"?> <web-app ...

  5. win10 uwp 使用 msbuild 命令行编译 UWP 程序

    原文:win10 uwp 使用 msbuild 命令行编译 UWP 程序 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http:// ...

  6. discuz数据库函数使用

  7. Cocos2d-X中国象棋的发展《五岁以下儿童》摆棋

    在博客上,以实现创建的游戏场景.而一些button,因为button落实到事件作出详细答复,需要使用一些功能摆棋.为此我特意button上的背面的具体实施, 在摆棋前先理清一下摆棋的思路: 1.创建一 ...

  8. 1.跟着微软 https://docs.microsoft.com/zh-cn/dotnet/core/ 学习.net core

    10分钟快速使用 安装之后 打开cmd 第一步. dotnet new console -o firstApp 第二步. cd firstApp 第三部.dotnet run 这样就运行了hello ...

  9. 1.开始第一个MVC项目

    安装就不说了 1.在指定路径创建好项目文件夹之后,打开cmd,进去这个文件夹路径下 输入命令 dotnet new mvc 就会在文件夹内创建一个mvc项目 2.创建好项目之后 直接在这个路径下输入 ...

  10. Python 产生两个方法将不被所述多个随机数的特定范围内反复

    在最近的实验中进行.通过随机切割一定比例所需要的数据这两个部分.事实上这个问题的核心是生成随机数的问题将不再重复.递归方法,首先想到的,然后我们发现Python中竟然已经提供了此方法的函数,能够直接使 ...