Python学习笔记【第十四篇】:Python网络编程二黏包问题、socketserver、验证合法性
TCP/IP网络通讯粘包问题
案例:模拟执行shell命令,服务器返回相应的类容。发送指令的客户端容错率暂无考虑,按照正确的指令发送即可。
服务端代码
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socket
import subprocess def server_Tcp():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, )
server.bind(('', ))
server.listen() while True:
client_socket, client_info = server.accept()
print("%s 已连接" % str(client_info))
try: while True:
client_msg = client_socket.recv()
if not client_msg: break
print("%s 收到指令<<%s" % (client_info, client_msg.decode('utf-8')))
# 处理执行的命令
res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_err = err
else:
cmd_err = res.stdout.read() # print(cmd_err.decode("gbk"))
client_socket.send(cmd_err)
except Exception as e:
print("%s 已断开" % str(client_info))
print(e)
continue
client_socket.close() server.close() if __name__ == "__main__":
server_Tcp()
客户端代码
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socket def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', )) while True:
msg = input('>>').strip()
if not msg: continue
if msg == 'quit': break
client.send(msg.encode('utf-8'))
result = client.recv()
print(result)
print("<<%s" % (result.decode('utf-8'))) client.close() if __name__ == "__main__":
client_Tcp()
说明:python中执行命令的指令模块是
import subprocess
# 处理执行的命令
res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,stdout=subprocess.PIPE, stdin=subprocess.PIPE)
执行演示
首先运行服务端,然后运行客户端输入ipconfig指令。
服务端

客户端

我们在windows下的cmd命令下看看,或者是在shell客户端查看Linux系统的一样。

以上客户端接收的结果为什么不是完整的简要说明
首先我们的客户端和服务端在电脑中其实就是两个程序。也就是应用程序,应用程序是无法和硬件接触的,应用程序去调用socket层去处理收发数据,而socket后面是去和操作系统对接,最后我们的收发数据是由操作系统去和硬件对接完成的。我们的数据都是在内存中读取的。如下图

当服务器一次性发送了10M数据,然而客户端每次接收2M这时候客户端就没有接收到完整的数据,下次接收还是在接收上次服务器发送的数据,也就是说,服务端可以发送任意大小的数据(配置条件下),而客户端不知道这次接收多大的数据就产生了黏包。
还有一种情况是服务端连续多次send如果这几次send相隔很近,TCP内部机制会将这个几次send组包为一次send发送内容。
在上述案例中我输入第二个指令dir 结果接收的还是第一次的内容

