第九章:Python の 网络编程基础(一)
本課主題
- 何为TCP/IP协议
- 初认识什么是网络编程
- 网络编程中的 "粘包"
- 自定义 MySocket 类
- 本周作业
何为TCP/IP 协议
TCP/IP协议是主机接入互网以及接入互联网的两台机器通信的标准,是一個通信合同,比如有两台机器,A 主机 和 B 主机,它们两者之间只是根据合同上的标准来工作就可以啦。TCP/IP 有4层架构:
- 应用层
- 运输层
- 网络层
- 链路层
OSI 七层
- 物理层:
- 链路层:mac 地址,ethernet
- 网络层:是用 IP协议,意思是通过 Ip 地址把信息发出去跟别的主器沟通
- 传输层:是 tcp、udp、port:具体表示一个应用程序。
- 会话层
- 表示层
- 应用层
原地址、目标地址和数据类型,扩播只能在一个子网络中。
初认识什么是网络编程
socket 是 TCP/IP 的一个封裝,对用戶来说它只是一堆接口,socket 是一个软件抽象层,它不负责发送数据,它只帮你做数据处理,Socket有分BS和CS架构,它們本质上都是一个客户端和服务端之間的数据通信,下图说明当客户端和服务端之间进行沟通时所需要调用的方法。创建 socket.socket( ) 对象,它的基本语法是
- s = socket.socket(socket_family, socket_type, protocol=0)
- # tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
服务端
- Socket( ):第一步创建一个 socket 对象,这是用来封装 TCP/IP的过程,之后就可以利用它来发送 TCP 或者是 UDP. e.g. s = socket.socket( )
- bind( ):第二步是绑定 IP 和端口,它接受一个元组类型的数据。e.g. s.bind(('127.0.0.1',8088,))
- listen( ):第三步是定义最多能挂起的数目,e.g. s.listen(2),意思说你当前允许一个客户端在连接,两个客户端在等待发送消息(挂起)。
- accept( ):第四步是创建客户端和服务端之间的那条连接 conn,程序在连接前会处于挂起的状态。 e.g. conn, addr = s.accept( )
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- ip_port = ('127.0.0.1',9999) # 这是一个元组
- # 买手机
- s = socket.socket()
- # 利用创建出来的对象来绑定: 买手机卡
- # 因为这个对象是已经封装好 TCP 协议的
- s.bind(ip_port)
- # 开机
- s.listen(5)
- # 等待电话
- # conn 服务端跟客户端连接的通讯
- conn, addr = s.accept() # 每次听电话只能跟一个人通信中、然后另外条线会挂着
- # 收消息
- recv_data = conn.recv(1024)
- print("--------",type(recv_data))
- # 发消息
- send_data = recv_data.upper()
- conn.send(send_data)
- # 挂电话
- conn.close()
socket服务端代码(基础版)
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- ip_port = ('127.0.0.1',9999) # 这是一个元组
- # 买手机
- s = socket.socket()
- # 利用创建出来的对象来绑定: 买手机卡
- # 因为这个对象是已经封装好 TCP 协议的
- s.bind(ip_port)
- # 开机
- s.listen(5) # 最大接受挂线的数目
- # 等待电话
- # conn 服务端跟客户端连接的通讯
- conn, addr = s.accept() # 每次听电话只能跟一个人通信中、然后另外条线会挂着
- while True:
- # 收消息
- recv_data = conn.recv(1024)
- print("--------",type(recv_data))
- if str(recv_data, encoding='utf8') == 'exit':
- break
- # 发消息
- send_data = recv_data.upper()
- print(send_data)
- conn.send(send_data)
- # 挂电话
- conn.close()
socket服务端代码(优化版)
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- ip_port = ('127.0.0.1',9999)
- s = socket.socket()
- s.bind(ip_port)
- s.listen(5)
- while True:
- conn, addr = s.accept()
- while True:
- try:
- recv_data = conn.recv(1024)
- if len(recv_data) == 0: break
- send_data = recv_data.upper()
- print(send_data)
- conn.send(send_data)
- except Exception:
- break
- conn.close()
socket服务端代码(完整版)
客戶端
- Socket( ):第一步创建一个 socket 对象,这是用来封装 TCP/IP的过程,之后就可以利用它来发送 TCP 或者是 UDP. e.g. s = socket.socket( )
- connect( ):第二步客户端用自己的对象来连接服务端,它接受一个元组类型的数据。e.g. s.connect(('127.0.0.1',8088,))
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- ip_port = ('127.0.0.1',9999) # 这是一个元组
- # 找一个手机
- s = socket.socket()
- # 拨号
- s.connect(ip_port)
- # 发消息
- send_data = input(">>: ").strip()
- s.send(bytes(send_data,encoding='utf-8'))
- # 收消息
- recv_data = s.recv(1024)
- print(str(recv_data, encoding='utf-8'))
- # 挂电话
- s.close()
socket客戶端代码(基础版)
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- ip_port = ('127.0.0.1',9999)
- s = socket.socket()
- s.connect(ip_port)
- while True:
- send_data = input(">>: ").strip()
- if len(send_data) == 0: continue
- s.send(bytes(send_data, encoding='utf8'))
- if send_data == 'exit': break
- recv_data = s.recv(1024)
- print(str(recv_data, encoding='utf8'))
- s.close()
socket客戶端代码(完整版)
運行結果
- #socket_client
- >>: hello python
- HELLO PYTHON
- >>: hello spark
- HELLO SPARK
- >>: hello kafka
- HELLO KAFKA
- >>:
- #socket_server
- -------- <class 'bytes'>
- b'HELLO PYTHON'
- -------- <class 'bytes'>
- b'HELLO SPARK'
- -------- <class 'bytes'>
- b'HELLO KAFKA'
方法一的运行结果
我想用几句话总结一下思路,你如果要用socket编程,需要有 Server-side 和 Client-side 的代码,它们俩用的代码都差不多,Server-side 比 Client-side 只多了一个准备的过程。
- 第一点:在写Serverside 的程序时,首先要创建s=socket.socket( )对象,然后给它绑定一个ip和端口s.bind(ip),然后定义自己最大可接收挂起的连接数目s.listen(5),这好比一个老师在讲课之前首先要准备课室和自己最大能同时教多少个学生,然后开通课室入口 conn,addr = s.accept( ),学生们登记认证好了以后,就可以跟据老师开通的连接进入课室上课了 s.connect(ip_port)。客戶端的 s.connect 就相当于服务端 conn.connect,如果其中一方断了,就会报错。
- 第二点:当成功建立连接后,下一步要考虑的就是消息的发送和接收的流程,Server-side (老师) 和 Client-side (学生) 双方都有发送和接收的功能,只不過 Server-side 是用了 conn 來完成,即 conn.send( ) 和 conn.recv(1024),但 client-side 就用它自己,即 s.send( ) 和 s.recv(1024)。
问题:s.recv(1024) 是什么意思,试说明? - 第三点:在整个 send( ) 和 recv( ) 的过程中,Server-side (老师) 和 Client-side (学生) 双方都有共通点,就是大家接受消息时都有一定的上限和原來老師是一個不慬中文的法国人,要么你直接跟他讲法语,要么你就用百度翻译一下,此刻,我选择了后者:
- # client-side
- send_data = "hello python socket programming"
- s.send(bytes(send_data, encoding='utf8')) #翻译的过程
- #server-side
- recv_data = s.recv(1024) #翻译的过程
- recv_data = str(recv_data,encoding='utf8')
翻译的过程
- # client-side
- 第四点:学生上课的过程中会不断的提问题和回答问题,老师也有可能提问题,这需要考虑你的情景, 想由谁开始先发消息 conn.send( )/ s.send( )
问题:send( ) 和 sendall( ) 方法有什么区别,试说明? - 第五点:问与答在一个课堂中是一个不断的循环过程 while True:
- 问题:为什么按一下回车之后程序就会卡住,试说明?
因为把 send_data调用了一个strip( )函数,所以空格没有了,变成了空值,然后下一步把空的东西发到服务端,从客户端发空消息是不会阻塞的,但服务端就卡在 conn.recv(1024) 这一步,socket 编程当中,除了accept( )会阻塞,recv( )也是会阻塞,客户端发空消息对服务端来说这相当于没有接收消息,conn.recv(1024) 变成一个在等待着接受消息的状态,形成阻塞的现象。 - 问题:为什么客户端意外地终止了程序之后,服务器端会不断地收到空消息值?
首先要知道 s.accept( ) 和s.recv( ) 是会阻塞的,意思说它们等待消息,但这是基于连接正常的情况下,因为客户端意外地终止了程序,它们之间的连接崩了,连接经崩了 s.recv( ) 就不能阻塞,在没有阻塞的情况下进入了死循环,所以就不断的打印空值。
网络编程中的 "粘包"
socket根本不负责数据真实的传输,数据真实的传输还是靠协义去做的,每个程序在接收的时候都设置了一个接收上限,e.g. conn.recv(1024),这里的 1024 就是每次與服务端沟通时接收消息的上限,客户端每次与服务端的循环过程中只接收1024字节的数据,导致进入下一个循环的时候,依旧在打印之前接收了但还没有打印成功的数据,这就是粘包问题。
粘包的解决办法是什么
有人会说增加了接收消息的字节上限,不就是简单地把问题解决了吗?有些现象可能会让你觉得问题已经解决,但实际上是没有的,因为协议在传送数据时总有一个上限。为什么程序在接收数据完毕后出现卡住的情况? 这是因为客户端不知道服务器端需要发送多少数据,阻塞在一个接受消息的阶段。粘包的解决方法是:
- 服务器端在真正发送数据之前,把这次需要发送数据的长度,先发给客户端 e.g. Ready|4096;
- #服务器端
- ready_tag = 'Ready|%s' %len(send_data) #Ready|4096
- conn.send(bytes(ready_tag, encoding='utf8')) # 发送数据长度
- #客户端
- ready_tag = s.recv(1024) # 获取数据长度的字节 Ready|4096
- ready_tag = str(ready_tag,encoding='utf8')
- if ready_tag.startswith('Ready'): #Ready|4096
- msg_size=int(ready_tag.split('|')[-1]) # 获取待接收数据
ReadyTag
- #服务器端
- 此时,客户端可以回复服务器端:你现在可以发消息啦,我已经准备好接收消息 - Started
- 当服务器端發送了一個大於1024的消息時,客户端就可以循环接收由服务端发送的消息,如果接收到的数据和服务器端发送的数据长度是一样的话,表示已经收完了,在这个逻辑的前提下,接收多少数据也不会出现粘包问题。
自己动手写一个 ssh 交互,优雅地解决了粘包问题。
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- import subprocess
- ip_port = ('127.0.0.1',9998) # 定义元组
- s = socket.socket() # 绑定协义,生成套接字
- s.bind(ip_port) # 绑定 IP 端口,用来唯一标视一个进程, ip_port 必需是元组格式
- s.listen(5) # 定义最大可以挂起的连接数
- while True: # 用来重复接收新的连接
- conn, addr = s.accept() # 接受客户端的连接请求,返还 conn (相当于一个特定的连接), addr 是客户端的 ip + port
- while True: # 用来基于一个连接重复收发消息
- try: # 捕捉客户端的异常关闭
- recv_data = conn.recv(1024) # 收消息,阻塞
- if len(recv_data) == 0: break # 客户端如果退出了,服务端将收到空消息,退出
- p = subprocess.Popen(str(recv_data, encoding='utf8'), shell=True, stdout=subprocess.PIPE) # 执行系统命令
- res = p.stdout.read()
- if len(res) == '': # 执行错误命令,标准输出为空
- send_data = 'cmd err'
- else:
- send_data = str(res, encoding='gbk') # 命令执行 ok, 字节 gbk --> str --> 字节 uft8
- send_data=bytes(send_data,encoding='utf8')
- # 为了解决粘包问题
- ready_tag = 'Ready|%s' %len(send_data) # 生成
- conn.send(bytes(ready_tag, encoding='utf8')) # 发送数据长度
- feedback = conn.recv(1024) # Started 接收确认信息
- feedback = str(feedback,encoding='utf8')
- if feedback.startswith('Started'):
- conn.send(send_data) # 发送命令的执行结果
- except Exception:
- break
- conn.close()
socket服务端ssh交互(粘包解决代码)
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- import subprocess #执行命令模块
- ip_port = ('127.0.0.1',9998) #定义元组
- s = socket.socket() # 绑定协义,生成套接字
- s.connect(ip_port) # 连接服务端,如果服务端已经有一个连接的话,就立即挂起
- while True: #基于 s.connect() 建立的连接来循环发消息
- send_data = input(">>: ").strip()
- if send_data == 'exit': break
- if len(send_data) == 0: continue
- s.send(bytes(send_data, encoding='utf8'))
- #为了解决粘包问题
- ready_tag = s.recv(1024) # 获取数据长度的字节 Ready|9998
- ready_tag = str(ready_tag,encoding='utf8')
- if ready_tag.startswith('Ready'): #Ready|9998
- msg_size=int(ready_tag.split('|')[-1]) # 获取待接收数据
- start_tag = 'Started' # 发送确认信息
- s.send(bytes(start_tag,encoding='utf8'))
- # 基于已经收到的待接收数据长度,循环接收消息
- recv_size = 0
- recv_msg=b''
- while recv_size < msg_size:
- recv_data = s.recv(1024)
- recv_msg += recv_data
- recv_size += len(recv_data)
- print("Msg Size %s Recv Size %s" % (msg_size, recv_size))
- print(str(recv_msg, encoding='utf8'))
- s.close()
socket客戶端ssh交互(粘包解决代码)
自己动手写一个 ftp 文件传送
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socketserver
- import json
- class MySocket(socketserver.BaseRequestHandler):
- def handle(self):
- print(self.request,self.client_address,self.server)
- conn = self.request
- send_data = "Hello...May I help you?"
- conn.sendall(bytes(send_data,encoding='utf-8'))
- while True: # 用来基于一个连接重复收发消息
- try: # 捕捉客户端的异常关闭
- recv_data = conn.recv(1024) # 收消息,阻塞
- if len(recv_data) == 0: break # 客户端如果退出了,服务端将收到空消息,退出
- print('{} says: {}'.format(self.client_address, recv_data.decode()))
- task_data = json.loads(recv_data.decode())
- task_action = task_data.get('action')
- if hasattr(self, "task_%s" %task_action):
- func = getattr(self,"task_%s" %task_action)
- func(task_data)
- else:
- print("task action is not supported", task_action)
- except Exception:
- break
- def task_put(self,*args, **kwargs):
- print('put',args,kwargs)
- file_name = args[0].get('filename')
- file_size = args[0].get('filesize')
- # response to the client side
- server_response = {'status':200}
- self.request.send(bytes(json.dumps(server_response),encoding='utf-8'))
- f = open(file_name,'wb')
- recv_size = 0
- while recv_size < file_size:
- recv_data = self.request.recv(4096)
- f.write(recv_data)
- recv_size += len(recv_data)
- print("filesize: %s recv_size: %s" %(file_size,recv_size))
- print("file recv success")
- if __name__=='__main__':
- ip_port = ('127.0.0.1',8088)
- server = socketserver.ThreadingTCPServer(ip_port,MySocket)
- server.serve_forever()
socket服务端-ftp例子
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # Author: Janice Cheng
- import socket
- import os
- import json
- ip_port = ('127.0.0.1',8088)
- s = socket.socket()
- s.connect(ip_port)
- welcome_msg = s.recv(1024)
- print("from server:",welcome_msg.decode())
- while True:
- send_data = input(">>: ").strip()
- if len(send_data) == 0: continue
- cmd_list = send_data.split()
- if len(cmd_list) < 2: continue
- task_type = cmd_list[0]
- if task_type == 'put':
- abs_filepath = cmd_list[1]
- if os.path.isfile(abs_filepath):
- file_size = os.stat(abs_filepath).st_size
- file_name = abs_filepath.split("/")[-1]
- print("file: %s size: %s" %(abs_filepath,file_size))
- msg_data = {"action":"put","filename":file_name,"filesize":file_size,"md5":''}
- s.send(bytes(json.dumps(msg_data),encoding='utf-8'))
- # send a Ready msg to the server
- server_confirmation_msg = s.recv(1024)
- confirm_data = json.loads(server_confirmation_msg.decode())
- if confirm_data['status'] == 200:
- print("Started sending filename:", file_name)
- f = open(abs_filepath, 'rb')
- for line in f:
- s.send(line)
- print("Send file done")
- else:
- print("File does not exists")
- continue
- else:
- print("doesn't support taskt type",task_type)
- continue
- # use getattr method
socket客戶端-ftp例子
自定义 MySocket 类
- 自己创建一个 Socket类
- 继承一个 socketserver.BaseRequestHandler的类
- 覆盖 handle 方法
- 创建一个 socketserver 的对象,这是创建 socketserver.ThreadingTCPServer 的对象
- 把自己定义的 Socket 类传入 socketserver.ThreadingTCPServer 类中
每当服务器收到一个请求(来自客户端的连接时),就会实例化一个请求处理程序,并在实例化时调用了它的__init__(self)方法,这个方法会调用self.setup( )、 self.hanlde( )和self.finally( )。所以当我们在自定义类中只有覆盖 handle 方法,它就会自动在socketserver.ThreadingTCPServer 创建时执行它。
- class BaseRequestHandler:
- """Base class for request handler classes.
- This class is instantiated for each request to be handled. The
- constructor sets the instance variables request, client_address
- and server, and then calls the handle() method. To implement a
- specific service, all you need to do is to derive a class which
- defines a handle() method.
- The handle() method can find the request as self.request, the
- client address as self.client_address, and the server (in case it
- needs access to per-server information) as self.server. Since a
- separate instance is created for each request, the handle() method
- can define arbitrary other instance variariables.
- """
- def __init__(self, request, client_address, server):
- self.request = request
- self.client_address = client_address
- self.server = server
- self.setup()
- try:
- self.handle()
- finally:
- self.finish()
- def setup(self):
- pass
- def handle(self):
- pass
- def finish(self):
- pass
socketserver.BaseRequestHandler源码
本周作业
作业:开发一个支持多用户在线的FTP程序
要求:
- 用户加密认证
- 允许同时多用户登录
- 每个用户有自己的家目录 ,且只能访问自己的家目录
- 对用户进行磁盘配额,每个用户的可用空间不同
- 允许用户在ftp server上随意切换目录
- 允许用户查看当前目录下文件
- 允许上传和下载文件,保证文件一致性
- 文件传输过程中显示进度条
- 附加功能:支持文件的断点续传
运行的知识点:
- socket 发送字符串
- socket 发送文件
- 客户端:1) 文件大小;2) 发消息
- 服务端:1) 接收消息 (文件大小)
- 客户端:
- 发消息
- 服务端:把数据存储成 Json 然后从客户端发消息到服务端
- 用户验证;
- 接受subprocess (默应 win>gbk 编码);
- 接收消息 (文件大小);
- 发消息;
- 断点输传:
- 5.1) a 追加、w 清空写;
- 5.2) 文件指針;
服务端
客户端
參考資料
银角大王:Python之路【第六篇】:socket
金角大王:Python之路,Day8 - Socket网络编程
第九章:Python の 网络编程基础(一)的更多相关文章
- Python网络编程基础|百度网盘免费下载|零基础入门学习资料
百度网盘免费下载:Python网络编程基础|零基础学习资料 提取码:k7a1 目录: 第1部分 底层网络 第1章 客户/服务器网络介绍 第2章 网络客户端 第3章 网络服务器 第4章 域名系统 第5章 ...
- Python网络编程基础pdf
Python网络编程基础(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1VGwGtMSZbE0bSZe-MBl6qA 提取码:mert 复制这段内容后打开百度网盘手 ...
- python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)
python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程 并行与并发 同步与异步 阻塞与非阻塞 CPU密集型与IO密集型 线程与进程 进 ...
- 第5章 Linux网络编程基础
第5章 Linux网络编程基础 5.1 socket地址与API 一.理解字节序 主机字节序一般为小端字节序.网络字节序一般为大端字节序.当格式化的数据在两台使用了不同字节序的主机之间直接传递时,接收 ...
- 好书推荐---Python网络编程基础
Python网络编程基础详细的介绍了网络编程的相关知识,结合python,看起来觉得很顺畅!!!
- Python网络编程基础 PDF 完整超清版|网盘链接内附提取码下载|
点此获取下载地址提取码:y9u5 Python网络编程最好新手入门书籍!175个详细案例,事实胜于雄辩,Sockets.DNS.Web Service.FTP.Email.SMTP.POP.IMAP. ...
- python网络编程基础
一.客户端/服务器架构 网络中到处都应有了C/S架构,我们学习socket就是为了完成C/S架构的开发. 二.scoket与网络协议 如果想要实现网络通信我们需要对tcpip,http等很多网络知识有 ...
- 第十一章:Python の 网络编程基础(三)
本課主題 多线程的创建和使用 消息队列的介绍 Python 操作 memached 和 redis 实战 本周作业 消息队列的介绍 对列是在内存中创建的,如果整个进程里的程序运行完毕之后会被清空,消息 ...
- 《Python网络编程基础》第四章 域名系统
域名系统(DNS) 是一个分布式的数据库,它主要用来把主机名转换成IP地址.DNS以及相关系统之所以存在,主要有以下两个原因: 它们可以使人们比较容易地记住名字,如www.baidu.com. 它 ...
随机推荐
- Libcurl的编译_HTTP/HTTPSclient源代码演示样例
HTTP/HTTPSclient源代码演示样例 环境: zlib-1.2.8 openssl-1.0.1g curl-7.36 Author: Kagula LastUpdateDate: 2 ...
- 使用asyncsocket群聊
#import "ViewController.h" #import "AsyncSocket.h" @interface ViewController ()& ...
- hdu4893 Wow! Such Sequence!
线段树结点上保存一个一般的sum值,再同一时候保存一个fbsum,表示这个结点表示的一段数字若为斐波那契数时的和 当进行3操作时,仅仅用将sum = fbsum就可以 其它操作照常进行,仅仅是单点更新 ...
- Docker + Jenkins 持续部署 ASP.NET Core 项目
Docker 是个好东西,特别是用它来部署 ASP.NET Core Web 项目的时候,但是仅仅的让程序运行起来远远不能满足我的需求,如果能够像 DaoCloud 提供的持续集成服务那样,检测 gi ...
- Docker-py 的使用
Docker SDK for Python A Python library for the Docker Engine API 具体文档这里,https://docker-py.readthedoc ...
- java String,StringBuffer和StringBulder学习笔记
1.String:不可改变的Unicode字符序列. 池化思想,把需要共享的数据放在池中,用一个存储区域来存放一些公用资源以减少存储空间的开销. 在String类中,以字面值创建时,回到java方法空 ...
- 青否云 - 小程序待办事项 jquery开源系统
青否云最新开源系统:小程序待办事项 jquery-demo 青否云 Jquery demo 下载地址:https://github.com/qingful/jquery-demo 官网 http:// ...
- Oracle安装步骤
1.在Oracle官网下载安装包: 2.非常重要:两个压缩包都要解压(不是分卷压缩的,不然安装过程中会报找不到文件的错误,被坑过!): 3.关闭所有安全相关软件(关闭杀毒软件.防火墙.windows ...
- iOS 使用 CATransform3D 处理 3D 影像、制做互动立体旋转的效果
1. Swift http://www.cocoachina.com/swift/20170518/19305.html domehttps://pan.baidu.com/s/1i4XXSkH OC ...
- ecshop中的$user对象
ecshop的程序中,有个对象:$user,它是用来处理用户信息的.比如登录.注册,还有就是用来和第三方管理通讯和共享资源的.在user.php中,有一条$user->login($userna ...