socket基于TCP(粘包现象和处理)

6socket套接字

socket网络套接字

什么是socket

  • 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
  • ​ sock是处于应用层和传输层之间的抽象层,1他是一组操作起来非常简单的接口(接收数据)此接口接收数据之后,交由操作系统
  • 为什么存在socket抽象层
  • 如果直接与操作系统数据交互非常麻烦,繁琐,socket对这些繁琐的操作高度的封装和简化
  • socket在python中就是一个模块。

7基于TCP协议的socket简单的网络通信

import socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

一对一

简单的socket抽象层 定义一些简单的接口 留下一个接口

1.socket处于应用层和传输层之间,提供了一些简单的接口,避免与操作系统直接对接,省去了相当繁琐复杂的过程

2.socket 在python中就是一个模块

不考虑缓存区的大小 发送是没有限制的虽然不限制但是不会一次性发送过多

有收必有发,send发--recv接收

AF_UNIX

基于套接字家族的名字:

unix一切皆文件,基于文件的套接字(dicheng1wen),运行同一机器,通过访问同一个文件系统完成通信

AF_INET(应用最广泛的一个)

(AF_INET6 ipv6)

encode转码 类字节 base()转码类字节

执行的时候端口被占有 长连接

如果进入需要等待

收发是成对的 有发必有收,

遇到一个recv 就阻塞 写两个就卡住

base类型:

​ ASCII字符:在字符串前面b' '

非ASCII字符:encode 转化成 bytes类型

报错类型

ConnectionRefusedError #服务端没开 报错
ConnectionResetError #服务端崩溃
ConnectionAbortedError#客户端退出

单一

#server端
import socket#调用socket
sk=socket.socket()#使用socket里面的socket
#相当于买手机
sk.bind(('127.0.0.1',8080))#bind(里面放入一个元组('ip',端口号))绑定手机卡
sk.listen(5)#监听 等着有人给我打电话#阻塞 5是允许5个人 链接我,剩下的链接也可以链接,等待 
#最大等待链接数5个
#允许5个进入半链接池
#()是和电脑性能有关 #

conn,addr=sk.accept()#接收到别人的电话 connection 连接管道 address地址 来电显示 #阻塞
ret=conn.recv(1024)#收听别人说话的长度#1024个字节#等消息
print(ret)
ret=conn.recv(1024)#等消息
print(ret.decode('utf-8'))#要解码
conn.send(b'1')#和别人说话,必须传一个bytes类型
conn.close()#挂电话#关闭链接
sk.close()#关手机#关闭socket对象

##重用ip地址
#client端
import socket
sk=socket.socket()#买手机
“”“
sk.setsockopt(socket.SQL_SOCKET,socket.SO_REUSEADDR,1)#允许socket重用避免服务重启的时候 address already in use
”“”“
sk.connect(('127.0.0.1',8080))#拨别人的号

sk.send(b'hello')#发送消息
sk.send('是我的'.encode('utf-8'))#中文要转码成为bit
ret=sk.recv(1024)
print(ret)
conn.close()#挂电话
sk.close()#关手机

链接+循环通信

#client
import socket


phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#使用网络传输 tcp
try:
    phone.connect(('127.0.0.1', 8848))
except ConnectionRefusedError:
    exit('服务端没开')

while 1:
    try:
        data=input('强输入')
        phone.send(data.encode('gbk'))
        if not data:
            print('不能为空')
        elif data.upper() == 'Q':
            print('您退出了')
            break
        else:
            from_server_data=phone.recv(1024)

            print(f'来自服务的消息:{from_server_data.decode("gbk")}')
    except ConnectionResetError:
        print('服务端崩溃了连不上')
        break
    except ConnectionAbortedError:
        pass



phone.close()
#server
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8848))

#开机监听
while 1:#循环通信
    phone.listen()
    print('开启通信')

    conn,addr=phone.accept()#阻塞
    # s = input('请输入')
    # conn.send(s.encode('utf-8'))
    print(conn,addr)

#from_client_data.upper()b

    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接受1024个字节 阻塞
            if from_client_data.upper()==b'Q':
                print('客户正常退出了')
            print(f'来自可无端的消息:{from_client_data.decode("utf-8")}')
            from_server_data=input('》》').strip().encode('utf-8')
            conn.send(from_server_data)
        except ConnectionResetError:
            print('客户端强行断开了')
            break

远程命令