解决黏包方法之一
思路是在我们每次发送的信息中封装一个头部信息,告诉对方这次发送数据的大小这样客户端就知道要接收多少数据量了。
服务端
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socket
import subprocess
import struct BUFFER_SIZE = def server_Tcp():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, )
server.bind(('', ))
server.listen() while True:
client_socket, client_info = server.accept()
print("%s 已连接" % str(client_info))
try: while True:
client_msg = client_socket.recv(BUFFER_SIZE)
if not client_msg: break
print("%s 收到指令<<%s" % (client_info, client_msg.decode('utf-8')))
# 处理执行的命令
res = subprocess.Popen(client_msg.decode('utf-8'), shell=True, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, stdin=subprocess.PIPE)
err = res.stderr.read()
if err:
cmd_err = err
else:
cmd_err = res.stdout.read() # # 第一种方式:解决粘包问题
# msg_len = len(cmd_err)
# print("数据长度为:", msg_len)
# client_socket.send(str(msg_len).encode('utf-8'))
# # 马上等待回复
# is_ok = client_socket.recv(BUFFER_SIZE)
# if is_ok == b"OK":
# client_socket.send(cmd_err) # 第二种方式:解决粘包问题
msg_len = len(cmd_err)
msg_len = struct.pack('i', msg_len)
# 下面两次发送,在客户端会当成一次接收
client_socket.send(msg_len)
client_socket.send(cmd_err) except Exception as e:
print("%s 已断开" % str(client_info))
print(e)
continue
client_socket.close() server.close() if __name__ == "__main__":
server_Tcp()
客户端
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socket
import struct BUFFER_SIZE = def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', )) while True:
msg = input('>>').strip()
if not msg: continue
if msg == 'quit': break client.send(msg.encode('utf-8')) # # 第一种方式:解决粘包问题
# content_length = client.recv(BUFFER_SIZE)
# # 规定好第一次发来的内容是内容大小,给服务器回复个OK
# client.send(b"OK")
# content_length = int(content_length.decode('utf-8'))
# 第二种方式:解决粘包问题
# 先接收四个字节
length_data = client.recv()
content_length = struct.unpack('i', length_data)[] print("准备接收%d大小的数据")
recv_size =
recv_msg = b''
# 循环获取数据
while recv_size < content_length:
recv_msg += client.recv(BUFFER_SIZE)
recv_size = len(recv_msg) print("<<%s" % (recv_msg.decode('gbk'))) client.close() if __name__ == "__main__":
client_Tcp()
当我们再次运行后输入ipconfig命令,这时候客户端收到的就是完整的内容了。截图略....
总结:
TCP是面向连接的,传输是基于数据流的,收发两端进行连接,数据传输,断开连接都是要经过来回的确认。TCP协议数据传输是不会丢失的,一次没有接收完,下次依然会接收,直到一端接收到ack时才知道数据接收完成。
UDP是无连接的。面向消息的,不管发送的是什么内容,内部都会为当前消息组装一个报文头。
UDP不会产生粘包问题,因为UDP只收一次,每次都会有个报文头+内容
socketserver ------多线程服务
用法很简单,在服务端代码中,创建一个类,让这个类继承BaseRequestHandler 然后在类中实现父类方法handle ,客户端代码不需做改变。详见代码
服务端
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socketserver BUFFER_SIZE =
ADDRESS_IP = ('172.16.6.5', ) class Communication(socketserver.BaseRequestHandler): def handle(self):
print(self.request) # ------->
print(self.client_address)
while True:
try:
client_msg = self.request.recv(BUFFER_SIZE)
if not client_msg:
break print("客户端【%s】>>%s" % (self.client_address, client_msg))
self.request.sendall(client_msg.upper())
except Exception as e:
print(e)
break if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(ADDRESS_IP, Communication)
server.serve_forever()
客户端代码
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socket BUFFER_SIZE =
ADDRESS_IP = ('172.16.6.5', ) def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADDRESS_IP) while True:
msg = input('>>:')
client.sendall(msg.encode('utf-8')) server_msg = client.recv(BUFFER_SIZE)
print("<<%s" % (server_msg.decode('utf-8'))) client.close() if __name__ == "__main__":
client_Tcp()
这样就可以实现服务端响应多个客户端了。
验证合法性
案例:写一个服务端,客户端。服务端验证客户端是否是合法的,验证通过的客户端才能正常进行通讯。
思路:和解决粘包思路一样,在头部再次封装一个验证的信息,当客户端连接服务成功后,服务端发送一个验证信息,客户端接收后通过key值加密后发回给服务端,服务端接收客户发来的密文和自己的进行比较,相同则是合法的。
服务端
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socketserver
import struct
import os
import hmac BUFFER_SIZE =
BIND_IP_PORT = ('172.16.6.5', )
AUTO_KEY = b"bc892ed2-8b2c-11e8-a328-f40669b72fef" class Communication(socketserver.BaseRequestHandler):
""" 通讯类型 """ def handle(self):
# print(self.client_address)
# print(self.request)
cl, pl = self.client_address
print("[%s:%s]客户端已连接" % (cl, pl))
# 合法验证
if conn_auth(self.request) == True:
# 循环接收客户端信息
while True:
try:
# 等待客户端消息
# 数据粘包处理
# 数据的前四位存储的是数据的大小
# 先接收四个字节
length_data = self.request.recv()
if not length_data: break content_length = struct.unpack('i', length_data)[] print("接收客户端[%s][%d]大小的数据" % (self.client_address, content_length))
recv_size =
recv_msg = b''
# 循环获取数据
while recv_size < content_length:
recv_msg += self.request.recv(BUFFER_SIZE)
recv_size = len(recv_msg)
print("[%s]>>%s" % (self.client_address, recv_msg.decode('utf-8')))
# 服务器回复客户端
server_msg = input("<<") server_send_length = struct.pack('i', len(server_msg)) self.request.sendall(server_send_length)
self.request.sendall(server_msg.encode("utf-8"))
except Exception as e:
print("[%s]客户端异常关闭" % self.client_address)
print(e)
else:
print("客户端【%s:%s】非法接入" % (cl, pl)) def conn_auth(conn):
# 合法性验证
msg = os.urandom()
conn.sendall(msg)
h = hmac.new(AUTO_KEY, msg)
digest = h.digest()
respone = conn.recv(len(digest))
return hmac.compare_digest(respone, digest) if __name__ == "__main__":
server = socketserver.ThreadingTCPServer(BIND_IP_PORT, Communication)
server.serve_forever()
客户端
# -*- coding: utf- -*- # 声明字符编码
# coding:utf- import socket
import struct
import os, hmac BUFFER_SIZE =
BIND_IP_PORT = ('172.16.6.5', )
AUTO_KEY = b"bc892ed2-8b2c-11e8-a328-f40669b72fef" def client_Tcp():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(BIND_IP_PORT) # 合法性验证
conn_auth(client)
while True: msg = input('>>').strip()
if not msg: continue
if msg == 'quit': break server_send_length = struct.pack('i', len(msg))
client.send(server_send_length)
client.send(msg.encode('utf-8'))
# # 第一种方式:解决粘包问题
# content_length = client.recv(BUFFER_SIZE)
# # 规定好第一次发来的内容是内容大小,给服务器回复个OK
# client.send(b"OK")
# content_length = int(content_length.decode('utf-8'))
# 第二种方式:解决粘包问题
# 先接收四个字节
length_data = client.recv()
content_length = struct.unpack('i', length_data)[] print("准备接收服务端[%d]大小的数据" % content_length)
recv_size =
recv_msg = b''
# 循环获取数据
while recv_size < content_length:
recv_msg += client.recv(BUFFER_SIZE)
recv_size = len(recv_msg) print("<< %s" % (recv_msg.decode('utf-8'))) client.close() def conn_auth(conn):
msg = conn.recv()
h = hmac.new(AUTO_KEY, msg)
digest = h.digest()
conn.sendall(digest) if __name__ == "__main__":
client_Tcp()
程序跑起来..................
客户端发送一个内容

