Python网络编程
tcp和udp的区别:https://www.jianshu.com/p/c63b082ac565
1.基于tcp
# 服务端
import socket # 创建服务端socket对象 server = socket.socket() # 绑定IP和端口 server.bind(('192.168.3.6',8000)) # 后边可以等5个人 server.listen(5) print('服务端准备开始接收客户端的连接') # 等待客户端来连接,如果没人来就傻傻的等待。 # conn是客户端和服务端连接的对象,服务端以后要通过该对象进行收发数据。 # addr是客户端的地址信息。 # #### 阻塞,只有有客户端进行连接,则获取客户端连接然后开始进行通信。 conn,addr = server.accept() print('已经有人连接上了,客户端信息:',conn,addr) # 通过对象去获取 # 1024表示:服务端获取数据时,一次性最多拿1024字节。 data = conn.recv(1024) print('已经有人发来消息了',data) # 服务端通过连接对象给客户端回复了一个消息。 conn.send(b'stop') # 与客户端断开连接 conn.close() # 关闭服务端的服务 server.close()
#客户端 import socket client = socket.socket() # 客户端向服务端发起连接请求 # 阻塞,去连接,直到连接成功后才会继续向下走。 client.connect(('192.168.3.6',8000)) # # 链接上服务端后,向服务端发送消息 client.send(b'hhhhhhhhhhhh') # 等待服务端给他发送消息 data = client.recv(1024) print(data) # 关闭自己 client.close()
1.1 while版
#服务端 import socket server = socket.socket() server.bind(('192.168.3.6',8001)) server.listen(5) while True: conn,addr = server.accept() # 字节类型 while True: data = conn.recv(1024) # 阻塞 if data == b'exit': break data1=' 梦想成真' data1=data1.encode("utf8") response = data +data1 conn.send(response) conn.close()
#客户端 import socket sk = socket.socket() sk.connect(('192.168.3.6',8001)) while True: name = input("请输入姓名:") sk.send(name.encode('utf-8')) # 字节 if name == 'exit': break response = sk.recv(1024) # 字节 print(response.decode('utf-8')) sk.close()
2.基于udp
#服务端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) # 建立一个socket对象, # 指定以UDP协议的形式来连接 sk.bind(('127.0.0.1',8080)) # 指定服务的地址 msg,addr = sk.recvfrom(1024) # 接收消息,发送端的地址 print(msg,addr) sk.sendto(b'HELLO',addr) # 给发送端回复消息 sk.close() # 关闭socket连接
#客户端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.sendto(b'hello',('127.0.0.1',8080)) # 直接给服务器发送一段消息 msg,addr = sk.recvfrom(1024) # 接收对面的回信 print(msg) sk.close()
2.1 udp聊天小工具
#udp聊天小工具 #服务端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',9090)) while True: msg,addr = sk.recvfrom(1024) print('来自[%s:%s]的消息--%s'%(addr[0],addr[1],msg.decode('utf-8'))) inp = input('>>>') sk.sendto(inp.encode('utf-8'),addr) sk.close()
#客户端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) addr = ('127.0.0.1',9090) while True: msg = input('>>>') sk.sendto(msg.encode('utf-8'),addr) msg_recv,addr = sk.recvfrom(1024) print(msg_recv.decode('utf-8')) sk.close()
2.2 时间服务器
#时间服务器 #服务端 # 需求 # 写一个时间同步的服务器 # 服务端接收请求 # 按照client端发送的时间格式,将服务器时间转换成对应格式 # 发送给客户端 import time import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',9000)) while True: msg,addr = sk.recvfrom(1024) # msg 客户端发送给server端的时间格式 "%Y-%m-%d %H:%M:%S" time_format = msg.decode('utf-8') time_str = time.strftime(time_format) sk.sendto(time_str.encode('utf-8'),addr) sk.close()
#客户端 # client端每隔一段时间发送请求到服务端 # 发送时间的格式 import time import socket sk = socket.socket(type = socket.SOCK_DGRAM) sk.sendto('%Y-%m-%d %H:%M:%S'.encode('utf-8'),('127.0.0.1',9000)) msg,addr = sk.recvfrom(1024) print(msg.decode('utf-8')) sk.close()
3.上传和下载
3.1 知识补充
# JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式 # 方法 描述 # json.dumps() 将 Python 对象编码成 JSON 字符串 # json.loads() 将已编码的 JSON 字符串解码为 Python 对象 # json.dump() 将Python内置类型序列化为json对象后写入文件 # json.load() 读取文件中json形式的字符串元素转化为Python类型
#服务端 import json import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() content = conn.recv(1024).decode('utf-8') content_dic = json.loads(content) # json.dumps()函数的使用,将字典转化为字符串 dumps(丢弃) # json.loads函数的使用,将字符串转化为字典 loads(容器) if content_dic['operate'] == 'upload': conn.send(b'received!') with open(content_dic['filename'],'wb') as f: while content_dic['filesize']: file = conn.recv(1024) f.write(file) content_dic['filesize'] -= len(file) conn.close() sk.close()
# 客户端 import os import json import socket sk = socket.socket() sk.connect(('127.0.0.1', 8080)) def get_filename(file_path): filename = os.path.basename(file_path) return filename # 选择 操作 operate = ['upload', 'download'] for num, opt in enumerate(operate, 1): # 看下面enumerate函数的用法 print(num, opt) num = int(input('请输入您要做的操作序号 : ')) if num == 1: '''上传操作''' # file_path = 'E:\python10\day33\作业.py' file_path = input('请输入要上传的文件路径 : ') # 告诉对方要上传的文件的名字 file_name = get_filename(file_path) # 读要上传的文件 存成字符串 with open(file_path, encoding='utf-8') as f: content = f.read() # content(内容) dic = {'operate': 'upload', 'filename': file_name, 'content': content} # 将字符串send给server端 str_dic = json.dumps(dic) sk.send(str_dic.encode('utf-8')) # server端接收 bytes转码程字符串 # server端打开文件 写文件 elif num == 2: '''下载操作''' sk.close() # 客户端结果 # 1 upload # 2 download # 请输入您要做的操作序号 : # =========================================================== # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串) # 组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。 seasons = ['Spring', 'Summer', 'Fall', 'Winter'] list(enumerate(seasons)) [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')] list(enumerate(seasons, start=1)) # 下标从 1 开始 [(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
4.远程执行系统命令
4.1 基于tcp
#基于tcp
#服务端(服务端向客户端远程执行命令)
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8090))
sk.listen()
conn,addr = sk.accept()
while True:
cmd = input('cmd : ')
if cmd == 'q':
conn.send(cmd.encode('utf-8'))
break
conn.send(cmd.encode('utf-8'))
print('stdout : ',conn.recv(1024).decode('gbk'))
conn.close()
sk.close()
#客户端 import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1',8090)) while True: cmd = sk.recv(1024).decode('utf-8') if cmd == 'q': break res = subprocess.Popen(cmd,shell=True, #管道的数据只能取一次 stdout=subprocess.PIPE, stderr=subprocess.PIPE) sk.send(res.stdout.read()) sk.send(res.stderr.read()) sk.close()
4.1 基于udp
#基于udp的远程执行系统命令 #服务端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8090)) msg,addr = sk.recvfrom(1024) while True: cmd = input('cmd : ') if cmd == 'q': sk.sendto(cmd.encode('utf-8'),addr) break sk.sendto(cmd.encode('utf-8'),addr) print('stdout : ',sk.recvfrom(2048)[0].decode('gbk')) #接收UDP数据,与recv()类似, #但返回值是(data,address)。 #其中data是包含接收数据的字符串, #address是发送数据的套接字地址。 print('stderr : ',sk.recvfrom(2048)[0].decode('gbk')) sk.close()
#客户端 import socket import subprocess sk = socket.socket(type=socket.SOCK_DGRAM) sk.sendto(b'111',('127.0.0.1',8090)) while True: cmd = sk.recvfrom(1024)[0].decode('utf-8') if cmd == 'q': break res = subprocess.Popen(cmd,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) sk.sendto(res.stdout.read()*100,('127.0.0.1',8090)) sk.sendto(res.stderr.read(),('127.0.0.1',8090)) sk.close()
5.黏包
5.1 黏包的产生
#连续两个send()会出现黏包 #连续两个recv(),第一个recv()接收很少的数据 #没接收完的数据会在内存缓存着 #不能一次性把很多数据放到内存 #一般一次性传4096字节 #大文件传输 #大文件的传输,一定是按照字节读,每一次读固定字节 #传输过程中,一边读一边传,接收端一边收一边写
5.2 接收发的缓存引起的黏包
#服务端 from socket import * #导入包,导入模块只需import+模块即可 ip_port=('127.0.0.1',8080) tcp_socket_server=socket() tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) data2=conn.recv(10) print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) import time # time.sleep(1) data2=conn.recv(10) print('----->',data2.decode('utf-8')) conn.close() tcp_socket_server.close() #服务端结果 #-----> he #-----> lloegg #----->
#客户端 import time import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8')) # time.sleep(1) s.send('egg'.encode('utf-8'))
5.3 发送发的缓存引起的黏包
#服务端 from socket import * ip_port=('127.0.0.1',8080) tcp_socket_server=socket() tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10) data2=conn.recv(10) print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close() #服务端结果 #-----> helloegg #----->
#客户端 import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8')) s.send('egg'.encode('utf-8'))
6.黏包问题解决方案
#为什么会出现黏包现象??? #只有在tcp协议中才会出现黏包现象,是因为tcp是面向流的 #tcp协议在发送数据的过程中还有缓存机制来避免数据丢失 #在连续发送小数据的时候,以及接收大小不符合的时候容易出现黏包现象 #本质还是因为在接收数据的时候不知道发送数据的大小 #解决黏包的问题 #不用struct #不要连续发或者连续收,发一次收一次 #用struct #先发送数据长度,转换成四个字节,再发数据,接收方先接收4个字节知道长度再接数据 #struct模块 #struct.pack("i",4096) i是int,就是把一个数字转换成固定长度的bytes类型 #函数 return explain #pack(fmt,v1,v2…) string 按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回. #unpack(fmt,v1,v2…) tuple 按照给定的格式(fmt)解析字节流,并返回解析结果
#服务端 #用的上传和下载的代码 #现在不只是发数据长度,还发文件路径,文件名,文件大小 import json import struct import socket sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() dic_len = conn.recv(4) # 4个字节 数字的大小 dic_len = struct.unpack('i',dic_len)[0] # [0]拿到之前打包的数字 content = conn.recv(dic_len).decode('utf-8') # 根据解包的数字拿到报头,报头和字节数字一起发的, # 也可以和数据一起发,报头中有数据size content_dic = json.loads(content) # 拿到字典 if content_dic['operate'] == 'upload': with open(content_dic['filename'],'wb') as f: while content_dic['filesize']: file = conn.recv(1024) f.write(file) content_dic['filesize'] -= len(file) conn.close() sk.close()
#客户端 import os import json import struct import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) def get_filename(file_path): filename = os.path.basename(file_path) return filename #选择 操作 operate = ['upload','download'] for num,opt in enumerate(operate,1): print(num,opt) num = int(input('请输入您要做的操作序号 : ')) if num == 1: '''上传操作''' file_path = input('请输入要上传的文件路径 : ') file_size = os.path.getsize(file_path) # 获取文件大小 file_name = get_filename(file_path) # 根据定义的函数拿到文件名 dic = {'operate': 'upload', 'filename': file_name,'filesize':file_size} str_dic = json.dumps(dic).encode('utf-8') ret = struct.pack('i', len(str_dic)) # 将字典的大小转换成一个定长(4)的bytes sk.send(ret + str_dic) # 发送字节类型的长度和字典(报头) with open(file_path,'rb') as f: while file_size: content = f.read(1024) sk.send(content) file_size -= len(content) elif num == 2: '''下载操作''' sk.close()
7.验证客户端连接的合法性(hashlib)
# 服务端 import os import socket import hashlib def check_client(conn): secret_key = b'egg' # 密钥 send_str = os.urandom(32) # 随机生成32位的字节 # random() 方法返回随机生成的一个实数,它在[0,1)范围内 conn.send(send_str) md5_obj = hashlib.md5(secret_key) md5_obj.update(send_str) # 把send_str更新到md5_obj里面 secret_ret = md5_obj.hexdigest() # hash.digest() 返回摘要,作为二进制数据字符串值 # hash.hexdigest() 返回摘要,作为十六进制数据字符串值 if conn.recv(1024).decode('utf-8') == secret_ret: print('合法的客户端') return True else: print('非法的客户端') return False sk = socket.socket() sk.bind(('127.0.0.1', 8090)) sk.listen() conn, addr = sk.accept() ret = check_client(conn) while ret: inp = input('>>>') conn.send(inp.encode('utf-8')) msg = conn.recv(1024) print(msg.decode('utf-8')) conn.close() sk.close()
#客户端 import socket import hashlib sk = socket.socket() sk.connect(('127.0.0.1',8090)) recv = sk.recv(1024) #接收到的是服务端随机生成的32位字节 # 用和server端相同的手法对这个字符串进行摘要 secret_key = b'egon' # 密钥和服务端相同才是合法的客户端 md5_obj = hashlib.md5(secret_key) md5_obj.update(recv) ret = md5_obj.hexdigest() sk.send(ret.encode('utf-8')) msg = sk.recv(1024) if msg: print(msg.decode('utf-8')) while True: inp = input('>>>') sk.send(inp.encode('utf-8')) msg = sk.recv(1024) print(msg.decode('utf-8')) sk.close()
8.验证客户端连接的合法性(hmac)
#服务端 import os import socket import hmac def check_client(conn): secret_key = b'egg' # 密钥 send_str = os.urandom(32) conn.send(send_str) hmac_obj = hmac.new(secret_key,send_str) secret_ret = hmac_obj.digest() #bytes类型 if conn.recv(1024) == secret_ret: print('合法的客户端') return True else: print('非法的客户端') return False sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() conn,addr = sk.accept() ret = check_client(conn) while ret: inp = input('>>>') conn.send(inp.encode('utf-8')) msg = conn.recv(1024) print(msg.decode('utf-8')) conn.close() sk.close()
#客户端 import socket import hmac sk = socket.socket() sk.connect(('127.0.0.1',8090)) recv = sk.recv(1024) # 用和server端相同的手法对这个字符串进行摘要 secret_key = b'egg' # 密钥 hmac_obj = hmac.new(secret_key,recv) ret = hmac_obj.digest() sk.send(ret) msg = sk.recv(1024) if msg: print(msg.decode('utf-8')) while True: inp = input('>>>') sk.send(inp.encode('utf-8')) msg = sk.recv(1024) print(msg.decode('utf-8')) sk.close()
9.socketserver
# socketserver的介绍 # socketserver是标准库中的一个高级模块 # socketserver可以简化创建客户端跟创建服务端的代码 导入模块 import socketserver # 初始化控制器类Handler-->Handler是一个继承BaseRequestHandler的类Handler中的 # handle方法决定了每一个连接过来的操作 # 控制器类的类名可以是其他的,不一定是Handler,只要继承了BaseRequestHandler就行 init(): # 初始化控制设置,初始化连接套接字,地址,处理实例等信息 handle():# 定义了如何处理每一个连接 setup(): # 在handle()之前执行,一般用作设置默认之外的连接配置 finish():# 在handle()之后执行 #变量: self.request # 属性是套接字对象,所以使用self.request.xxxx调用套接字的函数 self.server # 包含调用处理程序的实例 self.client_address # 是客户端地址信息
#服务端 import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): # 字节类型 while 1: # 针对window系统 try: print("等待信息") data = self.request.recv(1024) # 阻塞 # 针对linux if len(data) == 0: break if data == b'exit': break response = data + b'SB' self.request.send(response) except Exception as e: break self.request.close()
# 1 创建socket对象 2 self.socket.bind() 3 self.socket.listen(5) server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver) server.serve_forever() #============================================================ #客户端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8899)) while 1: name = input(">>>>:") sk.send(name.encode('utf-8')) # 字节 response = sk.recv(1024) # 字节 print(response.decode('utf-8'))