博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python socket 粘包问题 报头
阅读量:4658 次
发布时间:2019-06-09

本文共 8103 字,大约阅读时间需要 27 分钟。

一 socket(套接字)

1.什么是socket:Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。它把复杂的TCP/IP协议族隐藏在Socket接口后面,了socket以后,无需自己编写代码实现三次握手,四次挥手,ARP请求,打包数据等等,socket已经封装好了,只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

2.基本语法

客户端import socket# 1.创建socket对象  client = socket.socket() # 2.链接服务器    client.connect((ip,port)) # 3.收发数据   通常需要循环client.send(二进制数据) #   只能发二进制数据client.recv(字节数)    #  收多少字节数 阻塞直到接收到数据# 4.断开链接   client.close()服务端# 1.创建socket对象server = socket.socket()# 2.绑定一个固定的ip和端口server.bind(ip,port))# 3.开始监听客户端的到来server.listen() # 可不填# 4.接收客户端的链接请求conn,addr = server.accept()  # 阻塞直到客户链接到来  没有新连接则不可能执行该函数# 5.收发数据   通常需要循环conn.send(二进制数据) #   只能发二进制数据conn.recv(字节数)    #  收多少字节数 阻塞直到接收到数据# 6.关闭双行通道和服务器conn.close()server.close()复制代码
View Code
服务端套接字函数s.bind()    绑定(主机,端口号)到套接字s.listen()  开始TCP监听s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来客户端套接字函数s.connect()     主动初始化TCP服务器连接s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常公共用途的套接字函数s.recv()            接收TCP数据s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)s.recvfrom()        接收UDP数据s.sendto()          发送UDP数据s.getpeername()     连接到当前套接字的远端的地址s.getsockname()     当前套接字的地址s.getsockopt()      返回指定套接字的参数s.setsockopt()      设置指定套接字的参数s.close()           关闭套接字面向锁的套接字方法s.setblocking()     设置套接字的阻塞与非阻塞模式s.settimeout()      设置阻塞套接字操作的超时时间s.gettimeout()      得到阻塞套接字操作的超时时间
View Code

3.循环连接和循环通信

客户端import socketclient = socket.socket()client.connect(('127.0.0.1',8080))while True:    msg = input('>>>:').encode('utf-8')    if len(msg) == 0:continue    client.send(msg)    data = client.recv(1024)    print(data)服务端import socket"""服务端    固定的ip和port    24小时不间断提供服务"""server = socket.socket()  # 生成一个对象server.bind(('127.0.0.1',8080))  # 绑定ip和portserver.listen(5)  # 半连接池while True:    conn, addr = server.accept()  # 等到别人来  conn就类似于是双向通道    print(addr)  # ('127.0.0.1', 51323) 客户端的地址    while True:        try:            data = conn.recv(1024)            print(data)  # b''  针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b''            if len(data) == 0:break            conn.send(data.upper())        except ConnectionResetError as e:            print(e)            break    conn.close()
View Code
#    ==================================================客户端import socketfrom 二_CMD程序 import smallToolimport structclient = socket.socket()try:    client.connect(("127.0.0.1",1688))    print("链接成功!")    while True:        msg = input("请输入要执行指令:").strip()        if msg == "q": break        if not msg: continue        # 发送指令        # 先发长度        len_bytes = struct.pack("q",len(msg.encode("utf-8")))        client.send(len_bytes)        # 在发指令        client.send(msg.encode("utf-8"))        data = smallTool.recv_data(client)        print(data.decode("GBK"))    client.close()except ConnectionRefusedError as e:    print("链接服务器失败了!",e)except ConnectionResetError as e:    print("服务器挂了!", e)    client.close()import socketimport subprocessimport structfrom 二_CMD程序 import  smallToolserver = socket.socket(socket.AF_INET,socket.SOCK_STREAM)server.bind(("127.0.0.1",1688))server.listen()# backwhile True:    # socket,addr一个元组 客户端的ip和port    client,addr = server.accept()    print("客户端链接成功!")    # 循环收发数据    while True:        try:            cmd = smallTool.recv_data(client)            if not cmd:                break            print(cmd)            p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)            # 不要先读err错误信息  它会卡主  原因不详  linux不会有问题  tasklist  netstat - ano啥的            data = p.stdout.read()            err_data = p.stderr.read()            len_size = len(data) + len(err_data)            print("服务器返回了: %s " %  len_size)            len_bytes = struct.pack("q",len_size)            # 在发送真实数据前先发送 长度            client.send(len_bytes)            # 返回的结果刚好就是二进制            # 发送真实数据            client.send(data + err_data)        except ConnectionResetError as e:            print("客户端了挂了!",e)            break    client.close()#server.close()
View Code

 

二.socket使用中常见问题及解决办法:

1.端口占用

问题发生原因:

1.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!

2.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!

3.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接

解决的方案:

第1种原因,很简单关闭之前运行的服务器即可

第2,3中原因导致的问题,有两种解决方案:

from socket import SOL_SOCKET,SO_REUSEADDRsk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
View Code

2.粘包

1.什么是粘包:粘包指的是数据与数据之间没有明确的分界线,导致不能正确读取数据!

2.

粘包 仅发生在TCP协议中

  1. 发送端 发送的数据量小 并且间隔短 会粘

  2. 接收端 一次性读取了两次数据的内容 会粘

  3. 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起