服务端接收

当我把客户端的key值改一下试试..................
客户端连接服务端后,服务端进行验证不通过。

Python学习笔记【第十四篇】:Python网络编程二黏包问题、socketserver、验证合法性的更多相关文章
- Python 学习笔记(十四)Python类(三)
完善类的内容 示例: #! /usr/bin/env python # coding =utf-8 #通常类名首字母大写 class Person(object): """ ...
- Python 学习笔记(十四)Python类(二)
创建简单的类 新式类和经典类(旧式类) Python 2.x中默认都是经典类,只有显式继承了object才是新式类 Python 3.x中默认都是新式类,经典类被移除,不必显式的继承object 新式 ...
- Python 学习笔记(十四)Python类(一)
基本概念 问题空间:问题空间是问题解决者对一个问题所达到的全部认识状态,它是由问题解决者利用问题所包含的信息和已贮存的信息主动的地构成的. 初始状态:一开始时的不完全的信息或令人不满意的状况: 目标状 ...
- Python学习笔记(十四)
Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
- Python学习笔记(十四):模块高级
以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...
- Python基础笔记系列十四:python无缝调用c程序
本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!! python语言可以对c程序代码进行调用,以弥补python语言低性能的缺 ...
- Python学习笔记【第四篇】:基本数据类型
变量:处理数据的状态 变量名 = 状态值 类型 python中有以下基本数据类型: 1:整形 2:字符串类型 3:Bool类型 4:列表 5:元祖(不可变) 6:字典(无序) 7:集合 (无序.不重复 ...
- python学习笔记-(十四)I/O多路复用 阻塞、非阻塞、同步、异步
1. 概念说明 1.1 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可 ...
- python学习笔记-(十四)进程&协程
一. 进程 1. 多进程multiprocessing multiprocessing包是Python中的多进程管理包,是一个跨平台版本的多进程模块.与threading.Thread类似,它可以利用 ...
随机推荐
- spring-boot自定义favicon.ico文件
一.将ico文件存放到resources目录的static中的favicon下. 二.在application.properties文件中增加配置 spring.mvc.favicon.enabled ...
- ABB机器人设置安全区(案例版)
ABB机器人设置安全区.中断(案例版) 1.概述 在如今机器人中普遍会设置机器人的安全区域,也可以理解为工作范围.主要目的是为了机器人运行时的安全性和可靠性.ABB机器人也不例外,下面我们就讲讲ABB ...
- python写注册
# coding = UTF-8 注释格式 import datetime 引用日期 today = datetime.datetime.today().strftime("%Y-%m-%d ...
- jquery中 after append appendTo 的区别
after:在选择的元素后面添加内容,不在选择的元素内 append:在选择的元素后面的里面添加内容 appendTo:将元素添加到选择的元素里面,而且添加的元素必须是jQuery对象
- Jython 在 Eclipse 控制台报错 console: Failed to install '': java.nio.charset.UnsupportedCharsetException: cp0.
在 Eclipse 中使用 Jython 时报错 解决办法 右键 --> Run As --> Run Configurations --> Arguments --> 设置 ...
- Python之路(第三十五篇) 并发编程:操作系统的发展史、操作系统的作用
一.操作系统发展史 第一阶段:手工操作 —— 真空管和穿孔卡片 第一代之前人类是想用机械取代人力,第一代计算机的产生是计算机由机械时代进入电子时代的标志,从Babbage失败之后一直到第二次世界大 ...
- jquery倒计时按钮常用于验证码倒计时
<!doctype html><html><head> <meta charset="utf-8"> <title>jq ...
- Play中JSON序列化
总的来说在scala体系下,对于习惯了java和c#这些常规开发的人来说,无论是akka-http还是play,就处理个json序列化与反序列化真他娘够费劲的. 根据经验,Json处理是比较简单的,但 ...
- sqlserv 配置 CLR
转载地址:http://www.cnblogs.com/Brambling/p/8000911.html //clr 配置 https://docs.microsoft.com/zh-cn/sql ...
- 桌面小部件Wight父类AppWidgetProvider的三个方法
onUpdate()这个方法会在每次更新App Widget的时候调用,数据更新的逻辑都写在这个方法里边.而且要注意的是:在用户添加小部件的时候,会首先调用这个方法,应该在这个方法里进行初始化操作,比 ...