python之soket
一、概述 |
1、简介
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
2、代码逻辑图
二、重复发送和处理多个链接 |
2.1重复发送
怎么实现客户端发送多次,服务端接收多次呐?
①客户端
说明:客户端在发送处设置死循环(while True),实现重复发送。
import socket client = socket.socket() client.connect(("localhost",6969)) while True: #进入死循环,设置无限次发送 msg = input(">>>:") client.send(msg.encode()) data = client.recv(1024) print("recv:",data.decode()) client.close()
②服务端
说明:服务端在接收时,设置死循环,实现重复接收。
import socket sever = socket.socket() sever.bind(("127.0.0.1",6969)) sever.listen() conn,address = sever.accept() #接收连接实例 print("电话来了") while True: #设置死循环,接收多次 data = conn.recv(1024) print("recv:",data.decode()) conn.send(data) sever.close()
2.2.处理多个链接
说明:我们在客户端一起链接服务端,我们都知道,一个服务端只能跟一个客户端进行链接通信,那如果说,我这个正在通信的客户端断开跟服务端的通信,那其他的某个客户端就能跟客户端正常通信了,这个实验一定要在Linux服务器上去完成,因为在Windows上就是只要客户端一断开,服务端就断开了。跟上面一样,客户端的代码没有变,我们现在来变一下服务端的代码:
import socket sever = socket.socket() sever.bind(("127.0.0.1",6969)) sever.listen() while True: #在建立连接之前加一个死循环 conn,address = sever.accept() print("电话来了") count = 0 #加一个计数器 while True: data = conn.recv(1024) if not data:break #这边如果接受客户端数据为空,则重新建立连接 print("recv:",data.decode()) conn.send(data) count += 1 sever.close()
三、实现简单的ssh |
我们用过linux的就知道什么是ssh,它是一种客户端和服务端交互返回的一个解决,输入一个命令,给我返回什么,接下来我们说一说,如何用socket去简单的实现一个ssh
3.1客户端
import socket client = socket.socket() client.connect(("localhost",9999)) while True: cmd = input(">>>:").strip() if len(cmd) == 0:continue client.send(cmd.encode("utf-8")) cmd_res = client.recv(500) print(cmd_res.decode("utf-8")) client.close()
3.2服务端
import socket,os server = socket.socket() server.bind(("localhost",9999)) server.listen() while True: conn,addr = server.accept() print("new addr:",addr) while True: data = conn.recv(1024) if not data: print("客户端已断开") break print("执行指令:",data) cmd_res = os.popen(data.decode()).read() print("before send:",len(cmd_res)) if len(cmd_res) == 0: cmd_res = "cmd has no output...." conn.send(cmd_res.encode("utf-8")) print("send done") server.close()
四、socket接收大数据 |
当服务器发送至客户端的数据,大于客户端设置的数据,则就会把数据服务端发过来的数据剩余数据存在IO缓冲区中,那我们如何解决这个问题呢?
有的同学就说了:
- 改大客户端接收的数据的大小=>这个方案并不能解决问题,因为官方建议最多只能接收8k的数据,那服务端发送过来的数据大于8K咋办,很显然不行
- 客户端可以多收几次=>客户端需要收多少次,才能把这个命令返回的结果全部收回来呢?并且怎么确定这条命令返回的结果已经被全部收回来了呢?
很明显,上面第二种思路靠谱一点:就是说服务端给客户端发数据之前,先计算一下给客户端要发多少数据,我先判断 len 一下,就 ok 了,先让客户端知道服务端发送过来的大小,比如说发过来的是5k大小,客户端接收到了这个5k大小以后,就知道需要接收多少次了,循环接收,直到5k数据全部接收完毕为止。
4.1、逻辑图
4.2、逻辑代码
①客户端
import socket client = socket.socket() client.connect(("localhost",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.decode()) recevied_size = 0 #接收客户端发来数据的计算器 recevied_data = b'' #客户端每次发来内容的计数器 while recevied_size < int(cmd_res_size.decode()): #当接收的数据大小 小于 客户端发来的数据 cmd_res = client.recv(1024) recevied_size += len(cmd_res) #每次收到的服务端的数据有可能小于1024,所以必须用len判断 recevied_data += cmd_res else: print(recevied_data.decode("utf-8","ignore")) print("cmd res receive done ....",recevied_size) client.close()
②服务端
import socket,os server = socket.socket() server.bind(("localhost",9999)) server.listen(5) while True: conn,addr = server.accept() print("new addr:",addr) while True: data = conn.recv(1024) if not data: print("客户端已断开") break print("执行指令:",data) cmd_res = os.popen(data.decode()).read() print("before send:",len(cmd_res)) if len(cmd_res) == 0: cmd_res = "cmd has no output...." conn.send( str(len(cmd_res.encode())).encode() ) #发送服务端发送给客户端数据的长度 conn.send(cmd_res.encode("utf-8")) #发送服务端的数据 print("send done") server.close()
五、解决socket粘包大数据 |
在Linux系统中,服务端两次发送给客户端的数据粘在一起了,原因是什么呢?
答:服务端把两次发送的操作强制的合成一次发送给客户端,所以导致数据粘在一起了。
为啥在windows上是ok的?
答:因为python3的版本是可以的,但是python2.7的版本就会出现粘包的现象,但是有的时候python3也会出现粘包现象。
那我们如何解决这个问题呢?接下来我们就来解决这个问题。
方法一、服务端sleep解决
说明:在两次发送之前sleep一下
方法二、服务端插入交互解决粘包问题
我在服务端来一个等待客户端确认,就ok了,这个确认不需要我们用户输入,而是客户端自动的给你来这个响应,就是说,客户端自动的写好代码,自动的给服务器一个响应,只要收到服务端的数据大小,我就立刻给服务器一个响应,就是在第一次send和第二次send之前插入一个交互,就能把数据分开了。
思路图
六、soke实现文件发送,最后用MD5值校验 |
我们如何利用socket去下载一个文件,整体思路是这样的:
- 读取文件名
- 检测文件是否存在
- 打开文件
- 检测文件大小
- 发送文件大小给客户端
- 等客户确认
- 开始边读边发数据
- 发送md5值给客户端校验
①客户端
判断是否是下载命令(get) ->发送下载命令和文件名 ->获取文件大小->发送确认信息->判断时候已经全部接收
生成MD5值->计算接收的数据的MD5值->生成接收数据的MD5值16进制的形式->发送接收MD5值确认信息->接收客户端的MD5值->客户端和服务端的MD5值做比较
import socket,hashlib client = socket.socket() client.connect(("localhost",9999)) while True: cmd = input(">>>:").strip() if len(cmd) == 0:continue if cmd.startswith("get"): client.send(cmd.encode()) server_respose = client.recv(1024) print("server response:",server_respose) client.send("ready to recv file".encode()) file_total_size = int(server_respose.decode()) revived_size = 0 filename = cmd.split()[1] m = hashlib.md5() #生成MD5对象 with open(filename + ".new","wb") as f: while revived_size < file_total_size: data = client.recv(1024) revived_size += len(data) m.update(data) #计算数据接收的MD5值 f.write(data) else: print(file_total_size,revived_size) client_md5_vaule = m.hexdigest() #生成接收数据的MD5值16进制形式 client.send("ready to recv file md5 value".encode()) server_md5_value = client.recv(1024) #接收客户端的MD5值 if client_md5_vaule == server_md5_value.decode(): #客户端和服务端的MD5值做比较 print("file recv done") else: print(client_md5_vaule,server_md5_value.decode()) client.close()
②服务端
获取命令和文件名->判断文件是否存在->打开文件->获取文件大小->发送文件大小给客户端->等待客户端确认->边读边发
生成md5的对象->计算MD5值->生成16进制的形式->编码后发送给客户端
import hashlib import socket,os server = socket.socket() server.bind(("localhost",9999)) server.listen() while True: conn,addr = server.accept() print("new conn:",addr) while True: print("等待新指令") data = conn.recv(1024) if not data: print("客户端已断开") break cmd,filename = data.decode().split() print(filename) if os.path.isfile(filename): m = hashlib.md5() #生成MD5的对象 with open(filename,"rb") as f: file_size = os.stat(filename).st_size conn.send( str(file_size).encode() ) #send file size conn.recv(1024) for line in f: m.update(line) #计算md5值 conn.send(line) print("file md5",m.hexdigest()) conn.recv(1024) #等待客户确认发送MD5值 conn.send(m.hexdigest().encode()) #生成MD5值并且发送给客户端 print("send done") server.close()