码迷,mamicode.com
首页 > Web开发 > 详细

HTTP协议

时间:2019-12-27 15:29:14      阅读:121      评论:0      收藏:0      [点我收藏+]

标签:ons   等等   通过命令   line   内容   传输层   红点   选择   嵌套   

HTTP协议:

"""
http服务器本质也是tcp服务器.

浏览器  --> (通过HTTP协议 格式) HTTP服务器,也可看作是TCP服务器

格式:就是http协议
域名就是一串字符串.域名的本质还是IP.
域名找DNS去解析域名,转换成对应的IP.(类似找电话本的过程,由电话本里面的名字找到对应的电话号码,这里名字就是域名,电话号码就是IP地址)
域名解析:域名转换成IP地址
domain name service
DNS:1.> 建立连接 2.> 发起请求 3.> 回复响应
DHCP:动态主机配置协议 dynamic host configuration protocol .给局域网中的计算机自动分配IP
"""

"""
url:就是网址,分为下面三部分
	1.协议部分
	2.域名部分
	3.资源路径部分   --> 就是用户想要访问的资源在服务器的哪个位置的哪个文件
	
HTTP协议: 超文本传输协议 就是用来传输网页的数据.制作者:蒂姆.博纳斯.李 为了方便科学家展示研究成果
而开发的协议.就可以实现跨网络的数据传输.
目前使用的就是http/1.1版本
HTTP作用:浏览器-服务器之间传输网页数据资源
HTTP协议:就是tcp协议加上一些格式
网路传输模型:
	应用层 http/https ftp/sftp:加了s的就是加密了
	传输层 tcp/udp 比较底层了  端口是传输层使用的
	网络层 ip  更加底层了
	网络接口层  最底层
	
寄快递--> 应用层  任务/目标
快递公司--> 顺丰 邮政 选择哪一家快递公司提供服务  传输层  选择哪一种传输协议
物流中转站--> 网络层 转发 选择路径 根据目的ip选择路径 转发出去 就是根据目的地址选择走哪条路径 然后发出去
运输工具--> 轮船,飞机,火车,汽车 网络接口层,就看底层 提供了什么工具了  运输过程选择什么工具进行运输
路由器是工作在网络层的/和ip打交道 将数据包根据ip进行转发 <就是选择路径发出去>
交换机是工作在传输层的

HTTP协议的工作模式:一次请求<request>, 一次响应<response>的模式
index.html结尾的一般代表首页.
一种表示状态,一种表示真正的用户需要的数据
请求结果:
状态表示请求是否成功
数据表示真正需要的数据
http中 一定是浏览器主动发起请求,服务器根据请求返回对应的资源数据.

HTTP:基于tcp协议实现的
	请求/响应式的协议
	应用层协议
	作用:规定了服务器和浏览器之间数据通信的格式.数据一般是网页,图片,音频视频

请求就是请求报文<报文就是数据的意思>
响应就是响应报文<报文就是数据的意思>
请求报文和响应报文都是有自己规定好的格式.

source:就是网页源代码,也是代码最核心的部分,有的人写作core或者src
浏览器在向服务器获取网页资源的同时还会获取网页相关的图片资源,所以一般使用rb/wb模式进行读写访问操作	

chrome工具:
	红点:记录通信过程
	每一行都是一个请求,一次一次响应的过程
	响应头:放的是请求/响应的状态
	响应体:放的是真正响应请求对应的数据
	横向的是响应体body
	纵向的是响应头headers

查看原来的格式 view source
查看解析后的格式 parsed source    parsed:解析之后的意思.

学习策略:自己理解/动手验证 给别人讲一讲
"""


