浏览器进行http请求的时候,不单单会请求静态资源,还可能需要请求动态页面。

那么什么是静态资源,什么是动态页面呢?

静态资源 : 例如html文件、图片文件、css、js文件等,都可以算是静态资源

动态页面:当请求例如登陆页面、查询页面、注册页面等可能会变化的页面,则是动态页面。

浏览器请求动态页面过程

通过下图来了解一下页面HTTP请求的过程,如下:

可以看到web服务器是用wsgi协议调用应用程序框架的,这里我们先不讲什么是wsgi协议,先看看我之前写的静态web服务端。

多进程web服务端代码 - 面向过程

  1. #coding=utf-8
  2. from socket import *
  3. import re
  4. import multiprocessing
  5.  
  6. def handle_client(client_socket):
  7. """为一个客户端服务"""
  8. # 接收对方发送的数据
  9. recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数
  10. # 打印从客户端发送过来的数据内容
  11. #print("client_recv:",recv_data)
  12. request_header_lines = recv_data.splitlines()
  13. for line in request_header_lines:
  14. print(line)
  15.  
  16. # 返回浏览器数据
  17. # 设置内容body
  18. # 使用正则匹配出文件路径
  19. print("------>",request_header_lines[0])
  20. print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))
  21. ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])
  22. if ret:
  23. file_path = "./html/" + ret.group(1)
  24. if file_path == "./html/":
  25. file_path = "./html/index.html"
  26. print("file_path *******",file_path)
  27.  
  28. try:
  29. # 设置返回的头信息 header
  30. response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源
  31. response_headers += "\r\n" # 空一行与body隔开
  32. # 读取html文件内容
  33. file_name = file_path # 设置读取的文件路径
  34. f = open(file_name,"rb") # 以二进制读取文件内容
  35. response_body = f.read()
  36. f.close()
  37. # 返回数据给浏览器
  38. client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器
  39. client_socket.send(response_body) #转码utf-8并send数据到浏览器
  40. except:
  41. # 如果没有找到文件,那么就打印404 not found
  42. # 设置返回的头信息 header
  43. response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源
  44. response_headers += "\r\n" # 空一行与body隔开
  45. response_body = "<h1>sorry,file not found</h1>"
  46. response = response_headers + response_body
  47. client_socket.send(response.encode("utf-8"))
  48.  
  49. #client_socket.close()
  50.  
  51. def main():
  52. # 创建套接字
  53. server_socket = socket(AF_INET, SOCK_STREAM)
  54. # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
  55. server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  56. # 设置服务端提供服务的端口号
  57. server_socket.bind(('', 7788))
  58. # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
  59. server_socket.listen(128) #最多可以监听128个连接
  60. # 开启while循环处理访问过来的请求
  61. while True:
  62. # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
  63. # client_socket用来为这个客户端服务
  64. # server_socket就可以省下来专门等待其他新的客户端连接while True:
  65. client_socket, clientAddr = server_socket.accept()
  66. # handle_client(client_socket)
  67. # 设置子进程
  68. new_process = multiprocessing.Process(target=handle_client,args=(client_socket,))
  69. new_process.start() # 开启子进程
  70.  
  71. # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的
  72. client_socket.close()
  73.  
  74. if __name__ == "__main__":
  75. main()

先来回顾一下运行的情况:

好了,看到运行也是正常的,那么下面就要来分析一下,如何将代码封装为对象。

封装对象分析