传输的就是cmd是 gbk类型的base

半连接池 listen

9.tcp 实例:远程执行命令

subprocesss#远程执行命令

# shell: 命令解释器,相当于调用cmd 执行指定的命令。
# stdout:正确结果丢到管道中。
# stderr:错了丢到另一个管道中。
# windows操作系统的默认编码是gbk编码。
#server端
import socket#引用socket模块
import subprocess#引用远程执行模块
import struct
server_config=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#执行tcp默认不写
server_config.bind(('127.0.0.1',8848))#写一个地址
while 1:
    server_config.listen(5)
    print('开始通信')
    conn,addr=server_config.accept()
    while 1:
        try:
            form_client_data=conn.recv(1024)
            if form_client_data.upper()==b'Q':
                #设置一个退出的按钮
                print('客户端退出了')
            else:
                obj=subprocess.Popen(form_client_data.encode('utf-8'),
                                    shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE,
                                    )
                result=obj.stdout.read()+obj.stderr.subprocess.read()
                #拼接到一起进行输出 如果输入的是错的正确的命令为空,如果输入的是正确的 错误的信息为空
                total_size=len(result)
                head_bytes=struct.pack('i',total_size)
                conn.send(head_bytes)
                conn.send(result)
          except ConnectionAbortedError:
            print(f'客户端{addr}断开')
            break
   conn.close()
server_config.close()
#client #客户端
import socket
import struct
client_cofig=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#AF_INET#SOCK_STREAM
client_cofig.connect(('127.0.0.1',8848))
while 1:
    s=input('1212')
    client_cofig.send(s.encode('utf-8'))
    head.client=client_cogif.recv(4)
    total.size=struct.unpack('i',head.client)
    total.ppt=b''
    while len(total.ppt)<total.size:
        total.ppt+=client.recv(1024)
    print(total.ppt.decode('gbk'))
client_cofig.close()

10.粘包现象

11.操作系统的缓存区

1.为什么出现粘包

缓冲区

缓冲区优点:

1.暂时存储一些数据

缓冲区存在 如果你的网络波动,保证数据的收发稳定,匀速

缓冲区一般大小是8k

缺点:造成了粘包现象之一

防止数据丢失

不会一次性发很多 缓冲区满了就先不发

没收完的东西存储在了缓冲区里 等下次取直接取 因为取的时间比发的时间快

send完直接recv从缓冲区取 取出剩余

12.什么情况下出现粘包

1.出现粘包的情况

粘包只会出现在tcp中

2.收发的本质

不一定要一收一发

​ 1.连续短暂的send多次(数据量很小),数据会统一发送出去(因为会等缓冲区满了之后封包才发送出去s)

会有一个停留 nigle算法 连续send多次

#client
# import socket
#
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
#
#
# phone.send(b'he')
# phone.send(b'll')
# phone.send(b'o')
#
#
# phone.close()
# Nigle算法

#server
# import socket
#
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# phone.listen(5)
#
#
# conn,addr = phone.accept()  # 等待客户端链接我,阻塞状态中
#
# from_client_data = conn.recv(1024)  # 最多接受1024字节
# print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
# conn.close()
# phone.close()

2.send数量过大 send超过限制对方recv接受的数量时 会暂存缓存区。

第二次recv时会将上一次没有recv完的剩余数据

收发现象

发多次收一次

发一次收多次

13.low解决粘包现象

如何解决 服务端发一次数据 10000字节,

客户端接受数据时,循环接受,1每次(至多)接收1024个字节直至将所有的字节全部接收完毕,将接收的数据拼接在一起,最后解码

遇到的问题:recv次数无法确定

​ 你发送总数据之前,先给我发一个总数据的长度:len()

然后在发送总数据

客户端:先接收一个长度接收字节因为有粘包,50000个字节

encode转字符串 int转出数字

struct模块 加个类似tcp 固定head头 是固定长度头 之后解包

然后再循环recv 控制循环条件是反解出来的在服务端制作的长度

14.recv工作原理

'''
源码解释:
Receive up to buffersize bytes from the socket.
接收来自socket缓冲区的字节数据,
For the optional flags argument, see the Unix manual.
对于这些设置的参数,可以查看Unix手册。
When no data is available, block untilat least one byte is available or until the remote end is closed.
当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
When the remote end is closed and all data is read, return the empty string.
关闭远程端并读取所有数据后,返回空字符串。
'''
----------服务端------------:
# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。