"""请求报文格式:"""
"""
GET / HTTP/1.1  ---> request line 请求行,分为三部分 
---> 第一部分 : GET:请求方法<向服务器获取资源60%的使用场景>,向服务器上传资源使用-->POST :发送 ,推送.就是发送       <rfc2616标准文档>
---> 第二部分 :资源路径 默认情况都是加了/的  不管用户是否输入了/
---> 第三部分 :HTTP的协议版本      1.1的现在最通用


下面的都称之为请求头request headers   形式都是 名字:值
请求头:

Host: www.baidu.com   ---> 标识本次请求的服务器的身份<意思就是请求的哪一个服务器>  host还可以是ip地址:[端口号]  443是https的端口 

Connection: keep-alive  ---> 保持存活,长连接.代表是长连接还是短连接<close 关闭> ,这里是长连接

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36  
---> 用户代理 浏览器身份<理解:代理就是帮你起个名字来标识你>

应用场景:1.网站反爬 2.如果做一个网站,开发多个版本的适配,可以使用这个来进行适配.
适配:不同种类浏览器之间/PC端和移动端之间  这些都要进行适配. 

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8  ---> 接受 标识可以接受的是文件类型,比如是text文本,html网页的 


Accept-Encoding: gzip, deflate, br  ---> 可以接收的压缩方式 (降低网络传输的带宽,提高传输速度,文字的压缩率一般是20%,缺点:加重了cpu的负担>


Accept-Language: zh-CN,zh;q=0.9  ---> 可以接收的语言<实际是编码格式>  q表示权重,代表90%的情况下返回中文的


"""

"""
总结: 请求报文格式分为几部分?四部分
1.>请求行[请求方法,资源路径,版本\r\n]
2.>请求头[名称:值\r\n]   请求头可以有多个
3.>空行 [\r\n]
4.>请求体:<浏览器向服务器发送的消息>只存浏览器上传给服务器的数据,比如图片,文字,音频等等. 这一行不需要加\r\n

只有POST的才需要发送请求体,get方法不需要请求体

"""
"""
响应报文:服务器返回 给浏览器的数据.

状态码:
200:成功 表示访问成功
307:重定向,就是你要A,它把B给你<前提是A不存在了,A被B替代了>
404:请求的路径不存在  not found
503:服务不可用 service not application


响应报文格式:

HTTP/1.1 200 OK   2:成功 3:重定向 4:客户端错误,用户访问的数据不存在 5:服务器错误
	response line 响应行
	1.版本
	2.状态 200-->状态码 
	3.OK-->状态短语  状态码和状态短语说明是一一对应的.
为啥发的时候要版本了,回的时候也要版本?因为要收发一致,不一致就会出问题.,所以每次收和发都要 加版本号进行 确认.	

响应头 头名:值

1.>Connection: Keep-Alive
	代表长连接  如果是close代表短连接

2.>Date: Tue, 11 Sep 2018 02:39:06 GMT   默认情况下显示的0时区<格林威治>的时间,我们是使用东八区,所以差8小时<北京时间是东八区> 
PS:以后如果是跨国业务,最好使用0时区,直接加减对应的时差就行.千万不要使用东八区.麻烦

3.>Expires: Tue, 11 Sep 2018 02:38:32 GMT
	过期  租期,就是使用多久的意思
	
4.>Server: BWS/1.1  就是baidu server  代表服务器的后台的应用程序的身份
	Server:nginx,C++,Python等等都用这个. 世界上通用的web服务器     apache: java使用的多一点
	
5.>Content-Encoding: gzip  
	内容(实体 - 请求体/响应体) 压缩方式   根据头部所处报文决定什么体
	浏览器在接收到头部的时候,就可以把响应体的数据解压出来
	
6.>Content-Type: text/html  text/css  image/png
	实体的文件类型<文本的html类型><文本的html类型>
	浏览器可以根据该头部
7.>Content-Length:1207
	服务器可以根据该头部  接收对应长度的数据作为响应体

Content-X: 实体头部      也可以用在请求报文中


响应报文格式总结: 分为四部分
	1.响应行[版本 状态码 说明\r\n]
	2.响应头[名称:值\r\n]
	3.空行 [\r\n]  就是起一个隔开的作用  
	4.响应体 [服务器给浏览器发送的数据]<也就是浏览器需要的数据>


响应报文分为几部分?
状态码有几种?
server  web服务器程序的身份
	content-type
	content-length
	content-encoding 压缩方式	
"""

