socker通信struct模块粘包问题

Socket概念

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

一、socker层 (在程序中就是一个模块功能可以直接导入使用)

  Socker 是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,其实就i是一个门面模式,把复杂的协议放在socker后面。

IP地址: 127.0.0.1是本机回还地址,只能自己识别自己,其他人无法访问,用于python代码客户端和服务端的测试

二、 套接字(socker)的发展史

1:基于文件类型的套接字家族:AF_UNIX(一切皆文件)

2:基于网络类型的套接字家族:AF_INET(被用于ipv6)

三、tcp协议和udp协议

  TCP:可靠的面向连接的协议(如:打电话),传输效率低于全双工通信

  UDP:不可靠的、无连接的服务,传输效率高,一对一,一对多

TCP和TCP间的通信

基于TCP协议的socket

server端

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字
sk.listen()          #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024)  #接收客户端信息
print(ret)       #打印客户端信息
conn.send(b'hi')        #向客户端发送信息
conn.close()       #关闭客户端套接字
sk.close()        #关闭服务器套接字(可选)

client端

import socket
sk = socket.socket()           # 创建客户套接字
sk.connect(('127.0.0.1',8898))    # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024)         # 对话(发送/接收)
print(ret)
sk.close()            # 关闭客户套接字

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

基于UDP协议的socket

server端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)
udp_sk.close()                         # 关闭服务器套接字

client端

import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接

 四、套接字(socker)的使用

  client(客户端)关键字:connect  send  recv 

  server(服务端)关键字:bind    listen  accept   recv  send  conn.slose()

  TCP协议是基于连接的,必须先启动服务端,然后在启动客户端去连接服务端:

server:服务端

import  socket

""""
实现服务端24小时不间断的服务功能,固定的IP和port  
"""
server = socket.socket()  # 生成一个对象
server.bind(('127.0.0.1',8080))  # 绑定ip和port
server.listen(5)  # 半连接池
"""
用两层循环 实现客户端与服务端之间的循环交互式
"""
while True:
    conn,addr = server.accept() # 循环接收用户端的请求
    print(addr)
    while True:
        try:# 解决报错抛出的异常处理(位置,类型,)
            data = conn.recv(1024)
            print(data)
            if len(data) == 0 : break # 针对mac和 Linux 客户端异常退出之后
            conn.send(data.upper()) # 返转成大写
        except ConnectionRefusedError as e:  # 提示的报错信息
            print(e)
            break
    conn.close()

client:客户端

import socket
client = 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)

send(发出)与recv(接收)对应关系,不能出现两边都相同的情况

recv 是跟内存要数据的,

TCP的特点:

 会将数据量比较小的并且时间间隔比较短的数据
 一次性打包发送给对方

利用socker模块,用代码实现了服务端与客户端的实际交互情况,IP地址和pore端口地址必须完全匹配,其中注意一些概念 如 server.listen(5)  # 半连接池  指的是规定客户端访问的量,报错的异常处理。

一个作为接收端,一个作为反馈请求端,所用的参数也不一样  

五、黏包

会发生黏包的两种情况
情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

  黏包:指的是当客户端同时请求所传输的内容过大,过长是,服务端反馈的结果可能只有其中的一部分,显示不全,在执行其他命令的时候又接受收到了之前执行的另外一部分的结果。

  补充的subprocess子进程模块

import subprocess
cmd = input('cmd>>>:')
obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
print(obj.stdout.read().decode('gbk'))  # 正确命令返回的结果
print(obj.stderr.read().decode('gbk'))  # 错误的命令返回的结果

# subprocess获取到的数据 拿完就没有了  不能重复的拿
# print(obj.stdout.read().decode('gbk'))  # 正确命令返回的结果
# print(obj.stderr.read().decode('gbk'))  # 错误的命令返回的结果

 只能单次获取请求的数据,取完就没了, 不能重复的取

该模块可以在python解释器里,实现终端的请求命令行执行并打印结果:

它的功能以及常用的操作

# subprocess模块
# 1.用户通过网络连接上了你的这台电脑
# 2.用户输入相应的命令 基于网络发送给了你这台电脑上某个程序
# 3.获取用户命令 里面subprocess执行该用户命令
# 4.将执行结果再基于网络发送给用户
# 这样就实现  用户远程操作你这台电脑的操作
# ''

struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes

  struct.pack:打包 

   struct.unpack解包  

   有“i” ’q‘等模式,是处理数据大小的等级

  dict_size = struct.unpack('i',header_dict)[0]   # 解包的时候一定要加上索引0 

  服务端:结合json模块 dumps序列化成一个字典,制作一个报头

  客户端:用loads把字典反序列化成字符串出来,读取报头内容

当有黏包现象存在时如何解决?(即数据过大过长时)

  服务端client:

  1:先制作一个发送给客户端色字典

  2:制作字典的报头

  3:发送字典的报头

  4:再发真实的数据

  客户端srever:

    1.先接受字典的报头
    2.解析拿到字典的数据长度
    3.接受字典
    4.从字典中获取真实数据的长度
    5.接受真实数据

用字典打包好报头,获取固定的长度后在传输

内置函数构造:

简单黏包问题的存在,接收的数据和传出去的内容不一致导致。

client 客户端

import socket

client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port

client.send(b'hello')
client.send(b'baby')

server:服务端

import socket

server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8080))  # bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)  # 开机    半连接池

conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
print(data)

注意:只有TCP有粘包现象,UDP永远不会粘包

实现解决黏包的问题?

客户端:client

import socket
import struct
import json

client = 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'))


"""
1.如何将对方发送的数据收干净
解包

服务端:server

import socket
import subprocess
import struct
import json


server = 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()
制作包

文件的上传和下载

 (重点掌握,也是基于tcp协议的一个粘包问题,利用打包和解包)

import socket
import json
import os
import struct

client= socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    MOVIE_DIR = r'E:\python脱产10期视频\day29\视频'
    movie_list= os.listdir(MOVIE_DIR)
    for i,movie in enumerate(movie_list,1): # 枚举列出所有数据
        print(i,movie)
        # 用户选择
    choice=input("请选择你要下载的视频>>:")
    if choice.isdigit():
        choice = int(choice)-1
        if choice in range(0,len(movie_list)):
            path = movie_list[choice]
            file_path = os.path.join(MOVIE_DIR,path)
            file_size = os.path.getsize(file_path)
            res_d={
                'file_name':"精彩视频要你好看.MP4",
                'file_size':file_size,
                'msg':"做个年少有为的青年"
            }
            json_d = json.dumps(res_d)
            json_bytes = json_d.encode('utf-8')
            header = struct.pack('i',len(json_bytes))

            client.send(header)
            client.send(json_bytes)
            with open (file_path,'rb') as f:
                for line in f :
                    client.send(line)
        else:
            print("not in range")

    else:
        print("must be a number")


        """
        if else 的使用 先把正确逻辑的代码写好,后面再考虑搭配来写else的其他情况
        
        
        """
上传文件
import json
import socket
import struct


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
    conn,addr = server.accept()
    while True:
        try:
            header_len = conn.recv(4)
            # 解析字典报头
            header_len=struct.unpack('i',header_len)[0]
            # 再接收字典数据
            header_dic = conn.recv(header_len)
            real_dic = json.loads(header_dic.decode('utf-8'))
            total_size = real_dic.get('file_size')
            recv_size = 0
            with open (real_dic.get('file_name'),'wb') as f:
                while recv_size < total_size:
                    data = conn.recv(1024)
                    f.write(data)
                    recv_size+= len(data)
                    print("上传成功!")
        except ConnectionRefusedError as e:
            print(e)
            break
    conn.close()
文件下载

 简易版本的QQ实现:

  QQ服务端:

import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
    data,adder = server.recvfrom(1024)
    msg= input(">>>>:")
    server.sendto(msg.encode('utf-8'),adder)

QQ客户端:

import socket


client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)

while True:
    msg = input('>>>:')
    msg = '来自客户端1的消息:%s'%msg
    client.sendto(msg.encode('utf-8'),server_address)
    data, server_addr = client.recvfrom(1024)
    print(data.decode('utf-8'))

 

  

原文地址:https://www.cnblogs.com/Gaimo/p/11317311.html