一丶套接字(socket)
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
基于UDP协议的socket
server端:
import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字 udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 对话(接收与发送) udp_sk.close() # 关闭服务器套接字
client端:
import socket ip_port=('127.0.0.1',9000) udp_sk=socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr=udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr)
二丶粘包
在学习粘包之前我们先学几个新模块:
struct模块:
1、 struct.pack
struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。其函数原型为:struct.pack(fmt, v1, v2, ...),参数fmt是格式字符串,关于格式字符串的相关信息在下面有所介绍。v1, v2, ...表示要转换的python值。
2、 struct.unpack
struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组。
import struct a = 20 b = 400 s = struct.pack('ii', a, b) print(s, type(s)) #输出:b'\x14\x00\x00\x00\x90\x01\x00\x00' <class 'bytes'> print('length: ', len(s)) #输出:length: 8 s2 = struct.unpack('ii', s) print(s2) #输出:(20, 400) s2 = struct.unpack('ii', s) #报错:unpack requires a buffer of 4 bytes #==>解压需要一个4字节的缓冲区,也就是说'ii'表示8个字节的缓冲
#格式符"i"表示转换为int,'ii'表示有两个int变量。
#进行转换后的结果长度为8个字节(int类型占用4个字节,两个int为8个字节)
可以使用python的内置函数repr来获取可识别的字符串,其中十六进制的0x00000014, 0x00001009分别表示20和400。
subprocess模块:
这个模块用的不多,只是用于执行复杂的系统命令而已..我们不必深究.
让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)
res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码 且只能从管道里读一次结果 注意
同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是粘包。
那粘包的成因是什么呢?
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。
如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
那我们如何解决粘包呢?
1.我们可以用time模块,为两个文件之间设置一定的时间间隔,让两个不会粘结在一起,这个方法可行,但是会降低程序运行效率,而且很蠢.
2.我们上面讲过struct模块,我们可以用struct模块把报头的四个字节取出来,再解包获得文件的大小,通过解包出来的解包大小来读取数据.
服务端:
import socket import subprocess server = socket.socket() server.bind(('127.0.0.1',8008)) server.listen(5) while True: print("server is working.....") conn,addr = server.accept() #建立链接 # 字节类型 while True: # 针对window系统 try: cmd = conn.recv(1024).decode("utf8") # 阻塞 if cmd == b'exit': #判断与客户端链接是否结束 break res=subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) # print("stdout",res.stdout.read()) # print("stderr",res.stderr.read().decode("gbk")) out=res.stdout.read() err=res.stderr.read() print("out响应长度",len(out)) #输出出来的长度 print("err响应长度",len(err)) #错误的长度 if err: import struct header_pack = struct.pack("i", len(err)) #压包 conn.send(header_pack) #发送 conn.send(err) #发送错误信息 else: #构建报头 import struct header_pack=struct.pack("i",len(out)) #压包 print("header_pack",header_pack) #压包信息 # # 发送报头 conn.send(str(len(out)).encode("utf8")) #发送压包 # 发送数据 conn.send(out) #发送输出出来的数据 except Exception as e: #错误信息处理 break conn.close()
客户端:
import socket import struct sk = socket.socket() sk.connect(('127.0.0.1',8008)) while 1: cmd = input("请输入命令:") sk.send(cmd.encode('utf-8')) # 字节 #发送输出的指令 if cmd=="": continue if cmd == 'exit': #判断是否退出 break data_length=int(sk.recv(1024).decode("utf8")) #接收到的信息 print("data_length",data_length) #打印出来 recv_data_length=0 #接收到的长度 recv_data=b"" while recv_data_length<data_length: #如果接收到的长度小于设定的数据长度,则全输出出来,否则直接输出最大长度 data=sk.recv(1024) #阻塞 设定最大输出长度 recv_data_length+=len(data) #计算数据长度 recv_data+=data print(recv_data.decode("gbk")) sk.close()
3.还可以通过客户端发一个数据,服务器回一个数据,表示已经收到,然后再发送数据,这样就不会导致粘包了.
服务端:
import socket import json sock = socket.socket() sock.bind(("192.168.13.226",8080)) sock.listen(5) while 1: print("正在等待链接.....") conn,addr = sock.accept() #建立链接 while 1: data = conn.recv(1024).decode("utf8") #接收数据,并解码 file_info = json.loads(data) print("file_info",file_info) #查看反序列化出来的是什么 #文件信息 action = file_info.get("action") filename = file_info.get("filename") filesize = file_info.get("filesize") conn.send(b"200") #告诉客户端,我已准备接收数据 #接收文件数据 with open("put/" + filename,"wb") as f: recv_data_length = 0 while recv_data_length < filesize: data = conn.recv(1024) recv_data_length += len(data) f.write(data) print("总文件大小为%s,已接收%s"% (filesize,recv_data_length)) print("接收成功! ")
客户端:
import socket import os import json sock = socket.socket() sock.connect(("192.168.13.226",8080)) while 1: cmd = input("请输入命令:") #put 111.jpg action,filename = cmd.strip().split(" ") filesize = os.path.getsize(filename) file_info = { "action":action, #命令 "filename":filename, #文件名字 "filesize":filesize #文件大小 } file_info_json = json.dumps(file_info).encode("utf8") #将字典序列化 sock.send(file_info_json) #发送序列化后的字典 #确认服务端接收到了文件信息 code = sock.recv(1024).decode("utf8") if code == "200": #服务端已准备接收文件 #发送文件数据 with open(filename,"rb")as f: for line in f: #把文件一行一行的发过去 sock.send(line) else: print("服务器异常! ")
关于hashlib补充
import hashlib md5=hashlib.md5() md5.update(b"hello") md5.update(b"yuan") print(md5.hexdigest()) print(len(md5.hexdigest())) #helloyuan: d843cc930aa76f7799bba1780f578439 # d843cc930aa76f7799bba1780f578439
综合例题:上传文件
服务端
import struct import socket import json import hashlib sock = socket.socket() sock.bind(("127.0.0.1",8080)) sock.listen(5) while 1: print("正在等待连接.....") conn,addr = sock.accept() while 1: #接收json的打包长度 file_info_length_pack = conn.recv(4) file_info_length = struct.unpack("i",file_info_length_pack)[0] #解包 #接收json字符串 file_info_json = conn.recv(file_info_length).decode("utf8") file_info = json.loads(file_info_json) #反序列化 action = file_info.get("action") filename = file_info.get("filename") filesize = file_info.get("filesize") #循环接收文件 md5 = hashlib.md5() with open("put/"+ filename,"wb") as f: recv_data_length = 0 while recv_data_length < filesize: data = conn.recv(1024) recv_data_length += len(data) f.write(data) #MD5摘要 md5.update(data) #写入要加密的的东西 print("文件总大小:%s,已成功接收%s"% (filesize,recv_data_length)) print("接收成功!") conn.send(b"OK") print(md5.hexdigest()) md5_val = md5.hexdigest() #解密 client_md5 = conn.recv(1024).decode('utf8') if md5_val == client_md5: conn.send(b"203") else: conn.send(b"204")
客户端
import socket import os import json import struct import hashlib sock = socket.socket() sock.connect(("127.0.0.1",8080)) while 1: cmd = input("请输入命令:") #put 111.jpg action,filename = cmd.strip().split(" ") filesize = os.path.getsize(filename) #获取文件大小 file_info = { "action":action, #命令 "filename":filename,#名字 "filesize":filesize,#文件大小 } #将file_info字典先序列化,再编码赋值给file_info_json file_info_json = json.dumps(file_info).encode("utf8") #打包 ret = struct.pack("i",len(file_info_json)) #发送file_info_json的打包长度 sock.send(ret) #发送 file_info_json字节串 sock.send(file_info_json) #发送 文件数据 md5 = hashlib.md5() with open(filename,"rb") as f: for line in f: sock.send(line) md5.update(line) #写入要加密的东西 data = sock.recv(1024) print(md5.hexdigest()) md5_val = md5.hexdigest() #获取密文 sock.send(md5_val.encode("utf8")) is_valid = sock.recv(1024).decode("utf8") if is_valid == "203": print("文件完整!") else: print("文件上传失败!")