模拟简单浏览器进行网络通信:

# 0.创建socket
# 1.输入网址 域名解析 获取服务器ip
# 2.和服务器建立连接
# 3.发送 请求报文
# 4.接收 响应报文

# \r\n记得一定要带,否则会出错
import socket
# 创建socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接
tcp_socket.connect((www.jd.com, 80))
# 请求行
request_line = GET / HTTP/1.1\r\n
# 请求头
request_header = User-Agent:%s\r\n % (
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36)
# 拼接请求数据
request_data = request_line + request_header + \r\n
# 转成bytes字节类型进行发送
tcp_socket.send(request_data.encode())
# 返回服务器回送的数据
response_data = tcp_socket.recv(4096)
# 输出
print(response_data.decode())

web服务器:

"""
流程: Nginx  脑海中建立一条生产线
PWS1.0目的:在用户每次访问的时候,都返回一个固定的数据,例如Hello World
1.>接收请求报文
2.>解析请求报文-得到用户需求
3.>根据用户的需求找到对应的资源
4.>资源打包到HTTP响应报文
5.>发送响应报文给浏览器

最开始肯定是最简单的,一步一步的加多,慢慢地才会出来
PWS1.0:Python Web Server 1.0
"""
import socket


def main():
    # 1. 创建tcp服务器的socket  设置选项 绑定 监听
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((‘‘, 8887))  # 这里绑定的是服务器的固定端口,目的是让客户端能连接到了我的机器上面之后,可以找到我的这个程序
    server_socket.listen(128)

    while True:
        # 2.接受用户连接,从这里开始才是需要不断的进行,才使用死循环
        client_socket, client_addr = server_socket.accept()
        print(接受到%s的连接请求 % str(client_addr))

        # 3.接收用户请求报文
        request_data = client_socket.recv(4096)
        print(type(request_data))  # <class ‘bytes‘>
        print(request_data)

        # 4.返回固定的数据 Hello World  打包到响应报文中   PWS1.0\r\n这里是一个空行\r\n  所以不能少   <h1> 标题加粗的效果
        #               响应行               响应头/可以是0个  空行<不能省略,隔离作用> 响应体数据   这四个 只有响应头可以省略,其他的都不可以省略
        response_data = HTTP/1.1 200 OK\r\nServer:PWS1.0\r\n\r\n + <h1>Hello World</h1>
        client_socket.send(response_data.encode())

        # 5.一次请求/响应 就关闭连接  --> 短连接
        client_socket.close()


if __name__ == __main__:
    main()

web服务器返回固定页面:

"""
PWS2.0目的:在用户每次访问的时候,都会返回一个固定的页面,比如index.html

1.>接收请求报文
2.>解析请求报文-得到用户需求
3.>根据用户的需求找到对应的资源
4.>资源打包到HTTP响应报文

最开始肯定是最简单的,一步一步的加多,慢慢地才会出来
PWS2.0:Python Web Server 2.0
"""
import socket


def main():
    # 1. 创建tcp服务器的socket  设置选项 绑定 监听
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((‘‘, 8888))  # 这里绑定的是服务器的固定端口,目的是让客户端能连接到了我的机器上面之后,可以找到我的这个程序
    server_socket.listen(128)

    while True:
        # 2.接受用户连接
        client_socket, client_addr = server_socket.accept()
        print(接受到%s的连接请求 % str(client_addr))

        # 3.接收用户请求报文
        request_data = client_socket.recv(4096)
        print(request_data)

        # 4.返回固定的文件数据作为响应体   打包到响应报文中

        with open(33.jpg, rb) as file:
            html_data = file.read()  # html_data 是二进制bytes字节类型的数据

        # .encode()先把前面的str类型转成bytes字节类型,然后拼接
        response_data = (HTTP/1.1 200 OK\r\nServer:PWS2.0\r\n\r\n).encode() + html_data
        client_socket.send(response_data)

        # 5.一次请求/响应 就关闭连接  --> 短连接
        client_socket.close()