无论是那种情况,其根本原因在于 接收端不知道数据到底有多少

解决方案就是 提前告知接收方 数据的长度

3.解决方法

先发长度给对方 再发真实数据

#发送端

 1.使用struct 将真实数据的长度转为固定的字节数据

 2.发送长度数据

 3.发送真实数据

接收端

 1.先收长度数据 字节数固定

 2.再收真实数据 真实可能很长 需要循环接收

发送端和接收端必须都处理粘包 才算真正的解决了

客户端import socketclient = socket.socket()client.connect(("127.0.0.1",1688))client.send("hello".encode("utf-8"))client.send("python".encode("utf-8"))client.close()服务端import socketserver = socket.socket()server.bind(("127.0.0.1",1688))server.listen()client,addr = server.accept()data1 =  client.recv(5)print(data1)data2 = client.recv(6)print(data2)client.close()
View Code

4.struct模块

但是由于negle优化机制的存在,长度信息和数据还是有可能会粘包,而接受方并不知道长度信息具体几个字节,所以现在的问题是如何能够长度信息做成一个固定长度的bytes数据

我们可以将字符串拼接为一个固定长度的字符 但是这样太麻烦,struct模块为我们提供了一个功能,可以将整数类型转换为固定长度的bytes,此时就派上用场了# struct模块使用import struct# 整型转bytesres = struct.pack("i",100)print(res)print(len(res))# bytes转整型res2 = struct.unpack("i",res) # 返回一个元组print(res2)print(res2[0])
View Code
客户端import socketimport structc = socket.socket()c.connect(("127.0.0.1",8888))while True:    cmd = input(">>>:").strip()    c.send(cmd.encode("utf-8"))    data = c.recv(4)    length = struct.unpack("i",data)[0]        print(length)    size = 0    res = b""    while size < length:        temp = c.recv(1024)        size += len(temp)        res += temp    print(res.decode("gbk"))服务端import socketimport subprocessimport structserver = socket.socket()server.bind(("127.0.0.1",8888))server.listen()while True:    client, addr = server.accept()    while True:        cmd = client.recv(1024).decode("utf-8")        p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1)        data = p.stdout.read()+p.stderr.read()        length = len(data)        len_data = struct.pack("i",length)        client.send(len_data)        print(length)        client.send(data)
View Code

自定义报头解决粘包

1.什么是报头:当需要在传输数据时 传呼一些额外参数时就需要自定义报头例如我们要实现文件上传下载,不光要传输文件数据,还需要传输文件名字,md5值等等,报头本质是一个json 数据。

2.

客户端import socketimport structimport jsonclient = socket.socket()client.connect(('127.0.0.1',8080))while True:    msg = input('>>>:').encode('utf-8')    if len(msg) == 0:continue    client.send(msg)    # 1.先接受字典报头    header_dict = client.recv(4)    # 2.解析报头 获取字典的长度    dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0    # 3.接收字典数据    dict_bytes = client.recv(dict_size)    dict_json = json.loads(dict_bytes.decode('utf-8'))    # 4.从字典中获取信息    print(dict_json)    recv_size = 0    real_data = b''    while recv_size < dict_json.get('file_size'):  # real_size = 102400        data = client.recv(1024)        real_data += data        recv_size += len(data)    print(real_data.decode('gbk'))# 服务端import socketimport subprocessimport structimport jsonserver = socket.socket()server.bind(('127.0.0.1',8080))server.listen(5)while True:    conn, addr = server.accept()    while True:        try:            cmd = conn.recv(1024)            if len(cmd) == 0:break            cmd = cmd.decode('utf-8')            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)            res = obj.stdout.read() + obj.stderr.read()            d = {
'name':'jason','file_size':len(res),'info':'asdhjkshasdad'} json_d = json.dumps(d) # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 2.发送字典报头 conn.send(header) # 3.发送字典 conn.send(json_d.encode('utf-8')) # 4.再发真实数据 conn.send(res) # conn.send(obj.stdout.read()) # conn.send(obj.stderr.read()) except ConnectionResetError: break conn.close()
View Code

 

转载于:https://www.cnblogs.com/tfzz/p/11317612.html

你可能感兴趣的文章
使用spring dynamic modules的理由
查看>>
Leetcode 117 Populating Next Right Pointers in Each Node 2
查看>>
C++ Primer 第四版中文版
查看>>
变量关系
查看>>
NTP工作机制及时间同步的方法
查看>>
近段时间学习html和CSS的一些细碎总结
查看>>
第三章 栈和队列
查看>>
「Vue」v-html生成的图片大小无法调整的解决办法
查看>>
【BZOJ 4665】 4665: 小w的喜糖 (DP+容斥)
查看>>
Git 的 .gitignore 配置
查看>>
Language Integrated Query ----序
查看>>
【HDU】1542 Atlantis
查看>>
解决Android SDK Manager更新时出现问题
查看>>
Android Studio下“Error:Could not find com.android.tools.build:gradle:2.2.1”的解决方法
查看>>
第二章 第四节 添加SWT库
查看>>
docker file
查看>>
总结一些常见的国际标准化组织
查看>>
webpack 使用经验记录
查看>>
sanic连接mongo
查看>>
Set集合HashSet,TreeSet
查看>>