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、验证合法性的更多相关文章

  1. Python 学习笔记(十四)Python类(三)

    完善类的内容 示例: #! /usr/bin/env python # coding =utf-8 #通常类名首字母大写 class Person(object): """ ...

  2. Python 学习笔记(十四)Python类(二)

    创建简单的类 新式类和经典类(旧式类) Python 2.x中默认都是经典类,只有显式继承了object才是新式类 Python 3.x中默认都是新式类,经典类被移除,不必显式的继承object 新式 ...

  3. Python 学习笔记(十四)Python类(一)

    基本概念 问题空间:问题空间是问题解决者对一个问题所达到的全部认识状态,它是由问题解决者利用问题所包含的信息和已贮存的信息主动的地构成的. 初始状态:一开始时的不完全的信息或令人不满意的状况: 目标状 ...

  4. Python学习笔记(十四)

    Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...

  5. Python学习笔记(十四):模块高级

    以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...

  6. Python基础笔记系列十四:python无缝调用c程序

    本系列教程供个人学习笔记使用,如果您要浏览可能需要其它编程语言基础(如C语言),why?因为我写得烂啊,只有我自己看得懂!! python语言可以对c程序代码进行调用,以弥补python语言低性能的缺 ...

  7. Python学习笔记【第四篇】:基本数据类型

    变量:处理数据的状态 变量名 = 状态值 类型 python中有以下基本数据类型: 1:整形 2:字符串类型 3:Bool类型 4:列表 5:元祖(不可变) 6:字典(无序) 7:集合 (无序.不重复 ...

  8. python学习笔记-(十四)I/O多路复用 阻塞、非阻塞、同步、异步

    1. 概念说明 1.1 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方).操作系统的核心是内核,独立于普通的应用程序,可 ...

  9. python学习笔记-(十四)进程&协程

    一. 进程 1. 多进程multiprocessing multiprocessing包是Python中的多进程管理包,是一个跨平台版本的多进程模块.与threading.Thread类似,它可以利用 ...

随机推荐

  1. MySQL数据库(增删改查语句)

    MySQL数据库(增删改查语句)一.登录数据库:---->  mysql -uroot -proot;(对应用户名和密码)二.SQL语句:    数据定义语言DDL  用来定义数据库.表.列,关 ...

  2. java中的 java.util.concurrent.locks.ReentrantLock类中的lockInterruptibly()方法介绍

    在java的 java.util.concurrent.locks包中,ReentrantLock类实现了lock接口,lock接口用于加锁和解锁限制,加锁后必须释放锁,其他的线程才能进入到里面执行, ...

  3. ArrayAdapter构造方法中的textViewResourseId

    simple_list_item_1:每个列表项都是一个普通的textView simple_list_item_2:每个列表项都是一个普通的textView(字体略大) simple_list_it ...

  4. 2019-1-18 Spark 机器学习

    2019-1-18 Spark 机器学习 机器学习 模MLib板 预测 //有视频 后续会补充 1547822490122.jpg 1547822525716.jpg 1547822330358.jp ...

  5. Java基础类

    Java8提供了四千多个基础类,通过这些基础类库可以提高开发效率,使用它们编写好的类进行开发,不用自己去写好这个类,这个方法是干什么的,极大程度的降低了开发难度,为Java开发 带来了极大的便利.本文 ...

  6. rn下的弹性布局

    重点: 1]react native 下的弹性布局名字叫:flexDirection 2]flexDirection的默认值是column而不是row,而flex也只能指定一个数字值. 3]使用fle ...

  7. Linux学习---位运算符

    <<.>> ① << 左移  乘以2^n m << n m*(2^n) eg:4: 0 0 1 0 0 8: 0 1 0 0 0 [数据.数字]移位 左 ...

  8. 卸载HDP大数据平台

    使用以下说明卸载HDP: 停止所有已安装的HDP服务.请参阅HDP参考指南中的停止HDP服务. 如果安装了Knox,请在所有群集节点上运行以下命令: 对于RHEL / CentOS / Oracle ...

  9. JavaScript基础视频教程总结(111-120章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  10. REdis zset和double

    平台:x86_64 结论:Zset的最大分数不要超过18014398509481982(17位数字,54位二进制),否则不会得到期望的值. REdis:5.0.4 Zset采用double存储分数值( ...