if __name__ == __main__:
    main()

web服务器-面向对象实现:

"""
PWS5.0目的:使用协程实现多任务,提高体验

1.>接收请求报文
2.>解析请求报文-得到用户需求
3.>根据用户的需求找到对应的资源
4.>资源打包到HTTP响应报文

最开始肯定是最简单的,一步一步的加多,慢慢地才会出来
PWS2.0:Python Web Server 2.0

主从
master slave

主:接受用户数据
从:干活

leader:
follower:



"""
import socket
import re
import gevent
from gevent import monkey

monkey.patch_all()  # 自动切换  time.sleep recv accept 这些类型的函数在执行的时候会出现大量的耗时等待,所以让这部分闲置时间加以利用.





class HTTPServer:
    """web服务"""

    def __init__(self):
        """初始化操作"""
        # 1. 创建tcp服务器的socket  设置选项 绑定 监听
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind((‘‘, 8999))  # 这里绑定的是服务器的固定端口,目的是让客户端能连接到了我的机器上面之后,可以找到我的这个程序
        server_socket.listen(128)

        # 将socket对象保存在 当前对象的属性中
        self.server_socket = server_socket

    def start(self):
        """启动服务"""
        while True:
            client_socket, client_addr = self.server_socket.accept()
            print(接受到%s的连接请求 % str(client_addr))
            # 为每个用户创建一个协程,并且启动 运行
            gevent.spawn(self.client_handler, client_socket)

    def client_handler(self,client_socket):
        """处理每个客户端的请求"""
        # 3.接收用户请求报文
        request_data = client_socket.recv(4096)
        # 解码 bytes  --> str
        request_data_str = request_data.decode()
        print(request_data)
        # ps:当用户的请求路径为/时候,表示其请求的是首页homepage
        # 解析用户请求 获取到用户的资源请求路径
        # 正则匹配 只要是有/ 提取请求路径
        # GET /index.html
        result = re.search(r^\w+ (/\S*), request_data_str)
        # 判断是否提取成功  成功就取出这个值,不成功结束程序
        if not result:  # 路径为空
            print(请求报文格式错误)
            client_socket.close()
            return  # 结束 这种情况
        # 尽量扁平结构 不要使用else缩进去,那样的是嵌套结构,这是小技巧
        # 获取请求路径  资源路径<static> + /index.html
        # 如果执行到这里,一定是匹配成功了,因为失败的已经跳过了
        path_info = result.group(1)
        # 4.返回用户指定的文件数据作为响应体   打包到响应报文中  static + /index.html
        # static 是文件夹,open打不开 潜规则 :所有的web服务器 不写路径默认是主页

        # 如果path_info 是/ ,返回 index首页
        if path_info == /:
            path_info = /index.html

        try:
            with open(static + path_info, rb) as file:
                html_data = file.read()  # html_data 是二进制bytes字节类型的数据
        except Exception as e:
            with open(404.html, rb) as file:
                html_data = file.read()
            response_data = (HTTP/1.1 404 Not Found\r\nServer: PWS3.0Plus\r\n\r\n).encode() + html_data
        else:
            # .encode()先把前面的str类型转成bytes字节类型,然后拼接
            response_data = (HTTP/1.1 200 OK\r\nServer: PWS3.0Plus\r\n\r\n).encode() + html_data

        finally:
            # 统一最后发送
            client_socket.send(response_data)
            # 5.一次请求/响应 就关闭连接  --> 短连接
            client_socket.close()


if __name__ == __main__:
    # 创建web服务
    http_server = HTTPServer()
    # 启动web服务
    http_server.start()


"""
总结:
    # 封装 方法和属性封装在一个类中
    # 继承 功能扩展 快速获取技能
    # 多条 一种形式的多种状态

    # 面向过程  吃狗屎  吃(狗,屎)
    # 面向对象  狗吃屎   狗.吃(翔)
"""

协程实现多任务:

"""
PWS5.0目的:使用协程实现多任务,提高体验

1.>接收请求报文
2.>解析请求报文-得到用户需求
3.>根据用户的需求找到对应的资源
4.>资源打包到HTTP响应报文

最开始肯定是最简单的,一步一步的加多,慢慢地才会出来
PWS2.0:Python Web Server 2.0

主从
master slave

主:接受用户数据
从:干活

leader:
follower:



"""
import socket
import re
import gevent
from gevent import monkey
monkey.patch_all()  # 自动切换  time.sleep recv accept 这些类型的函数在执行的时候会出现大量的耗时等待,所以让这部分闲置时间加以利用.


def client_handler(client_socket):
    """处理每个客户端的请求"""
    # 3.接收用户请求报文
    request_data = client_socket.recv(4096)
    # 解码 bytes  --> str
    request_data_str = request_data.decode()
    print(request_data)
    # ps:当用户的请求路径为/时候,表示其请求的是首页homepage
    # 解析用户请求 获取到用户的资源请求路径
    # 正则匹配 只要是有/ 提取请求路径
    # GET /index.html
    result = re.search(r^\w+ (/\S*), request_data_str)
    # 判断是否提取成功  成功就取出这个值,不成功结束程序
    if not result:  # 路径为空
        print(请求报文格式错误)
        client_socket.close()
        return  # 结束 这种情况
    # 尽量扁平结构 不要使用else缩进去,那样的是嵌套结构,这是小技巧
    # 获取请求路径  资源路径<static> + /index.html
    # 如果执行到这里,一定是匹配成功了,因为失败的已经跳过了
    path_info = result.group(1)
    # 4.返回用户指定的文件数据作为响应体   打包到响应报文中  static + /index.html
    # static 是文件夹,open打不开 潜规则 :所有的web服务器 不写路径默认是主页

    # 如果path_info 是/ ,返回 index首页
    if path_info == /:
        path_info = /index.html

    try:
        with open(static + path_info, rb) as file:
            html_data = file.read()  # html_data 是二进制bytes字节类型的数据
    except Exception as e:
        with open(404.html, rb) as file:
            html_data = file.read()
        response_data = (HTTP/1.1 404 Not Found\r\nServer: PWS3.0Plus\r\n\r\n).encode() + html_data
    else:
        # .encode()先把前面的str类型转成bytes字节类型,然后拼接
        response_data = (HTTP/1.1 200 OK\r\nServer: PWS3.0Plus\r\n\r\n).encode() + html_data

    finally:
        # 统一最后发送
        client_socket.send(response_data)
        # 5.一次请求/响应 就关闭连接  --> 短连接
        client_socket.close()


def main():
    # 1. 创建tcp服务器的socket  设置选项 绑定 监听
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind((‘‘, 8999))  # 这里绑定的是服务器的固定端口,目的是让客户端能连接到了我的机器上面之后,可以找到我的这个程序
    server_socket.listen(128)

    while True:
        # 2.接受用户连接  协程实现 实现多个用户连接
        client_socket, client_addr = server_socket.accept()
        print(接受到%s的连接请求 % str(client_addr))
        # 为每个用户创建一个协程,并且启动 运行
        gevent.spawn(client_handler, client_socket)
    # 等待协程执行完成,因为协程依赖于主进程<主进程退出后协程也跟着退出了>
    # g1.join() 这里不需要这个,因为主进程不会退出   主进程本身就要一个while True死循环,主进程不会退出,所以创建的协程就不会受到主进程因退出带来的影响


if __name__ == __main__:
    main()

web服务器-命令行参数控制内部绑定的端口:

import socket
import re
import gevent
from gevent import monkey
import sys

monkey.patch_all()  # 自动切换  time.sleep recv accept 这些类型的函数在执行的时候会出现大量的耗时等待,所以让这部分闲置时间加以利用.