首先我需要定义一个webServer类,然后将访问静态资源的功能都封装进去。

  1. #coding=utf-8
  2. from socket import *
  3. import re
  4. import multiprocessing
  5.  
  6. class WebServer:
  7.  
  8. def __init__(self):
  9. # 创建套接字
  10. self.server_socket = socket(AF_INET, SOCK_STREAM)
  11. # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
  12. self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  13. # 设置服务端提供服务的端口号
  14. self.server_socket.bind(('', 7788))
  15. # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
  16. self.server_socket.listen(128) #最多可以监听128个连接
  17.  
  18. def start_http_service(self):
  19. # 开启while循环处理访问过来的请求
  20. while True:
  21. # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
  22. # client_socket用来为这个客户端服务
  23. # self.server_socket就可以省下来专门等待其他新的客户端连接while True:
  24. client_socket, clientAddr = self.server_socket.accept()
  25. # handle_client(client_socket)
  26. # 设置子进程
  27. new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,))
  28. new_process.start() # 开启子进程
  29. # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的
  30. client_socket.close()
  31.  
  32. def handle_client(self,client_socket):
  33. """为一个客户端服务"""
  34. # 接收对方发送的数据
  35. recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数
  36. # 打印从客户端发送过来的数据内容
  37. #print("client_recv:",recv_data)
  38. request_header_lines = recv_data.splitlines()
  39. for line in request_header_lines:
  40. print(line)
  41.  
  42. # 返回浏览器数据
  43. # 设置内容body
  44. # 使用正则匹配出文件路径
  45. print("------>",request_header_lines[0])
  46. print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))
  47. ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])
  48. if ret:
  49. file_path = "./html/" + ret.group(1)
  50. if file_path == "./html/":
  51. file_path = "./html/index.html"
  52. print("file_path *******",file_path)
  53.  
  54. try:
  55. # 设置返回的头信息 header
  56. response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源
  57. response_headers += "\r\n" # 空一行与body隔开
  58. # 读取html文件内容
  59. file_name = file_path # 设置读取的文件路径
  60. f = open(file_name,"rb") # 以二进制读取文件内容
  61. response_body = f.read()
  62. f.close()
  63. # 返回数据给浏览器
  64. client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器
  65. client_socket.send(response_body) #转码utf-8并send数据到浏览器
  66. except:
  67. # 如果没有找到文件,那么就打印404 not found
  68. # 设置返回的头信息 header
  69. response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源
  70. response_headers += "\r\n" # 空一行与body隔开
  71. response_body = "<h1>sorry,file not found</h1>"
  72. response = response_headers + response_body
  73. client_socket.send(response.encode("utf-8"))
  74.  
  75. def main():
  76. webserver = WebServer()
  77. webserver.start_http_service()
  78.  
  79. if __name__ == "__main__":
  80. main()

好了,从上面的代码来看,我已经将前面面向过程的代码修改为面向对象了。

运行一下看看有没有错误:

  1. 思考:已经封装为对象了,下一步还要优化什么呢?

请求静态资源的页面已经可以了,那么如果请求动态的页面呢?

如果web服务端是java写的话,通常http请求就是http:xxxx/xxx.jsp

如果web服务端是php写的话,通常http请求就是http:xxxx/xxx.php

那么,既然这次我使用python来写,就可以定义动态资源的请求为http:xxxx/xxx.py

那么如果来识别并执行 http:xxxx/xxx.py 的请求呢?

增加识别动态资源请求的功能

需求:识别并返回http:xxxx/xxx.py 的请求

那么让我想一下,先做个简单的,例如:我请求一个http的请求 http:xxxx/time.py 则返回一个当前服务端的时间给浏览器。

那么如果http请求了一个py结尾的请求,我需要在哪里处理呢?

还有我可以用什么方法来判断 .py 后缀的文件呢?
用正则匹配?
其实可以使用endswith("文件后缀")的方法来判断处理。

  1. In [1]: file_name = "time.py"
  2.  
  3. # 匹配后缀为 .html ,直接报False
  4. In [3]: file_name.endswith(".html")
  5. Out[3]: False
  6.  
  7. # 匹配后缀为 .py ,则报True
  8. In [4]: file_name.endswith(".py")
  9. Out[4]: True

那么下面就可以来写写这里判断的处理分支了。

测试执行一下:

首先请求HTML等静态资源页面

请求动态资源页面

先简单地写一串HTML+当前服务器时间的内容吧。

  1. if file_path.endswith(".py"):
  2. # 请求动态资源
  3. print("这个是请求动态资源的!")
  4. # 设置返回的头信息 head
  5. response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源
  6. response_headers += "\r\n" # 空一行与body隔开
  7. # 设置返回浏览器的body内容
  8. response_body = "<h1>hello this is xxx.py</h1><br>"
  9. response_body += time.ctime()
  10. response = response_headers + response_body
  11. # 返回数据给浏览器
  12. client_socket.send(response.encode("utf-8"))

从这里已经可以正常返回动态页面的内容的了。

  1. 思考 :如果将动态处理页面的代码在web服务端不断地写,代码就会很庞大。是否可以拆分出来,服务端只接受浏览器的消息,判断静态还是动态以及其他业务功能放到另一个模块进行编写呢?

这里就涉及到 web服务端 与 业务处理服务端 之间的一个协议了,这个业界内通用的协议就是 WSGI协议。

为什么需要 WSGI协议

