1、 动态导入模块
第一种方法(python解释器自己内部用的):
上图是我程序的目录结构
下面代码是动态导入模块3.py的源码:
#AUTHOR:FAN
lib_dir = __import__('lib.aa')
print(lib)
obj = lib.aa.C()
print(obj.name)
lib****包目录下aa.py的源码如下:
#AUTHOR:FAN
class C:
def __init__(self):
self.name = "dean"
这个时候运行动态导入模块.py程序,运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/动态导入模块3.py
<module 'lib' from 'D:\\python培训\\s14\\day8\\lib\\__init__.py'>
dean
Process finished with exit code 0
对上述动态导入模块3.py代码进行分析:
lib_dir = __import__(‘lib.aa’)
print(lib)
打印出的内容是:
从打印结果可以看出__import__(‘lib.aa’)这个得到的就是lib目录
所以这个时候想要调用lib目录下aa中类中的数据直接:
obj = lib.aa.C()
print(obj.name)
这样就取得了aa.py程序中C类中初始化参数的name的值
第二种方法(官方建议):
程序目录如下:
动态导入模块4.py的代码如下:
#AUTHOR:FAN
import importlib
aa = importlib.import_module('lib.aa')
print(aa)
obj = aa.C()
print(obj.name)
lib****目录下的aa.py不变化还是上述相同
这个时候运行动态导入模块4.py,运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/动态导入模块4.py
<module 'lib.aa' from 'D:\\python培训\\s14\\day8\\lib\\aa.py'>
dean
Process finished with exit code 0
对上述代码进行分析:
aa = importlib.import_module(‘lib.aa’)
print(aa)
打印出的内容如下:
可以看出打印出的是lib.aa,所以这个时候可以直接实例化aa.C,并取得类中初始化参数中的name的值:
obj = aa.C()
print(obj.name)
通过上述两种方法也可以得出,两者虽然最终结果是相同的,但是过程中还是有区别的:
第一种方法:lib_dir = __import__(‘lib.aa’),这种方法得到的module是lib
第二种方法:
aa = importlib.import_module(‘lib.aa’)****,这种方法得到的module是lib.aa
2、断言assert
先看如下代码:
#AUTHOR:FAN
name = "dean"
assert type(name) is str
print(name)
运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/断言.py
dean
Process finished with exit code 0
断言其实就是对值进行判断,看是否满足条件,如果满足就向下执行,如果不满足就报错,我们将代码进行更改,再次运行:
#AUTHOR:FAN
name = 123
assert type(name) is str
print(name)
运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/断言.py
Traceback (most recent call last):
File "D:/python培训/s14/day8/断言.py", line 5, in <module>
assert type(name) is str
AssertionError
Process finished with exit code 1
3、Socket
socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过“套接字”向网络发出请求或者应答网络请求
下面是一些功能:
我们再通常使用socket的时候都需要先导入socket模块
即import socket,然后实例化socket,例如:
sk = socket(socket.AF_INET,socket.SOCK_STREAM,0)
参数一:地址簇
在这个参数中包含以下几个参数:
socket.AF_INET 表示IPV4(默认)
socket.AF_INET6 表示IPV6
socket.AF_UNIX 只能用于单一的Unix系统进程间的通信
参数二:类型
socket.SOCK_STREAM 流式socket for TCP(默认)
socket.SOCK_DGRAM 数据格式socket,for UDP
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP,IGMP等网络报文,可以通过IP_HDRINCL套接字选项由用户构造IP头
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序,SOCK_RDM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文,SOCK_RAM通常仅限于高级用户或管理员运行的程序使用
socket.SOCK_SEQPACKET 可靠的连续数据包服务
参数三:协议
默认与特定地址家族相关的协议,如果是0 则系统就会根据地址格式和套接类别,自动选择一个合适的协议
sk.bind((ip****地址,port端口))
这种是在默认的AF_INET下这样以元组的形式存在即(ip,port)
sk.listen(backlog)
开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量
backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
这个值不能无限大,因为需要在内核中维护连接队列
sk.setblocking(bool)
是否阻塞(默认为True),如果设置False,那么accept和recv时一旦无数据,就会报错
sk.accept()
接收连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据,address是连接客户端的地址
接收TCP客户端的连接(阻塞)等待连接的到来
sk.connect(address)
连接到address处的套接字,一般,address的格式为元组(hostname,port),如果连接出错,返回socket,error错误
sk.connect_ex(address)
同上的sk.connect只不过会有返回值,连接成功时返回0,连接失败时返回编码
sk.close()
关闭套接字
sk.recv(bufsize[,flag])
接收套接字的数据,数据以字符串形式返回,bufsize指定最多可以接收的数量,flag提供有关消息的其他信息,通常可以忽略
sk.recvfrom(bufsize[.flag])
与recv()类似,但返回值是(data,address)其中data是包含接收数据的字符串,address是发送数据的套接字地址
sock.send(string[,flag])
将string中的数据发送到连接的套接字,返回值是要发送的字节数量,该数量可能小于string的字节大小,即:可能未将指定内容全部发送
sk.sendall(string[,flag])
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有的数据,成功返回None,失败则抛出异常
内部通过递归调用send,将所有内容发送出去
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ip地址,port)的元组,指定远程地址,返回值时发送的字节数,该函数主要用于UDP协议
sk.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,但是为秒
值为None表示没有超时期,一般超时期应该在刚创建套接字时设置,因为他们可能用于连接的操作
sk.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.fileno()
套接字的文件描述符
用socket写一个简单的类似ssh的工具:
服务端:
#AUTHOR:FAN
import socket,os
server = socket.socket()
server.bind(('127.0.0.1',9999))
server.listen()
while True:
conn,addr = server.accept()
print("一个新的连接:",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已经断开")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode()).read()
print("send before")
if len(cmd_res) == 0:
cmd_res = "cmd has no output......"
conn.send(cmd_res.encode())
print("send done")
server.close()
客户端:
#AUTHOR:FAN
import socket,os
client = socket.socket()
client.connect(('127.0.0.1',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode("utf-8"))
cmd_res = client.recv(1024)
print(cmd_res.decode())
client.close()
先启动服务端,在启动客户端,并在客户端执行ipconfig命令(先在windows上测试),运行结果如下:
客户端运行后结果显示:
D:\python35\python.exe D:/python培训/s14/day8/sock_ssh_client.py
>>:ipconfig
Windows IP 配置
以太网适配器 Bluetooth 网络连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
本地链接 IPv6 地址. . . . . . . . : fe80::85b7:6c65:f032:d29%12
IPv4 地址 . . . . . . . . . . . . : 192.168.1.102
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.1.1
以太网适配器 VMware Network Adapter VMnet1:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::9c7d:99a2:b09b:fa49%15
IPv4 地址 . . . . . . . . . . . . : 10.0.10.22
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 VMware Network Adapter
>>:dir
VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::4d7:4817:776d:6386%16
IPv4 地址 . . . . . . . . . . . . : 172.16.1.200
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
隧道适配器 isatap.DHCP HOST:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
隧道适配器 Teredo Tunneling Pseudo-Interface:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 isatap.{AEC4AF99-6E8C-4696-B0CE-1044479986E4}:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 isatap.{E8300FF5-2E5A-4E63-8F58-33282553726B}:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
>>:
服务端显示:
D:\python35\python.exe D:/python培训/s14/day8/sock_ssh_server.py
一个新的连接: ('127.0.0.1', 62969)
等待新指令
执行指令: b'ipconfig'
send before
send done
等待新指令
执行指令: b'dir'
send before
send done
等待新指令
从上面我们可以看出,当我们执行ipconfig命令的时候,显示的内容并不全,当再次执行dir命令的时候刚才没有显示的部分,从这里也可以看出,ipconfig第一次没有显示的内容被放在了缓冲区里,当dir执行的时候,先将缓冲区的内容显示,而这个时候dir命令显示的内容又被存在了缓冲区中………
所以需要解决上述问题:
解决的思路就是在发送数据之前需要将要发送文件的大小先发送过去,这样客户端根据收到数据的大小和这个文件的大小比较,直到收完为止(切记一个问题,整数不能直接encode()必须转换成字符串)
服务端代码如下:
#AUTHOR:FAN
import socket,os
server = socket.socket()
server.bind(('127.0.0.1',9999))
server.listen()
while True:
conn,addr = server.accept()
print("一个新的连接:",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已经断开")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode()).read()
print("send before")
if len(cmd_res) == 0:
cmd_res = "cmd has no output......"
conn.send(str(len(cmd_res)).encode())
conn.send(cmd_res.encode())
print("send done")
server.close()
客户端代码如下:
#AUTHOR:FAN
import socket,os
client = socket.socket()
client.connect(('127.0.0.1',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode("utf-8"))
cmd_res_size = client.recv(1024)
print("命令结果大小:",cmd_res_size)
#received_data = b''
received_data_size = 0
while received_data_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_data_size+= len(data)
#print(data.decode())
#cmd_res = client.recv(1024)
print(received_data_size)
# print(cmd_res.decode())
else:
print("cmd res receive done")
client.close()
这个时候现将收到命令结果给注释,先打印收到服务端发送命令数据的大小,已经自己收到的数据累加的大小
同样先启动服务端再启动客户端
运行结果如下:(下面是客户端的测试结果)
D:\python35\python.exe D:/python培训/s14/day8/sock_ssh_client.py
>>:ipconfig
命令结果大小: b'1494'
1024
1960
cmd res receive done
>>:dir
命令结果大小: b'734'
852
cmd res receive done
>>:ipconfig /all
命令结果大小: b'4660'
1024
2048
3072
4096
5120
cmd res receive done
>>
从户可以看出,服务端发送给客户端命令结果的大小和客户端实际通过多次收到数据的总和不匹配,出现了客户端收到的数据大小大于服务器端发送过来的大小。
将服务端代码放到linux系统上执行,在windows运行客户端,运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/sock_ssh_client.py
>>:pwd
命令结果大小: b'11'
11
cmd res receive done
>>:ifconfig
命令结果大小: b'902'
902
cmd res receive done
>>:
从这里可以看出是相同的了,并且判断出是因为在windows下有中文,同时在服务端发送的时候,有点问题,服务端代码的问题是下面部分:
conn.send(str(len(cmd_res)).encode())
这个发送的时候应该先改为:
conn.send(str(len(cmd_res.encode())).encode())
这样就解决了汉字发送后大小不匹配的问题
这样重新在windows上测试服务端程序和客户端程序:
更改之后客户端的运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/sock_ssh_client.py
>>:ipconfig
命令结果大小: b'1960'
1024
1960
cmd res receive done
>>:
这样就完美的解决了之前的问题
这样重新将服务端整理好的代码如下:
服务端代码如下:
#AUTHOR:FAN
import socket,os
server = socket.socket()
server.bind(('127.0.0.1',9999))
server.listen()
while True:
conn,addr = server.accept()
print("一个新的连接:",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已经断开")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode()).read()
print("send before")
if len(cmd_res) == 0:
cmd_res = "cmd has no output......"
conn.send(str(len(cmd_res.encode())).encode())
conn.send(cmd_res.encode())
print("send done")
server.close()
客户端代码如下:
#AUTHOR:FAN
import socket,os
client = socket.socket()
client.connect(('127.0.0.1',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode("utf-8"))
cmd_res_size = client.recv(1024)
print("命令结果大小:",cmd_res_size)
received_data = b''
received_data_size = 0
while received_data_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_data_size+= len(data)这里用len判断长度是因为服务器每次发来的不一定是1024,这个问题一定要注意
print(received_data_size)
received_data+=data
else:
print("cmd res receive done")
print(received_data.decode())
client.close()
客户端运行结果如下:
D:\python35\python.exe D:/python培训/s14/day8/sock_ssh_client.py
>>:dir
命令结果大小: b'852'
852
cmd res receive done
驱动器 D 中的卷是 新加卷
卷的序列号是 7095-8443
D:\python培训\s14\day8 的目录
2016/09/12 23:25 <DIR> .
2016/09/12 23:25 <DIR> ..
2016/09/11 16:54 <DIR> cc
2016/05/12 17:25 181,238,643 jdk8.tar.gz
2016/09/10 10:00 <DIR> lib
2016/09/12 23:25 681 sock_ssh_client.py
2016/09/12 23:22 608 sock_ssh_server.py
2016/09/10 09:34 11 __init__.py
2016/09/10 10:26 292 动态导入模块.py
2016/09/10 10:06 88 动态导入模块2.py
2016/09/11 16:12 102 动态导入模块3.py
2016/09/11 16:23 113 动态导入模块4.py
2016/09/11 16:45 66 断言.py
9 个文件 181,240,604 字节
4 个目录 181,201,088,512 可用字节
>>:ipconfig
命令结果大小: b'1960'
1024
1960
cmd res receive done
Windows IP 配置
以太网适配器 Bluetooth 网络连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 本地连接:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 无线网络连接:
连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
本地链接 IPv6 地址. . . . . . . . : fe80::85b7:6c65:f032:d29%12
IPv4 地址 . . . . . . . . . . . . : 192.168.1.103
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.1.1
以太网适配器 VMware Network Adapter VMnet1:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::9c7d:99a2:b09b:fa49%15
IPv4 地址 . . . . . . . . . . . . : 10.0.10.22
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::4d7:4817:776d:6386%16
IPv4 地址 . . . . . . . . . . . . : 172.16.1.200
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
隧道适配器 isatap.DHCP HOST:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
隧道适配器 Teredo Tunneling Pseudo-Interface:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 isatap.{AEC4AF99-6E8C-4696-B0CE-1044479986E4}:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
隧道适配器 isatap.{E8300FF5-2E5A-4E63-8F58-33282553726B}:
媒体状态 . . . . . . . . . . . . : 媒体已断开
连接特定的 DNS 后缀 . . . . . . . :
>>:
这个时候命令结果显示正常,并且结果大小也显示正常
但是当把程序放到linux上执行时发现出现问题了,客户端提示错误如下,并且提示错误后程序退出,服务端提示客户端断开连接:
>>:ifconfig
命令结果大小: b'906ens33 Link encap:Ethernet HWaddr 00:0c:29:96:2f:bc \n inet addr:192.168.1.105 Bcast:192.168.1.255 Mask:255.255.255.0\n inet6 addr: fe80::b011:2300:ecc8:c70d/64 Scope:Link\n UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1\n RX packets:814292 errors:0 dropped:0 overruns:0 frame:0\n TX packets:65371 errors:0 dropped:0 overruns:0 carrier:0\n collisions:0 txqueuelen:1000 \n RX bytes:923448604 (923.4 MB) TX bytes:4865728 (4.8 MB)\n\nlo Link encap:Local Loopback \n inet addr:127.0.0.1 Mask:255.0.0.0\n inet6 addr: ::1/128 Scope:Host\n UP LOOPBACK RUNNING MTU:65536 Metric:1\n RX packets:1444 errors:0 dropped:0 overruns:0 frame:0\n TX packets:1444 errors:0 dropped:0 overruns:0 carrier:0\n collisions:0 txqueuelen:1 \n RX bytes:115764 (115.7 KB) TX bytes:115764 (115.7 KB)\n\n'
Traceback (most recent call last):
File "socket_ssh_client.py", line 16, in <module>
while received_data_size < int(cmd_res_size.decode()):
ValueError: invalid literal for int() with base 10: '906ens33 Link encap:Ethernet HWaddr 00:0c:29:96:2f:bc \n inet addr:192.168.1.105 Bcast:192.168.1.255 Mask:255.255.255.0\n inet6 addr: fe80::b011:2300:ecc8:c70d/64 Scope:Link
root@python:~/0912#
出现上述错误的原因是因为在服务端的两行代码:
conn.send(str(len(cmd_res.encode())).encode())
conn.send(cmd_res.encode())
这两句代码连着发导致了粘包:
两次连着send缓冲区会将两次的数据合并为一条发送给客户端从而导致数据粘在一起
解决方法:
服务端代码:
#AUTHOR:FAN
import socket,os
server = socket.socket()
server.bind(('127.0.0.1',9999))
server.listen()
while True:
conn,addr = server.accept()
print("一个新的连接:",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已经断开")
break
print("执行指令:",data)
cmd_res = os.popen(data.decode()).read()
print("send before")
if len(cmd_res) == 0:
cmd_res = "cmd has no output......"
conn.send(str(len(cmd_res.encode())).encode())
client_ack = conn.recv(1024)
print("来自客户端的确认:",client_ack.decode())
conn.send(cmd_res.encode())
print("send done")
server.close()
客户端:
#AUTHOR:FAN
import socket,os
client = socket.socket()
client.connect(('127.0.0.1',9999))
#client.connect(('192.168.1.105',9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0:continue
client.send(cmd.encode("utf-8"))
cmd_res_size = client.recv(1024)
client.send("准备好接收数据了".encode())
print("命令结果大小:",cmd_res_size)
received_data = b''
received_data_size = 0
while received_data_size < int(cmd_res_size.decode()):
data = client.recv(1024)
received_data_size+= len(data)
print(received_data_size)
received_data+=data
else:
print("cmd res receive done")
print(received_data.decode())
client.close()
这样就完美的解决了粘包的问题
4、 SocketServer模块
SocketServer内部使用IO多路复用以及“多线程”和“多进程”,从而实现并发处理多个客户端请求的Socket服务端,即:每个客户端请求连接到服务器时,Socket服务端都会在服务器上创建一个线程或进程专门负责处理当前客户端的所有请求
关于SocketServer的使用:
a. 必须自己创建一个请求处理类,并且这个类要继承BaseRequestHandler并且还要重写父类里的handle()
b. 必须实例化TCPServer,并且传递server ip和上面创建的请求处理类给这个TCPServer
c. server.handle_request()只处理一个请求
server.server_forever()处理多个请求,永远执行
d. 最后关闭socket
下面是一个socketserver的一个例子:
服务端:
#AUTHOR:FAN
import socketserver
#对应上面所说的自己创建一个请求处理类并继承BaseRequestHandler
class MyTCPHandler(socketserver.BaseRequestHandler):
#对应上面的重写父类里的handle(),切记所有的交互都在handle()里面切记
def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
self.request.sendall(self.data.upper())
except ConnectionResetError as e:
print(e)
break
if __name__ == "__main__":
HOST,PORT = '127.0.0.1',9999
#这里对应上面的实例化TCPServer,并传递server ip和上面创建的请求处理类,也就是MyTCPHandler
server = socketserver.TCPServer((HOST,PORT),MyTCPHandler)
server.serve_forever()