class HTTPServer:
    """web服务"""

    def __init__(self, port):
        """初始化操作"""
        # 1. 创建tcp服务器的socket  设置选项 绑定 监听
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.bind((‘‘, port))  # 这里绑定的是服务器的固定端口,目的是让客户端能连接到了我的机器上面之后,可以找到我的这个程序
        server_socket.listen(128)

        # 将socket对象保存在 当前对象的属性中
        self.server_socket = server_socket

    def start(self):
        """启动服务"""
        while True:
            client_socket, client_addr = self.server_socket.accept()
            print(接受到%s的连接请求 % str(client_addr))
            # 为每个用户创建一个协程,并且启动 运行
            gevent.spawn(self.client_handler, client_socket)

    def client_handler(self, client_socket):
        """处理每个客户端的请求"""
        # 3.接收用户请求报文
        request_data = client_socket.recv(4096)
        # 解码 bytes  --> str
        request_data_str = request_data.decode()
        print(request_data)
        # ps:当用户的请求路径为/时候,表示其请求的是首页 homepage
        # 解析用户请求 获取到用户的资源请求路径
        # 正则匹配 只要是有/ 提取请求路径
        # GET /index.html
        result = re.search(r^\w+(/\S*), request_data_str)
        # 判断是否提取成功  成功就取出这个值,不成功结束程序
        if not result:  # 路径为空
            print(请求报文格式错误)
            client_socket.close()
            return  # 结束 这种情况
        # 尽量扁平结构 不要使用else缩进去,那样的是嵌套结构,这是小技巧
        # 获取请求路径  资源路径<static> + /index.html
        # 如果执行到这里,一定是匹配成功了,因为失败的已经跳过了
        path_info = result.group(1)
        # 4.返回用户指定的文件数据作为响应体   打包到响应报文中  static + /index.html
        # static 是文件夹,open打不开 潜规则 :所有的web服务器 不写路径默认是主页

        # 如果path_info 是/ ,返回 index首页
        if path_info == /:
            path_info = /index.html

        try:
            with open(static + path_info, rb) as file:
                html_data = file.read()  # html_data 是二进制bytes字节类型的数据
        except Exception as e:
            with open(404.html, rb) as file:
                html_data = file.read()
            response_data = (HTTP/1.1 404 Not Found\r\nServer: PWS3.0Plus\r\n\r\n).encode() + html_data
        else:
            # .encode()先把前面的str类型转成bytes字节类型,然后拼接
            response_data = (HTTP/1.1 200 OK\r\nServer: PWS3.0Plus\r\n\r\n).encode() + html_data

        finally:
            # 统一最后发送
            client_socket.send(response_data)
            # 5.一次请求/响应 就关闭连接  --> 短连接
            client_socket.close()


def main():
    # 获取程序运行所需的参数<端口>   python3  1.py  8080  argv:[‘x.py‘,8080]
    if len(sys.argv) < 2:  # 至少是两个参数
        print(参数使用出错,应该是形如:python3 .\07-web服务器-命令行参数控制内部绑定的端口.py 8080)
    # 端口号是一个数字
    port = int(sys.argv[1])

    # 创建web服务
    http_server = HTTPServer(port)
    # 启动web服务
    http_server.start()


if __name__ == __main__:
    main()

# ----------------------------总结:---------------------------------->
"""
如果不指定,该端口就需要在代码中修改,这样修改会容易引起错误
通过命令行指定 不同的端口在程序中直接使用对应端口即可,减少了直接修改代码的情况

减少更改代码的情况,因为端口号会经常修改

解耦合,降低耦合度

内聚:模块的独立程度

一般要求:高内聚,低耦合

1.py bind((,) --> Python3解释器  --> 操作系统  sys.argv存储一个程序运行时所需的参数<比如1.py 8080>

argv:里面全部都是一个字符串

结论:
1.>argv里面存的是程序运行时的参数
2.>argv:是一个list列表,每个元素都是str类型
3.>里面的数据是操作系统启动程序的时候放入到argv中的

"""

HTTP协议

标签:ons   等等   通过命令   line   内容   传输层   红点   选择   嵌套   

原文地址:https://www.cnblogs.com/huaibin/p/12107300.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!