在讲WSGI协议之前,我先把处理动态页面的功能拆分到另一个模块文件中。

  1. import time
  2.  
  3. def application(client_socket):
  4. # 请求动态资源
  5. print("这个是请求动态资源的!")
  6. # 设置返回的头信息 head
  7. response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源
  8. response_headers += "\r\n" # 空一行与body隔开
  9. # 设置返回浏览器的body内容
  10. response_body = "<h1>hello this is xxx.py</h1><br>"
  11. response_body += time.ctime()
  12. response = response_headers + response_body
  13. # 返回数据给浏览器
  14. client_socket.send(response.encode("utf-8"))

那么在原来的webserver.py模块只要import该模块文件,使用application()方法就可以处理刚才的业务了。

好了,做了这个解耦的操作之后,下面来运行测试一下:

从上面的调用结果来看,的确是调用成功啦,理解大概如下图:

可以看出来,webserver想要调用 framework处理业务的话,就要这样去写,如下:

  1. framework.application(client_socket)

这种方式虽然可行,但是在业界中是不通用的。也就是说这种调用方法扔给别人写的框架,就无法兼容了。

例如:假设我后面改用Django、Flask框架来处理业务,此时一定就不是用这种方式来通讯调用的。

那么该用什么方式呢?

是否可以修改服务器和架构代码而确保可以在多个架构下,保证与web服务器之间的通讯调用呢?

答案就是  Web Server Gateway Interface (或简称 WSGI,读作“wizgy”)。

WSGI协议的介绍

  1. WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器和web框架,选择一个适合的配对。
    比如,可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, Pyramid。真正的混合匹配,得益于WSGI同时支持服务器和架构。

web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。

WSGI由web服务器支持,而web框架允许你选择适合自己的配对,但它同样对于服务器和框架开发者提供便利使他们可以专注于自己偏爱的领域和专长而不至于相互牵制。其他语言也有类似接口:java有Servlet API,Ruby 有 Rack。

定义WSGI接口

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。

我们来看一个最简单的Web版本的“Hello World!”
  1. def application(environ, start_response):
  2. start_response('200 OK', [('Content-Type', 'text/html')])
  3. return 'Hello World!'

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

  • environ:一个包含所有HTTP请求信息的dict对象;
  • start_response:一个发送HTTP响应的函数。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,把底层web服务器解析部分和应用程序逻辑部分进行了分离,这样开发者就可以专心做一个领域了。

不过,这个application()函数怎么调用?如果我们自己调用,两个参数environ和start_response我们没法提供,返回的str也没法发给浏览器。
所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器。而我们此时的web服务器项目的目的就是做一个既能解析静态网页还可以解析动态网页的WSGI服务器。

编写framwork支持WSGI协议,实现浏览器显示 hello world

直接协议规范代码复制进去。

那么在webserver.py的部分,就需要接受application返回的信息。

首先,start_response 就是在framwork设置http请求header信息的。而return 就是返回http请求body信息的。

那么知道了这两点之后,下一步要做的。就是想办法来接受这个application的设置header以及body信息。

那么怎么处理呢?

为了方便对比查看这两个文件的代码,使用pycharm同时打开两个视图窗口来查看。

好了,下面来继续看看。

下面来创建这两个形参:

  • environ:一个包含所有HTTP请求信息的dict对象;
  • start_response:一个发送HTTP响应的函数。

先随便写个空的,来填入WSGI规范所需要的参数。

其中response_body通过return的返回值就可以接受到了。

那么response_header该怎么处理呢?

可以从代码中看出start_response 在webserver.py 传入到 framwork.py 的application中调用。

其中在application中就直接设置header信息到start_response的参数中。然后我在webserver.py能否直接将其取出来,拼接成header信息呢?

编写start_response 接收 header 信息

那么首先编写一个类变量来保存信息,然后测试打印一下看看。

运行测试一下看看:

那么只要将其保存到self.application_header中,我就可以在类方法的任意一个地方进行拆分或者拼接成所需要的http header返回值了。

编写如下:

运行测试看看。

这样就得到了完成的header内容啦,那么下面将其拼接body内容,然后返回浏览器中显示。

运行测试如下:

本次开发的完整代码如下:

webserver.py

  1. #coding=utf-8
  2. from socket import *
  3. import re
  4. import multiprocessing
  5. import time
  6. import framework
  7.  
  8. class WebServer:
  9.  
  10. def __init__(self):
  11. # 创建套接字
  12. self.server_socket = socket(AF_INET, SOCK_STREAM)
  13. # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
  14. self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  15. # 设置服务端提供服务的端口号
  16. self.server_socket.bind(('', 7788))
  17. # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
  18. self.server_socket.listen(128) #最多可以监听128个连接
  19.  
  20. def start_http_service(self):
  21. # 开启while循环处理访问过来的请求
  22. while True:
  23. # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
  24. # client_socket用来为这个客户端服务
  25. # self.server_socket就可以省下来专门等待其他新的客户端连接while True:
  26. client_socket, clientAddr = self.server_socket.accept()
  27. # handle_client(client_socket)
  28. # 设置子进程
  29. new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,))
  30. new_process.start() # 开启子进程
  31. # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的
  32. client_socket.close()
  33.  
  34. def handle_client(self,client_socket):
  35. """为一个客户端服务"""
  36. # 接收对方发送的数据
  37. recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数
  38. # 打印从客户端发送过来的数据内容
  39. #print("client_recv:",recv_data)
  40. request_header_lines = recv_data.splitlines()
  41. for line in request_header_lines:
  42. print(line)
  43.  
  44. # 返回浏览器数据
  45. # 设置内容body
  46. # 使用正则匹配出文件路径
  47. print("------>",request_header_lines[0])
  48. print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))
  49. ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])
  50. if ret:
  51. file_path = "./html/" + ret.group(1)
  52. if file_path == "./html/":
  53. file_path = "./html/index.html"
  54. print("file_path *******",file_path)
  55.  
  56. # 判断file_path是否py文件后缀,如果是则请求动态资源,否则请求静态资源
  57. if file_path.endswith(".py"):
  58.  
  59. # framework.application(client_socket)
  60. # 支撑WGSI协议的调用方式
  61. environ = {}
  62. response_body = framework.application(environ, self.start_response)
  63. # 设置返回的头信息header
  64. # 1.拼接第一行HTTP/1.1 200 OK + 换行符内容
  65. response_headers = "HTTP/1.1 " + self.application_header[0] + "\r\n"
  66. # 2.循环拼接第二行或者多行元组内容:Content-Type:text/html
  67. for var in self.application_header[1]:
  68. response_headers += var[0]+":"+var[1] + "\r\n"
  69. # 3.空一行与body隔开
  70. response_headers += "\r\n"
  71. # 4.打印看看header的内容信息
  72. print("response_header=")
  73. print(response_headers)
  74.  
  75. # 设置返回的浏览器的内容
  76. response = response_headers + response_body
  77. client_socket.send(response.encode("utf-8"))
  78.  
  79. else:
  80. # 请求静态资源
  81. try:
  82. # 设置返回的头信息 header
  83. response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源
  84. response_headers += "\r\n" # 空一行与body隔开
  85. # 读取html文件内容
  86. file_name = file_path # 设置读取的文件路径
  87. f = open(file_name,"rb") # 以二进制读取文件内容
  88. response_body = f.read()
  89. f.close()
  90. # 返回数据给浏览器
  91. client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器
  92. client_socket.send(response_body) #转码utf-8并send数据到浏览器
  93. except:
  94. # 如果没有找到文件,那么就打印404 not found
  95. # 设置返回的头信息 header
  96. response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源
  97. response_headers += "\r\n" # 空一行与body隔开
  98. response_body = "<h1>sorry,file not found</h1>"
  99. response = response_headers + response_body
  100. client_socket.send(response.encode("utf-8"))
  101.  
  102. def start_response(self,status,header):
  103. self.application_header = [status,header]
  104. print("application_header=",self.application_header)
  105.  
  106. def main():
  107. webserver = WebServer()
  108. webserver.start_http_service()
  109.  
  110. if __name__ == "__main__":
  111. main()

framework.py

  1. # 支撑WGSI协议
  2. def application(environ, start_response):
  3. start_response('200 OK', [('Content-Type', 'text/html')])
  4. return 'Hello World!'


来源:简书
作者:DevOps海洋的渔夫
链接:https://www.jianshu.com/p/417cd1989781

