一:黏包

###tcp协议在发送数据时,会出现黏包现象.

    (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,

    缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。

    (2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度

导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包

 

### 黏包出现的两种情况

#黏包现象一:

在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包

#黏包现象二:

在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包

#总结:

    发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包

    核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

 

###黏包对比:tcp和udp

#tcp协议:

优点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包

缺点:不限制数据包的大小,稳定传输不丢包

 

#udp协议:

优点:接收时候数据之间有边界,传输速度快,不黏包

缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

 

#tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送

但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止

而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

 

### 解决黏包问题

#解决黏包场景:

应用场景在实时通讯时,需要阅读此次发的消息是什么

#不需要解决黏包场景:

下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.

1.黏包现象

黏包现象一:

服务端代码:

import socket

sk = socket.socket()
# 在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定地址端口(在网络上注册主机)
sk.bind( ("127.0.0.1",9001) )
sk.listen() conn,addr = sk.accept()
conn.send("".encode("utf-8"))
message = "hello,my "
conn.send(message.encode("utf-8"))
conn.send("world".encode("utf-8")) # 四次挥手
conn.close()
# 退还端口
sk.close()

客户端代码:

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9001)) res0 = int(sk.recv(1).decode("utf-8")) #res0 "6"
print(res0) res1 = sk.recv(res0)
print(res1) #print(res1.decode("utf-8"))
res2 = sk.recv(20)
print(res2)
sk.close()

服务端向客户端发送两次消息,客户端接收三次,其中第三次出现黏包现象,因为客户端设置只接收6个字节,而服务端第二次发送了8个字节数,所有将剩下2个字节与第三次发送的数据黏包一起发送过来了,现象如截图:

黏包现象二:

首先是服务端代码:

import socket
import time sk = socket.socket()
#在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #绑定地址端口(在网络上注册主机)
sk.bind(("127.0.0.1", 9001))
sk.listen() conn,addr = sk.accept()
conn.send("".encode("utf-8"))
message = 'hello' * 20
conn.send(message.encode("utf-8")) #time.sleep(1)
conn.send("world".encode("utf-8")) #四次挥手
conn.close() #退还端口
sk.close()

然后是客户端代码:

import socket
import time sk = socket.socket()
sk.connect(("127.0.0.1", 9001)) #time.sleep(0.2)
res0 = int(sk.recv(8).decode("utf-8")) #res0 "6"
print(res0) res1 = sk.recv(res0)
print(res1) #print(res1.decode("utf-8"))
res2 = sk.recv(10)
print(res2)
sk.close()

这个因为在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包,以至于第三次接收到的内容为空,因为已经黏包到第二次数据上了。如下截图:

这种情况只要将第二次发送和第三次发送数据隔开一点时间,比如sleep1秒即可,即将注释#time.sleep(1)去掉后运行结果截图如下:

但是这样黏包的现象依然存在,接下来我们先来介绍下可以解决黏包的模块的用法。

2.struct

python中的struct模块就提供了这样的机制,该模块的主要作用就是对python基本类型值与用python字符串格式表示的C struct类型间的转化(This module performs conversions between Python values and C structs represented as Python strings.)。stuct模块提供了很简单的几个函数,下面写例子。

两个函数:pack()、unpack()

struct模块最重要的两个函数就是pack()unpack()方法

打包函数:pack(fmt, v1, v2, v3, ...)

解包函数:unpack(fmt, buffer)

#例:

import struct
# pack 把任意长度的数字转化成固定4个字节长度的字节流
# unpack 将4个字节的值恢复成原本的数据,最后返回一个元组 res = struct.pack("i",2372722) #不能超过int 范围
print(res)
print(len(res)) res = struct.unpack("i",res)
print(res)
print(res[0],type(res[0]))

结果为:

b'r4$\x00'

4

(2372722,)

2372722 <class 'int'>

3.用struct解决黏包现象

首先是服务端代码:

import socket
import struct sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(("127.0.0.1", 9999))
sk.listen() conn,addr = sk.accept()
inp = input("C>>>msg:")
msg = inp.encode("utf-8") #发送数据的长度通过pack进行替换,变成具有固定长度的4个字节的值
res = struct.pack("i",len(msg))
conn.send(res) #接下来,开始真正的发送数据
conn.send(msg)
conn.send("world".encode("utf-8")) res = conn.recv(1024)
print(res)
print(res.decode("utf-8")) #四次挥手
conn.close()
#退还端口
sk.close()

然后是客户端代码:

import socket
import struct
import time sk = socket.socket()
sk.connect(("127.0.0.1", 9999))
time.sleep(0.1) #接收4个字节长度,它实际要发送的那个数字转化来的
n = sk.recv(4)
n = struct.unpack("i",n)[0]
print(n)
#接下来接收服务端发送过来的数据
res1 = sk.recv(n)
print(res1.decode("utf-8"))
res2 = sk.recv(1024)
print(res2.decode("utf-8")) #空格不是ascii编码中,大家注意
sk.send(b'gqicuq_love_lin') #关闭连接
sk.close()

此时可以输入任何数据,且不会再造成黏包现象,运行后输入数据及输出不在黏包截图:

输入端:

输出端:

二:socketserver并发

#网络协议的最底层就是socket,基于原有socket模块,又封装了一层,就是socketserver

socketserver 为了实现tcp协议,server端的并发.

首先是对socketserver基本用法代码如下:

服务端:

import socketserver
#需要自定义一个类,并继承socketserver.BaseRequestHandler
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
print("---->执行这句") #Threading ((ip,端口号),自定义类)
server = socketserver.ThreadingTCPServer(("127.0.0.1", 9001), MyServer) #循环调用
server.serve_forever()