import socket

phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data1 = conn.recv(2)
print(from_client_data1)
from_client_data2 = conn.recv(2)
print(from_client_data2)
from_client_data3 = conn.recv(1)
print(from_client_data3)
conn.close()
phone.close()

# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。

import socket

phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data = conn.recv(1024)
print(from_client_data)
print(111)
conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
print(222)

conn.close()
phone.close()


# 3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。

import socket

phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8080))

phone.listen(5)

conn, client_addr = phone.accept()
from_client_data1 = conn.recv(1024)
print(from_client_data1)
from_client_data2 = conn.recv(1024)
print(from_client_data2)
from_client_data3 = conn.recv(1024)
print(from_client_data3)
conn.close()
phone.close()
------------客户端------------
# 1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)

phone.close()



# 2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
time.sleep(20)

phone.close()

# 3,验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
import socket
import time
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080))
phone.send('hello'.encode('utf-8'))
phone.close()

15.高大上版 解决粘包方式(自定制包头)

服务端
import socket
import subprocess
import json
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('192.168.14.97',8848))

#开机监听
while 1:
    phone.listen(2)
    print('开启通信')

    conn,addr=phone.accept()#阻塞
    # s = input('请输入')
    # conn.send(s.encode('utf-8'))
    print(conn,addr)

#from_client_data.upper()b

    while 1:
        try:
            from_client_data = conn.recv(1024)  # 至多接受1024个字节 阻塞
            if from_client_data.upper()==b'Q':
                print('客户正常退出了')
                break
            else:
                # print(f'来自可无端的消息:{from_client_data.decode("utf-8")}')
                # from_server_data=input('》》').strip().encode('utf-8')
                #
                obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                       shell=True,
                                       stdout=subprocess.PIPE,#正确命令
                                       stderr=subprocess.PIPE,# 错误命令

                                       )

                # s= # 正确命令
                # s2=obj.stderr.read().decode('gbk')  # 错误命令
                total_base=obj.stdout.read()+obj.stderr.read()#原本就是gbk格式的
                total_size=len(total_base)
                #1.制作自定义报头
                head_dic={'file_name':'text1',
                'md5':54645898788,
                'total_size':total_size}
                #2.json形式的报头
                head_dic_json=json.dumps(head_dic)
                #3.bytes形式包头(把json形式字符串改成字节)
                head_dic_json_bytes = json.dumps(head_dic).encode('utf-8')
                #4获取bytes的总字节数(int类型)
                len_head_dic_json_bytes=len(head_dic_json_bytes)
                #5将不固定的int总字节变成固定长度的4个字节
                four_head_bytes=struct.pack('i',len_head_dic_json_bytes)
                #6 发送固定的4个字节
                conn.send(four_head_bytes)
                #7发送包头数据
                conn.send( head_dic_json_bytes)
                #8发送总数据
                conn.send(total_base)
        except ConnectionResetError:
            print('客户端强行断开了')
            break
    conn.close()
phone.close()
客户端
import socket
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
    phone.connect(('192.168.14.97', 8848))
except ConnectionRefusedError:
    exit('服务端没开')

while 1:
    try:
        data=input('强输入')
        if not data:
            print('不能为空')
        elif data.upper() == 'Q':
            print('您退出了')
            break
        else:
            phone.send(data.encode('utf-8'))
            #获取的包头固定数据字节
            from_server_data = phone.recv(4)
            #转换获取的字节(变成int类型的长度)
            len_totals=struct.unpack('i',from_server_data)[0]
            #获得bytes类型字典的总字节数
            len_totals_bytes=phone.recv(len_totals)
            #获取betes的dic数据(变成json字符串类型)1
            len_totals_bytes_json=len_totals_bytes.decode('utf-8')
            #解开json字符串获取原类型
            len_totals_json_dic=json.loads(len_totals_bytes_json)
            #通过自定义包头的里面的长度获取数据
            total_ppt=b''
            while len(total_ppt)< len_totals_json_dic['total_size']:
                total_ppt+=phone.recv(1024)
            print(f'来自服务的消息:{total_ppt.decode("gbk")}')
            print(f'来自服务的消息:{len(total_ppt)}')
    except ConnectionResetError:
        print('服务端崩溃了连不上')
        break
    except ConnectionAbortedError:
        pass



phone.close()

转载于:https://www.cnblogs.com/strawberry-1/p/11377124.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值