Python web框架开发 - WSGI协议的更多相关文章

  1. python web框架 django wsgi 理论

    django wsgi python有个自带的wsgi模块 可以写自定义web框架 用wsgi在内部创建socket对象就可以了 自己只写处理函数就可以了django只是web框架 他也不负责写soc ...

  2. [Python web开发] Web框架开发基础 (一)

    Python WEB框架 WSGI,WEB Server Gateway Interface,可以看做是一种底层协议,它规定了服务器程序和应用程序各自实现上面接口.Python的实现称为wsgiref ...

  3. Python Web框架本质——Python Web开发系列一

    前言:了解一件事情本质的那一瞬间总能让我获得巨大的愉悦感,希望这篇文章也能帮助到您. 目的:本文主要简单介绍Web开发中三大基本功能:Socket实现.路由系统.模板引擎渲染. 进入正题. 一. 基础 ...

  4. python三大web框架Django,Flask,Flask,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架

    Python几种主流框架 从GitHub中整理出的15个最受欢迎的Python开源框架.这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等. Django: Python We ...

  5. 微型 Python Web 框架: Bottle

    微型 Python Web 框架: Bottle 在 19/09/11 07:04 PM 由 COSTONY 发表 Bottle 是一个非常小巧但高效的微型 Python Web 框架,它被设计为仅仅 ...

  6. 一文读懂Python web框架和web服务器之间的关系

    我们都知道 Python 作为一门强大的语言,能够适应快速原型和较大项目的制作,因此被广泛用于 web 应用程序的开发中. 在面试的过程中,大家或多或少都被问到过这样一个问题:一个请求从浏览器发出到数 ...

  7. Django,Flask,Tornado三大框架对比,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架

    Django 与 Tornado 各自的优缺点Django优点: 大和全(重量级框架)自带orm,template,view 需要的功能也可以去找第三方的app注重高效开发全自动化的管理后台(只需要使 ...

  8. 2018年要学习的10大Python Web框架

    通过为开发人员提供应用程序开发结构,框架使开发人员的生活更轻松.他们自动执行通用解决方案,缩短开发时间,并允许开发人员更多地关注应用程序逻辑而不是常规元素. 在本文中,我们分享了我们自己的前十大Pyt ...

  9. Python Web 应用:WSGI基础

    在Django,Flask,Bottle和其他一切Python web 框架底层的是Web Server Gateway Interface,简称WSGI.WSGI对Python来说就像 Servle ...

随机推荐

  1. BS版本的TCP程序

    // 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象 InputStream is = socket.getInputStream();// ...

  2. 使用hbuilder打包vue项目容易出现的坑点

    1.打包后手机打开"该app专为旧版本安卓"问题解决(在hbuilder中设置) 打开manifest.json 然后 2.打包后app打开显示白屏. 路径问题:在webpack中 ...

  3. [BUUCTF]REVERSE——reverse2

    reverse2 附件 例行检查,64位目标 64位ida载入,首先shift+f12检索程序里的字符串 得到了"this is the right flag!" 的提示字符串,还 ...

  4. Tornadofx学习笔记(4)——IconTextFx开源库,整合5000+个字体图标

    JavaFx中其实也可以直接使用字体图标iconfont的,只需要加载ttf字体文件,之后设置unicode即可,具体可以看我给出的代码 既然JavaFx可以,那么以JavaFx为基础的Tornado ...

  5. TensorFlow.NET机器学习入门【0】前言与目录

    曾经学习过一段时间ML.NET的知识,ML.NET是微软提供的一套机器学习框架,相对于其他的一些机器学习框架,ML.NET侧重于消费现有的网络模型,不太好自定义自己的网络模型,底层实现也做了高度封装. ...

  6. apscheduler 设置python脚本定时任务

    理论概念:https://zhuanlan.zhihu.com/p/95563033 BlockingScheduler与BackgroundScheduler区别 :https://www.jian ...

  7. 【LeetCode】912. Sort an Array 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 库函数排序 桶排序 红黑树排序 归并排序 快速排序 ...

  8. 【LeetCode】617. Merge Two Binary Trees 解题报告

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcod ...

  9. codeforce 595B-Pasha and Phone(数学)

    今天补题,昨天是我太猖狂了,在机房吹牛,然后说着说着忘了时间,后来楼长来了,我们走了,CF没打成. 不扯了,下面说题: 题目的意思是给你n和k, n代表最后得出的号码有n为,然后k能被n整除,就是把n ...

  10. 【自编教材】16万8千字的HTML+CSS基础 适合从0到1-可收藏

    [图片链接有点小问题,这几天更新,敬请期待!] 目 录 第一章HTML基础 1.1 HTML简介和发展史 1.1.1 什么是HTML 1.1.2 HTML的发展历程 1.1.3 web标准 1.2 开 ...