客户端:

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9001))
sk.close()

然后是使用socketserver来达到并发的效果:

服务端代码:

import socketserver
class MyServer(socketserver.BaseRequestHandler):
#在handle里面自定义收发逻辑
def handle(self):
print("--->这句话被执行了") conn = self.request
while True:
msg = conn.recv(1024).decode("utf-8")
print(msg)
conn.send(msg.upper().encode("utf-8")) #产生一个对象
server = socketserver.ThreadingTCPServer(("127.0.0.1", 9999), MyServer)
#循环不调用
server.serve_forever()

因为要做到并发的效果,所有这边启动了两个客户端,并为此区别,一个客户端发一段a字母,一个客户端发h字母。

客户端1代码如下:

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999)) while True:
sk.send(b'aaaaaaaaaaaaaaa')
msg = sk.recv(1024)
print(msg) sk.close()

客户端2代码如下:

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999)) while True:
sk.send(b"hhhhhhhhh")
msg = sk.recv(1024)
print(msg)

运行截图如下:

服务器端打印:

客户端1打印:

客户端2打印:

就此形成了并发效果。

而socketserver是需要严格的格式编写的,除了下图中红色框起来的可以自由编写,别的代码都是固定的:

Python 之网络编程之socket(2)黏包现象和socketserver并发的更多相关文章

  1. Python 之网络编程之socket(1)TCP 方式与UDP方式

    一:socket介绍 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. 建立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API) ...

  2. Python 之网络编程之socket(3)hashlib模块

     hashlib模块 #hashlib 这个模块是一堆加密算法的集合体,哈希算法的加密方式不止一种 httpswww.cmd5.com md5解密 # 应用场景在需要效验功能时使用     用户密码的 ...

  3. 网络编程之socket

    网络编程之socket socket:在网络编程中的一个基本组件,也称套接字. 一个套接字就是socket模块中的socket类的一个实例. 套接字包括两个: 服务器套接字和客户机套接字 套接字的实例 ...

  4. 网络编程之Socket & ServerSocket

    网络编程之Socket & ServerSocket Socket:网络套接字,网络插座,建立网络通信连接至少要一对端口号(socket).socket本质是编程接口(API),对TCP/IP ...

  5. GO语言的进阶之路-网络编程之socket

    GO语言的进阶之路-网络编程之socket 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是socket; 在说socket之前,我们要对两个概念要有所了解,就是IP和端口 ...

  6. [深入浅出Cocoa]iOS网络编程之Socket

    http://blog.csdn.net/kesalin/article/details/8798039 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+]   [深入浅出Co ...

  7. 网络编程之Socket代码实例

    网络编程之Socket代码实例 一.基本Socket例子 Server端: # Echo server program import socket HOST = '' # Symbolic name ...

  8. [网络编程之Socket套接字介绍,套接字工作流程,基于TCP协议的套接字程序]

    [网络编程之Socket套接字介绍,套接字工作流程,基于TCP协议的套接字程序] 为何学习socket套接字一定要先学习互联网协议: 1.首先:要想开发一款自己的C/S架构软件,就必须掌握socket ...

  9. Python自动化运维之15、网络编程之socket、socketserver、select、twisted

    一.TCP/IP相关知识 TCP/UDP提供进程地址,两个协议互不干扰的独自的协议       TCP :Transmission Control Protocol 传输控制协议,面向连接的协议,通信 ...

随机推荐

  1. Markdown Learning Notes

    Markdown 教程 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档. Markdown 语言在 2004 由约翰·格鲁伯(英语:John Gruber)创建. ...

  2. 《一篇文章读懂HTTPS及其背后的加密原理》阅读笔记

    HTTPS(Hypertext Transfer Protocol Secure,超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版.这篇文章深入介绍了它的原理. 当我们适用 ...

  3. BFS(广度优先搜索遍历保存全局状态,华容道翻版做法)--08--DFS--蓝桥杯青蛙跳杯子

    题目描述 X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色. X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去. 如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙 ...

  4. 让你的逼格瞬间提升的十个Python语法!

    Python 是一种代表简单思想的语言,其语法相对简单,很容易上手.不过,如果就此小视 Python 语法的精妙和深邃,那就大错特错了.本文精心筛选了最能展现 Python 语法之精妙的十个知识点,并 ...

  5. Linux - Shell后台、前台,运行命令

    后台运行(终端能操纵) 只需要在后面加& gedit & 查看正在运行的jobs jobs 放到前台运行(终端不能操作) fg % fg %1 一个终端一个context.一个终端就是 ...

  6. 喵星之旅-狂奔的兔子-svn安装及使用

    一.服务端安装配置 1.安装svn 创建版本库并配置 以root用户登录,或者具有sudo权限的用户,这里选择root. yum install subversion 都选择y 2.创建版本库并配置 ...

  7. java与以太坊之web3j

    web3j:https://docs.web3j.io/index.html 如何使用Web3j生成私钥和地址,而不只是创建密钥存储JSON文件? https://blog.csdn.net/mong ...

  8. 判断一个数组是否包含一个指定的值 includes-ES6

    var array1 = [1, 2, 3]; console.log(array1.includes(2));  // trueconsole.log(array1.includes(2, 5)); ...

  9. python3 linux

    记录了Linux 安装python3.7.0的详细过程,供大家参考,具体内容如下 我这里使用的时centos7-mini,centos系统本身默认安装有python2.x,版本x根据不同版本系统有所不 ...

  10. Vagrant 安装使用

    先安装虚拟机 https://www.virtualbox.org/ 再安装 https://www.vagrantup.com/  1.nginxhttp://nginx.org/download/ ...