python之路 -- 网络编程
Posted aberwang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python之路 -- 网络编程相关的知识,希望对你有一定的参考价值。
1.软件开发的架构
- C/S架构(需要安装应用程序使用的软件)
c client 客户端
s server 服务端
- B/S架构(可以通过浏览器使用的)
b broser 浏览器
s server 服务端
不需要额外的安装客户端了,只需要一个网址就可以访问
轻量级,使用成本低
2.tcp协议/udp协议
tcp协议
全双工的通信协议
建立了专门穿送数据的通道(连接),是一个长连接
面向流的传输
传输速率比udp协议慢
数据安全不容易丢失
大文件算法自己拆包编号发送
建立连接的 三次握手
断开连接的 四次挥手
udp协议
是面向文件的传输,
是一种不可靠的
不需要建立连接,直接传送(传输通道)
传输速率比tcp协议高
不适合大文件的传输(文件过大会报错)
3.socket
网络编程就是如何在程序中实现两台计算机的通信,而两台计算机间的通信其实就是两个进程间的通信,进程间的通信主要通过socket(套接字)来描述ip地址(主机)和端口(进程)来实现的
socket是python中的一个内置模块
socket的使用
--使用tcp协议的socket
1 server端-tcp_server.py 2 import socket 3 tcp_sk = socket.socket() #创建客户套接字 4 tcp_sk.bind(("127.0.0.1",8090)) #把地址,端口绑定到套接字,参数为一个元组 5 6 tcp_sk.listen() #监听链接(请求) 7 conn, addr = tcp_sk.accept() #接受客户端链接 8 ret1 = conn.recv(1024) #接收客户端信息,参数为这一次最接收1024个字节 9 print(ret1) #打印客户端信息 10 conn.send(b‘hi‘) #向客户端发送信息,必须传一个bytes类型 11 ret2 = conn.recv(1024) 12 print(ret2.decode("utf-8")) 13 conn.send(bytes(‘吃了‘,encoding="utf-8")) 14 15 conn.close() #关闭客户端套接字 16 tcp_sk.close() #关闭服务器套接字 17 18 19 client端-tcp_clent.py 20 import socket 21 tcp_sk = socket.socket() 22 tcp_sk.connect((‘127.0.0.1‘,8090)) 23 24 tcp_sk.send(b‘hello‘) 25 ret = tcp_sk.recv(1024) 26 print(ret) 27 tcp_sk.send(bytes("吃了吗?",encoding=‘utf-8‘)) 28 ret2 = tcp_sk.recv(1024) 29 print(ret2.decode(‘utf-8‘)) 30 31 tcp_sk.close()
--使用udp协议的socket
1 server端-udp_server.py 2 import socket 3 sk = socket.socket(type=socket.SOCK_DGRAM) 4 sk.bind(("127.0.0.1",8096)) 5 # 传入的IP和端口号必须是元组的形式 6 msg,addr = sk.recvfrom(1024) 7 print(msg.decode(‘utf-8‘)) 8 sk.sendto(b‘bye‘,addr) 9 sk.close() 10 11 12 client端-udp_client.py 13 import socket 14 sk = socket.socket(type=socket.SOCK_DGRAM) 15 ip_port = ("127.0.0.1",8096) 16 # 传入IP_port的时候必须是元组形式 17 sk.sendto(b‘hello‘,ip_port) 18 ret,addr = sk.recvfrom(1024) 19 print(ret.decode(‘utf-8‘)) 20 21 sk.close() 22 23 # udp的server不需要进行监听,也不需要建立连接 24 # 在启动服务之后只能被动的等待客服端发送消息过来 25 # 客户端发送消息的同时还需要带上地址信息 26 # 消息回复的时候,不仅需要发送消息,还需要把对方的地址填写上
注意事项:
--发送的消息必须是bytes类型的数据。注意编码encode,解码decode
--传入的IP地址、端口号必须以元组的形式传入,直接开的端口号一般在8000以后
--tcp协议不能发空消息,udp协议可以
--tcp协议会发生黏包现象,udp协议不会发生黏包现象
--使用tcp协议的socket必须先运行sever端,再运行client端。基于udp的socket不需要有运行的顺序
--一般先关闭client端,再关闭sever端
黏包问题及解决方案
1.为什么会出现黏包现象:
首先只有在tcp协议中才会出现黏包现象
是因为tcp协议是面向流的协议
在发送数据传输的过程中还有缓存机制来避免数据丢失
因此在连续发送小数据的时候,以及接收大小不符合的时候都容易出现黏包现象
本质还是因为我们在接收数据的时候不知道发送的数据的长短
2.解决黏包问题的方案:
在传输大量数据之前先高数接收端要发送的数据的大小
高跟好的解决方案:通过struct模块来定制协议
demo1,远程发送系统命令
1 demo1,远程发送系统命令 2 server端 3 import socket 4 sk = socket.socket() 5 sk.bind((‘127.0.0.1‘,8090)) 6 sk.listen() 7 conn,addr = sk.accept() 8 9 while True: 10 cmd = input(‘>>>‘) 11 if cmd == "q": 12 conn.send(b"q") 13 break 14 conn.send(cmd.encode(‘gbk‘)) 15 res = conn.recv(500) 16 print(res.decode(‘gbk‘)) 17 18 conn.close() 19 sk.close() 20 21 client端 22 import socket 23 import subprocess 24 25 sk = socket.socket() 26 sk.connect((‘127.0.0.1‘,8090)) 27 28 while True: 29 cmd = sk.recv(1024).decode(‘gbk‘) 30 if cmd == "q": 31 break 32 res = subprocess.Popen(cmd,shell=True, 33 stdout=subprocess.PIPE, 34 stderr=subprocess.PIPE) 35 sk.send(res.stdout.read()) 36 sk.send(res.stderr.read()) 37 38 sk.close()
demo2,使用struct模块解决黏包问题
1 demo2,使用struct模块解决黏包问题 2 server端 3 import socket 4 import struct 5 6 sk = socket.socket() 7 sk.bind((‘127.0.0.1‘,8090)) 8 sk.listen() 9 conn,addr = sk.accept() 10 11 while True: 12 cmd = input(‘>>>‘) 13 if cmd == "q": 14 conn.send(b"q") 15 break 16 conn.send(cmd.encode(‘gbk‘)) 17 num_by = conn.recv(4) 18 num = struct.unpack(‘i‘,num_by)[0] 19 res = conn.recv(int(num)).decode(‘gbk‘) 20 print(res) 21 22 conn.close() 23 sk.close() 24 25 client端 26 import socket 27 import subprocess 28 import struct 29 30 sk = socket.socket() 31 sk.connect((‘127.0.0.1‘,8090)) 32 33 while True: 34 cmd = sk.recv(1024).decode(‘gbk‘) 35 if cmd == "q": 36 break 37 res = subprocess.Popen(cmd,shell=True, 38 stdout=subprocess.PIPE, 39 stderr=subprocess.PIPE)#只能读取一次,读后就没有了,类似队列 40 std_out = res.stdout.read() 41 std_err = res.stderr.read() 42 len_num = len(std_out)+len(std_err) #计算所要发送数据的长度 43 num_by = struct.pack(‘i‘,len_num) 44 sk.send(num_by) 45 sk.send(std_out) 46 sk.send(std_err) 47 48 sk.close()
demo3,实现大文件的传输
1 server端 2 #_*_coding:utf-8_*_ 3 4 import socket 5 import struct 6 import json 7 8 buffer = 1024 9 10 sk = socket.socket() 11 sk.bind((‘127.0.0.1‘,8080)) 12 sk.listen() 13 conn, addr = sk.accept() 14 15 headlen = conn.recv(4) 16 head_len = struct.unpack(‘i‘,headlen)[0] #unpack之后得到的是一个元组 17 head_json = conn.recv(head_len).decode(‘utf-8‘) 18 head = json.loads(head_json) 19 file_size = head[‘filesize‘] 20 21 with open(head["filename"],mode="wb")as f: 22 while file_size: 23 if file_size >= buffer: 24 content = conn.recv(buffer) 25 file_size -= buffer 26 f.write(content) 27 else: 28 content = conn.recv(file_size) 29 f.write(content) 30 break 31 32 conn.close() 33 sk.close() 34 35 36 client端 37 #_*_coding:utf-8_*_ 38 39 import socket 40 import os 41 import json 42 import struct 43 44 buffer = 1024 45 46 sk = socket.socket() 47 sk.connect((‘127.0.0.1‘,8080)) 48 49 # 制作报头 50 head = { 51 ‘filename‘:‘04 python fullstack s9day32 struct模块补充.mp4‘, 52 ‘filepath‘:‘F:python全栈9期视频第一部分-基础day32‘, 53 ‘filesize‘:‘‘ 54 } 55 file_path = os.path.join(head[‘filepath‘],head[‘filename‘]) 56 file_size = os.path.getsize(file_path) 57 head[‘filesize‘] = file_size 58 59 #先发送报头的大小 60 json_head = json.dumps(head) #将报头的字典转换成字符串 61 bytes_head = json_head.encode(‘utf-8‘) #将报头head转换成bytes类型 62 len_head = len(bytes_head) #计算head的长度 63 head_pack = struct.pack(‘i‘,len_head) 64 sk.send(head_pack) #先发送报头的长度 65 sk.send(bytes_head) #再发送报头的bytes类型数据 66 with open(file_path,mode = "rb") as f: 67 while file_size: 68 if file_size >= buffer: 69 content = f.read(buffer) 70 file_size -= buffer 71 sk.send(content) 72 else: 73 content = f.read(file_size) 74 sk.send(content) 75 break 76 77 sk.close()
demo4,验证客户端的合法性
1 demo4,验证客户端的合法性 2 server端 3 import socket 4 import os 5 import hmac 6 7 secret_key = b‘egg‘ 8 sk = socket.socket() 9 sk.bind(("127.0.0.1",8066)) 10 sk.listen() 11 12 def check_conn(conn): 13 msg = os.urandom(32) 14 conn.send(msg) 15 h = hmac.new(secret_key,msg) 16 digest = h.digest() 17 client_digest = conn.recv(1024) 18 return hmac.compare_digest(digest,client_digest) 19 20 conn,addr = sk.accept() 21 res = check_conn(conn) 22 if res: 23 print("合法的客户端") 24 conn.close() 25 else: 26 print("不合法的客户端") 27 conn.close() 28 29 sk.close() 30 31 32 client端 33 import socket 34 import hmac 35 36 secret_key = b‘egg‘ 37 38 sk = socket.socket() 39 sk.connect((‘127.0.0.1‘,8066)) 40 msg = sk.recv(1024) 41 h = hmac.new(secret_key,msg) 42 digest = h.digest() 43 sk.send(digest) 44 45 sk.close()
以上是关于python之路 -- 网络编程的主要内容,如果未能解决你的问题,请参考以下文章