Django(一):从socket到MVC
一、socket的http套路
web应用本质上是一个socket服务端,用户的浏览器是一个socket客户端。socket处在应用层与传输层之间,是操作系统中I/O系统的延伸部分(接口),负责系统进程和应用之间的通信。
HTTP协议又称超文本传输协议。
//浏览器发送一个HTTP请求;
//服务器收到请求,根据请求信息,进行函数处理,生成一个HTML文档;
//服务器把HTML文档作为HTTP响应的Body发送给浏览器;
//浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示;
1、客户端套路解析
import socket, ssl
# socket 是操作系统用来进行网络通信的底层方案,用来发送/接收数据 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket.AF_INET表示是ipv4协议,socket.SOCK_STREAM表示是tcp协议,这两个值是默认的。
# s = socket.socket()
# 上面只能连接http,如果连接https,需要用s = ssl.wrap_socket(socket.socket()) host, port = ("g.cn", 80)
s.connect((host, port)) # 连接主机,参数是主机的和端口 ip, port = s.getsockname()
print('本机 ip 和 port {} {}'.format(ip, port)) # 查看本机的ip和端口 # 构造一个http请求
http_request = 'GET / HTTP/1.1\r\nhost:{}\r\n\r\n'.format(host) # 发送HTTP请求给服务器
# send 函数只接收bytes作为参数,所以要重编码为utf-8。实际上,web数据传送都是utf-8编码的字节流数据。 request = http_request.encode(encoding='utf_8', errors='strict')
print("发送请求", request)
s.send(request) # 接收服务器的相应数据
response = s.recv(1024) # buffer_size=1024,只接收1024个字节,多余的数据就不接收了。粘包。 # 输出响应的数据,bytes类型
# print("响应", response)
# 再讲response的utf-8编码的字节流数据进行解码(实际上是转换成unicode编码的字符串,因为读进了内存)。
print("响应的 str 格式: ", end="\r\n")
print(response.decode(encoding='utf-8'))
2、服务器套路解析
import socket host, port = '', 2000
# 服务器的host为空字符串,表示接受任意ip地址的连接 s = socket.socket()
s.bind((host, port)) # 监听 # 用一个无线循环来接受数据
while True:
print("before listen")
s.listen(5) # 接收请求
connection, address = s.accept()
# connection是一个socket.socket对象
print("after listen") # 接收请求
request = connection.recv(1024) # 只接收1024个字节
print("ip and request, {}\n{}".format(address, request.decode("utf-8"))) # 构造响应
response = b"HTTP/1.1 200 OK\r\n\r\n<h1>Hello World!</h1>" # 用sendall发送响应数据
connection.sendall(response) # 关闭连接
connection.close()
在浏览器中输入localhost:2000,看到Hello World。后台打印的结果如下:
before listen
after listen
ip and request, ('127.0.0.1', 54275)
GET / HTTP/1.1
Host: localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Cookie: username-localhost-8888="2|1:0|10:1524886551|23:username-localhost-8888|44:ZTc2ZjE3MjIxMTMwNDIxYzg3OTZmMDlkMDdhNzhjMjI=|b432650e1e450be30083dd567068aebd47dc8d5b7167b068610af60db4c5a35d"; _xsrf=2|fc2cd1bd|88c10f771944fea069e65d5e767b6621|1524882104
Connection: keep-alive
Upgrade-Insecure-Requests: 1
粘包处理
粘包处理: buffer_size = 1023
r = b''
while True:
request = connection.recv(buffer_size)
r += request
if len(request) < buffer_size:
break
3、HTTP请求内容解析
"""
HTTP头
http://localhost:2000/,浏览器默认会隐藏http://和末尾的/ GET / HTTP/1.1
# GET表示请求方式, / 表示请求的资源路径, HTTP/1.1 协议版本 Host: localhost:2000
# 主机地址和端口 Connection: keep-alive
# keep-alive保持连接状态,它表示http连接(不用TCP请求断开再请求);close每次离开就关闭连接 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 Firefox/59.0
# 用户代理,浏览器标识。可以伪造浏览器。 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
# 浏览器接收的数据类型。左边是数据类型,右边是解析时的权重(优先级)。 Accept-Encoding: gzip, deflate
# 可解压的压缩文件类型;只表示能解压该类型的压缩文件,不代表拒绝接收其它类型的压缩文件。 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
# 支持的语言类型及其解析权重。 Cookie: username-localhost-8888="2|1:0|10:1524886551|23:username-localhost-8888|44:ZTc2ZjE3MjIxMTMwNDIxYzg3OTZmMDlkMDdhNzhjMjI=|b432650e1e450be30083dd567068aebd47dc8d5b7167b068610af60db4c5a35d"; _xsrf=2|fc2cd1bd|88c10f771944fea069e65d5e767b6621|1524882104
# 浏览器缓存。 /favicon.ico
# url地址图标,非必须。 # Header里可以添加任意的内容。
"""
二、socket的udp和tcp套路
1、udp的客户端和服务端写法
客户端
import socket
# upd链接
# SOCK_DGRAM:数据报套接字,主要用于UDP协议
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 关闭防火墙
# 同一网段(局域网)下,主机的ip地址和端口号.
sendAddr = ('192.168.10.247', 8080) # 绑定端口:写的是自己的ip和固定的端口,一般是写在sever端.
udpSocket.bind(('', 9900)) # sendData = bytes(input('请输入要发送的数据:'), 'gbk')
# gbk, utf8, str
sendData = input('请输入要发送的数据:').encode('gbk') # 使用udp发送数据,每一次发送都需要写上接收方的ip地址和端口号
udpSocket.sendto(sendData, sendAddr)
# udpSocket.sendto(b'hahahaha', ('192.168.10.247', 8080)) udpSocket.close()
服务端
import socket udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 接收方一般需要绑定端口
# ''表示自己电脑的任何一个ip,即无线和有限同时连接或者电脑有不同的网卡(桥接),会有多个ip.
# 绑定自己的端口
bindAddr = ('', 7788)
udpSocket.bind(bindAddr) recvData = udpSocket.recvfrom(1024)
# print(recvData)
print(recvData[0].decode('gbk')) udpSocket.close()
# recvData的格式:(data, ('ip', 端口)).它是一个元组,前面是数据,后面是一个包含ip和端口的元组.
2、tcp的客户端和服务端写法
客户端
import socket tcpClient = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverAddr = ('192.168.10.247', 8899) # tcp的三次握手,写进了这一句话
tcpClient.connect(serverAddr) sendData = input('') # 直接用send就行了,udp是用sendto
tcpClient.send(sendData.encode('gbk')) recvData = tcpClient.recv(1024) print('接收到的数据为:%s' % recvData.decode('gbk')) tcpClient.close() # 为什么用send而不是sendto?因为tcp连接是事先链接好了,后面就直接发就行了。前面的connect已经连接好了,后面直接用send发送即可。
# 而udp必须用sendto,是发一次数据,连接一次。必须要指定对方的ip和port。
# 相同的道理,在tcpServer端,要写recv,而不是recvfrom来接收数据
服务端
import socket
tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcpServer.bind(('', 8899))
tcpServer.listen(5) # tcp的三次握手,写进了这一句话当中
tcpClient, tcpClientInfo = tcpServer.accept()
# tcpServer.accept(),不需要写ip,可以接收多个客户端的。但事先要绑定端口和接入的客户端的数量
# client 表示接入的新的客户端
# clientInfo 表示接入的新的客户端的ip和端口port recvData = tcpClient.recv(1024)
print('%s: %s' % (str(tcpClientInfo), recvData.decode('gbk'))) # tcp的四次握手,写进了这一句话
tcpClient.close()
tcpServer.close() # tcpServer.accept():等待客户端的接入,自带堵塞功能:即必须接入客户端,然后往下执行
# tcpClient.recv(1024): 也是堵塞,不输入数据就一直等待,不往下执行.
# tcpServer创建了两个套接字,一个是Server,另一个是tcpClient.Server负责监听接入的Client,再为其创建专门的tcpClient进行通信.
3、服务端开启循环和多线程模式
开启循环
import socket Server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Server.bind(('', 9000))
Server.listen(10) while True:
# 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务
serverThisClient, ClientInfo = Server.accept()
print('Waiting connect......') # 如果客户发送的数据是空的,那么断开连接
while True:
recvData = serverThisClient.recv(1024)
if len(recvData) > 1: print('recv: %s' % recvData.decode('gbk')) sendData = input('send: ')
serverThisClient.send(sendData.encode('gbk'))
else:
print('再见!')
break
serverThisClient.close()
多线程写法
from threading import Thread
import socket
# 收数据,然后打印
def recvData():
while True:
recvInfo = udpSocket.recvfrom(1024)
print('%s:%s' % (str(recvInfo[1]), recvInfo[0].decode('gbk'))) # 检测键盘,发数据
def sendData():
while True:
sendInfo = input('')
udpSocket.sendto(sendInfo.encode('gbk'), (destIp, destPort)) udpSocket = None
destIp = ''
destPort = 0
# 多线程
def main(): global udpSocket
global destIp
global destPort destIp = input('对方的ip: ')
destPort = int(input('对方的端口:')) udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udpSocket.bind(('', 45678)) tr = Thread(target=recvData)
ts = Thread(target=sendData) tr.start()
ts.start() tr.join()
ts.join()
if __name__ == '__main__':
main()
三、socket 获取 html
目标:获取https://movie.douban.com/top250整个网页。
过程:
1.构造GET请求头,包括: https协议处理,movie.douban.com为host,port默认为443,/top250为请求的url。
2.粘包接收字节流并进行解析,返回状态码、响应头、响应体。
实例代码:
import socket, ssl """
https 请求的默认端口是 443,https 的 socket 连接需要 import ssl,并且使用 s = ssl.wrap_socket(socket.socket()) 来初始化 HTTP 协议的 301 状态会在 HTTP 头的 Location 部分告诉你应该转向的 URL
如果遇到 301, 就请求新地址并且返回
HTTP/1.1 301 Moved Permanently
...
Location: https://movie.douban.com/top250
""" def parse_url(url):
protocol = 'http' if url[:7] == 'http://':
u = url.split("://")[1]
elif url[:8] == "https://":
protocol = "https"
u = url.split("://")[1]
else:
u = url # https://g.cn:1234/hello/world
# 这里host就是g.cn:1234
# 这里path就是/hello/world # 检查host和path
i = u.find('/')
if i == -1:
host = u
path = '/'
else:
host = u[:i]
path = u[i:] # 检查端口
port_dict = dict(
http=80,
https=443,
)
# 默认端口
port = port_dict[protocol]
if ":" in host:
h = host.split(":")
host = h[0] # 到这里获取ip
port = int(h[1]) # 获取端口
return protocol, host, port, path def socket_by_protocol(protocol):
"""根据协议返回一个socket实例"""
if protocol == 'http':
s = socket.socket()
else:
# https协议要用到ssl
s = ssl.wrap_socket(socket.socket())
return s def response_by_socket(s):
"""s是一个socket实例,返回这个socket读取的所有数据"""
response = b''
buffer_size = 1024
while True:
r = s.recv(buffer_size)
if len(r) == 0:
break
response += r
return response def parsed_response(r):
"""
把response解析出 状态码 headers body 返回
状态码是 int
headers是 dict
body是 str
"""
header, body = r.split('\r\n\r\n', 1) # split 1 只分割1次
h = header.split('\r\n') # HTTP/1.1 200 OK
status_code = h[0].split()[1] # 空格切分,取中间的状态码
status_code= int(status_code) headers = {}
for line in h[1: ]:
k, v = line.split(": ")
headers[k] = v
return status_code, headers, body def get(url):
"""用get请求url并返回响应"""
protocol, host, port, path = parse_url(url)
print(protocol, host, port, path)
# 根据protocol确定socket方式
s = socket_by_protocol(protocol)
s.connect((host, port)) # 构造请求,注意不要用keep-alive,因为服务器不会主动关闭连接
request = 'GET {} HTTP/1.1\r\nhost: {}\r\nconnection: close\r\n\r\n'.format(path, host) # 发送请求
s.send(request.encode(encoding='utf_8', errors='strict')) # 获取响应
response = response_by_socket(s) # 接收所有的数据
r = response.decode('utf-8') status_code, headers, body = parsed_response(r) # 解析状态码,请求头和请求体 if status_code in [301, 302]: # 301和302是重定向,要递归进行寻址
url = headers["Location"]
return get(url)
return status_code, headers, body def main():
url = 'https://movie.douban.com/top250'
status_code, headers, body = get(url)
print(status_code, headers, body) if __name__ == '__main__':
main()
缺陷:这里没有对html文档进行处理,包括html文档中所需内容(node节点)的解析和保存。可以使用bs4进一步处理。
注意:request请求必须要确保无误,否则会很容易出错。
四、socket写web服务
这里的内容极其重要。一切web框架的本质是socket。Django和Flask的框架都是在下面最简功能上进行拆分解耦建立起来的。
import socket, functools
from docutils.parsers.rst.directives import encoding # 包裹print,禁止它的一些额外的功能
def log(*args, **kwargs):
print("LOG: ", *args, **kwargs)
# 禁止函数的默认返回, func = lambda x: x, print = func(print)。这个在flask的werkzeug.local中用了几十次。 def route_index():
"""主页的处理函数, 返回响应"""
header = 'HTTP/1.x 200 OK \r\nContent-Type: text/html\r\n'
body = '<h1>Hello World.</h1><img src="dog.gif"/>'
r = header + '\r\n' + body
return r.encode(encoding='utf-8') def route_image():
"""返回一个图片"""
with open('dog.gif', mode='rb') as f:
header = b'HTTP/1.x 200 OK\r\nContent-Type: image/gif\r\n\r\n'
img = header + f.read()
return img def page(html):
with open(html, encoding="utf-8") as f:
return f.read() def route_msg():
"""返回一个html文件"""
header = 'HTTP/1.x 200 OK \r\nContent-Type: text/html\r\n'
body = page("html_basic.html")
r = header + '\r\n' + body
return r.encode(encoding='utf-8') def error(code=404):
e = {
404: b'HTTP/1.x 404 NOT FOUND\r\n\r\n<h1>Page Not Found</h1>',
}
return e.get(code, b'') def response_for_path(path):
"""根据path调用相应的处理函数,没有处理的path会返回404"""
r = {
'/': route_index,
'/dog.gif': route_image,
'/msg': route_msg,
}
response = r.get(path, error) # 注意,这里用dict的get方法设置了不存在时的默认值
return response() def run(host="", port=3000):
"""启动服务器"""
with socket.socket() as s:
# 使用with可以保证程序终端的时候正确关闭socket,释放占用的端口
s.bind((host, port)) while True:
s.listen(5) connection, address = s.accept()
request = connection.recv(1024)
request = request.decode('utf-8')
log('ip and request, {}\n{}'.format(address, request)) try:
path = request.split()[1]
response = response_for_path(path) # 用response_for_path函数来根据不同的path,生成不同的响应内容
connection.sendall(response) except Exception as e:
log("error ", e) connection.close() def main():
config = dict(
host='',
port=4000,
)
run(**config) if __name__ == '__main__':
main()
用到的图片直接放在当前.py同一目录下即可。html也是同级目录,内容如下:
<!DOCTYPE html>
<!-- 注释是这样的, 不会被显示出来 -->
<!--
html 格式是浏览器使用的标准网页格式
简而言之就是 标签套标签
-->
<!-- html 中是所有的内容 -->
<html>
<!-- head 中是放一些控制信息, 不会被显示 -->
<head>
<!-- meta charset 指定了页面编码, 否则中文会乱码 -->
<meta charset="utf-8">
<!-- title 是浏览器显示的页面标题 -->
<title>例子 1</title>
</head>
<!-- body 中是浏览器要显示的内容 -->
<body>
<!-- html 中的空格是会被转义的, 所以显示的和写的是不一样的 -->
<!-- 代码写了很多空格, 显示的时候就只有一个 -->
很 好普通版
<h1>很好 h1 版</h1>
<h2>很好 h2 版</h2>
<h3>很好 h3 版</h3>
<!-- form 是用来给服务器传递数据的 tag -->
<!-- action 属性是 path -->
<!-- method 属性是 HTTP方法 一般是 get 或者 post -->
<!-- get post 的区别上课会讲 -->
<form action="/" method="get">
<!-- textarea 是一个文本域 -->
<!-- name rows cols 都是属性 -->
<textarea name="message" rows="8" cols="40"></textarea>
<!-- button type=submit 才可以提交表单 -->
<button type="submit">GET 提交</button>
</form>
<form action="/" method="post">
<textarea name="message" rows="8" cols="40"></textarea>
<button type="submit">POST 提交</button>
</form>
</body>
</html>
运行上述py程序,访问localhost:4000/msg,在get和post输入框里分别输入内容并点击发送。可以看到,get请求的参数包含在请求头里,post请求的参数包含在请求体里。这需要分别处理并获取参数。
五、从socket到web框架
有了上述内容,整个url请求的处理流程如下图所示。
创建python package。创建以下几个py文件。(https://github.com/ZJingyu/socket_learning)
- web
- static: 静态文件
- templates: html文件
- server: 主要负责监听socket连接和接收请求,并对请求进行解析,传递给不同的处理函数。
- routes: url对应的处理函数。
- models: 数据交互的处理。
当然,对于models和routes都是可以再拆分。如:
- web
- static
- templates
- db
- views.py: 将routes改名为views.py,删掉route_dict。
- urls.py: 把route_dict放进来,从views.py中导入所有视图函数。
- models:新建名为models的python package,创建Message.py和User.py,将原models.py中的三个类分开,Model放进__init__.py里。
__init__.py: Model类
- Message.py: Message类
- User.py: User类
# __init__.py import json def save(data, path):
"""把一个dict或者list写入文件""" # indent是缩进, ensure_ascii=False用于保存中文
s = json.dumps(data, indent=2, ensure_ascii=False)
with open(path, 'w+', encoding='utf-8') as f:
print('save', path, s, data)
f.write(s) def load(path):
"""从一个文件中载入数据并转化为dict或者list"""
with open(path, 'r', encoding='utf-8') as f:
s = f.read()
print('load', s)
return json.loads(s) # Model是用于存储数据的基类
class Model(object):
@classmethod
def db_path(cls):
classname = cls.__name__
path = '{}.txt'.format(classname)
return path @classmethod
def new(cls, form):
m = cls(form) # 初始化实例 m = Model(form)
return m @classmethod
def all(cls):
path = cls.db_path()
models = load(path)
ms = [cls.new(m) for m in models]
return ms def save(self):
models = self.all()
print('models', models)
models.append(self)
l = [m.__dict__ for m in models]
path = self.db_path()
save(l, path) def __repr__(self):
classname = self.__class__.__name__
properties = ['{}: ({})'.format(k, v) for k, v in self.__dict__.items()]
s = '\n'.join(properties)
return '< {}\n{} >\n'.format(classname, s)
# Message.py
from . import Model # 定义一个class用于保存message
class Message(Model):
def __init__(self, form):
self.author = form.get('author', "")
self.message = form.get('message', "")
# User.py
from . import Model # 以下两个类用于实际的数据处理
class User(Model):
def __init__(self, form):
self.username = form.get('username', "")
self.password = form.get('password', "") def validate_login(self):
return self.username == "gua" and self.password == "" def validate_register(self):
return len(self.username) > 2 and len(self.password) > 2
当然,,server也可以再拆分成server和manage的。将serve.py中的if main 放到manage.py去执行。
上面这些虽然简陋,但是实现了socket搭建web服务器的基本逻辑和功能,有助于梳理web服务的流程。原理在手,一切的web框架不外乎如是。
Django(一):从socket到MVC的更多相关文章
- Django的MVT模式与MVC模式
Django的MVT模式与MVC模式 在正式开始coding之前,我觉得有必要探讨下Django的MVT模式,理论和实践相结合,才能更好的掌握一门技术.Django中的MVT模式,Django就是属于 ...
- django模块导入/函数/中间件/MVC和MTV/CSRF
目录 一:模块导入 二:函数 三:中间件 四:MVC和MTV 五:csrf 一:模块导入 第一种:继承 这里的母版更像是一个架子,子板都是定义的内容(如果多个页面中 ,存在相同的页面:这样我们可以抽到 ...
- Django——WEB三层架构与MVC
而我发此文的目的有二:一者,让初学者能够听到一家之言,是为解惑:二者,更希望抛砖引玉,得到专家的批判. 许多学生经常问我,MVC到底和WEB三层架构有啥关系? 开始时,我也只能给他们一些模糊的回答.时 ...
- Django之WSGI 和MVC/MTV
一.什么是WSGI? WEB框架的本质是一个socket服务端接收用户请求,加工数据返回给客户端(Django),但是Django没有自带socket需要使用 别人的 socket配合Django才能 ...
- Django WSGI,MVC,MTV,中间件部分,Form初识
一.什么是WSGI? WEB框架的本质是一个socket服务端接收用户请求,加工数据返回给客户端(Django),但是Django没有自带socket需要使用 别人的 socket配合Django才能 ...
- Django之MVC与MTV
MVC框架 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写 模型 - 视图 - 控制器是一种通常用于开发用户界面的 ...
- Django MVC与MTV概念 Ajax、分页实现
MVC与MTV概念 MTV与MVC(了解) MTV模型(django): M:模型层(models.py) T:templates ...
- Django多对多表的三种创建方式,MTV与MVC概念
MTV与MVC MTV模型(django): M:模型层(models.py) T:templates V:views MVC模型: M:模型层(models.py) V:视图层(views.py) ...
- django ajax MTV与MVC 多对多创建方式
MTV与MVC MTV模型(django): M:模型层(models.py) T:templates V:views MVC模型: M:模型层(models.py) V:视图层(views.py) ...
随机推荐
- 【Python】 \uxxxx转中文
背景 写Python接口自动化过程中,使用到邮件发送测试结果详情,邮件呈现出来的内容为 \uxxxx ,不是中文 接收到的邮件内容: 成功: 110 失败: 1 失败的用例如下 : [(u'\u752 ...
- nginx 日志搜集解决方案
# nginx 日志搜集解决方案 ## 系统环境描述 ``` java8 logstash --监控nginx日志文件 ``` ## 技术描述 ``` 通过logstash监控nginx access ...
- Selenium入门练习(二)
自动登录博客园并且退出登录 package TestNG; import org.testng.annotations.Test;import org.testng.annotations.Befor ...
- ArcMap加载在线地图
SimpleGIS 小小的SimpleGIS除了提供6大地图让人喜爱之外,更有其他的能耐同样让你爱不释手. 功能1:作为出图底图地图提供商中Bing.天地图两家提供的地图是无偏移的地图,所以可直接应用 ...
- 图片服务器(FastDFS)的搭建
1.1 什么是FastDFS FastDFS是用c语言编写的一款开源的分布式文件系统.FastDFS为互联网量身定制,充分考虑了冗余备份.负载均衡.线性扩容等机制,并注重高可用.高性能等指标,使用Fa ...
- lvs+keepalived+vsftp配置FTP服务器负载均衡
LVS+Keepalive 实现服务器的负载均衡高可用一.安装两台机器的安装是一样的,这里只记录一遍.1. 下载LVS+Keepalive 所需安装包http://www.keepalived.org ...
- 【Python】测算代码运行时间
整理自这里和这里 timeit模块 timeit模块定义了接受两个参数的 Timer 类.两个参数都是字符串. 第一个参数是你要计时的语句或者函数. 传递给 Timer 的第二个参数是为第一个参数语句 ...
- linux命令权限
linux-命令权限 1) 新建用户natasha,uid为1000,gid为555,备注信息为“master” 2) 修改natasha用户的家目录为/Natasha 3) 查看用户信息 ...
- h1标签
h1标签一.每个网页只能拥有一个<H1>标签 H2,H3,H4可以有多个...但多个H1造成的后果是搜索引擎不知道你这个页面哪个标题内容最重要,会淡化这个页面的标题和关键词.H1用得好的话 ...
- 自研发RPC调用框架
自主研发设计RPC远程调用框架,实现服务自动注册,服务发现,远程RPC调用,后续实现服务负载均衡 主要包括:客户端服务,服务端,服务发现,服务注册 github地址:https://github.co ...