Python成长之路——socket《二》-程序员宅基地

技术标签: socket  Python  

接上一篇Python成长之路——socket《一》

黏包的解决方法
  • 方案一
    解决黏包的问题方法在于先找到问题的根源,接收端不知道发送端要发送多长的字节流长度,所以需要在发送端在发送数据之前,将要发送字节流的长度告知接收端,接收端用一个死循环接收所有的数据。
    在这里插入图片描述
    server端

    import socket, subprocess
    
    ip_port = ('127.0.0.1', 8080)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    s.bind(ip_port)
    s.listen(5)
    
    while True:
        conn, addr = s.accept()
        print('客户端', addr)
        while True:
            msg = conn.recv(1024)
            if not msg: break
            res = subprocess.Popen(msg.decode('gbk'), shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
            err = res.stderr.read()
            if err:
                ret = err
            else:
                ret = res.stdout.read()
            data_length = len(ret)
            conn.send(str(data_length).encode('gbk'))
            data = conn.recv(1024).decode('gbk')
            if data == 'recv_ready':
                conn.sendall(ret)
        conn.close()
    

    client端

    # _*_coding:utf-8_*_
    import socket, time
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    res = s.connect_ex(('127.0.0.1', 8080))
    
    while True:
        msg = input('>>: ').strip()
        if len(msg) == 0: continue
        if msg == 'quit': break
    
        s.send(msg.encode('gbk'))
        length = int(s.recv(1024).decode('gbk'))
        s.send('recv_ready'.encode('gbk'))
        send_size = 0
        recv_size = 0
        data = b''
        while recv_size < length:
            data += s.recv(1024)
            recv_size += len(data)
        print(data.decode('gbk'))
    

    但是这样做的话也会存在一定的问题,就是程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

  • 方案二
    方案一是多了一次发送请求,现在我们可以借助一个模块struct,帮我们解决这个问题,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
    该模块可以把一个类型,如数字,转成固定长度的bytes

    >>> struct.pack('i',1111111111111)
    
    struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
    

    在这里插入图片描述

  • 远程执行命令的程序
    借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

    发送时 接收时
    先发送struct转换好的数据长度4字节 先接受4个字节使用struct转换成数字来获取要接收的数据长度
    再发送数据 再按照长度接收数据

    server端

    import socket, subprocess,struct
    
    ip_port = ('127.0.0.1', 8080)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    s.bind(ip_port)
    s.listen(5)
    
    while True:
        conn, addr = s.accept()
        print('客户端', addr)
        while True:
            msg = conn.recv(1024)
            if not msg: break
            res = subprocess.Popen(msg.decode('gbk'), shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE,
                                   stdout=subprocess.PIPE)
            err = res.stderr.read()
            if err:
                ret = err
            else:
                ret = res.stdout.read()
            data_length = len(ret)
            num = struct.pack('i',data_length)
            conn.send(num)
            conn.sendall(ret)
        conn.close()
    

    client端

    # _*_coding:utf-8_*_
    import socket, time,struct
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    res = s.connect_ex(('127.0.0.1', 8080))
    
    while True:
        msg = input('>>: ').strip()
        if len(msg) == 0: continue
        if msg == 'quit': break
    
        s.send(msg.encode('gbk'))
        num = s.recv(4)
        length = struct.unpack('i',num)[0]
        send_size = 0
        recv_size = 0
        data = b''
        while recv_size < length:
            data += s.recv(1024)
            recv_size += len(data)
        print(data.decode('gbk'))
    
  • 文件上传小案例来了解struct
    我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

    发送时 接收时
    先发报头长度 先收报头长度,用struct取出来
    再编码报头内容然后发送 根据取出的长度收取报头内容,然后解码,反序列化
    最后发真实内容 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
    import socket
    import struct
    import json
    
    sk = socket.socket()
    sk.bind(('127.0.0.1',8088))
    sk.listen()
    buffer = 1024
    conn,addr = sk.accept()
    # 先收报头4个bytes,得到报头长度的字节格式
    head_len = conn.recv(4)
    # 提取报头的长度
    head_len = struct.unpack('i',head_len)[0]
    # 按照报头长度x,收取报头的bytes格式
    json_head = conn.recv(head_len).decode('utf-8')
    # 提取报头
    head = json.loads(json_head)
    filesize = head['filesize']
    with open(head['filename'],'wb') as f:
        while filesize:
            if filesize >= buffer:
                content = conn.recv(buffer)
                f.write(content)
                filesize -= buffer
            else:
                content = conn.recv(filesize)
                f.write(content)
                break
    conn.close()
    sk.close()
    

    client端

    import socket
    import os
    import json
    import struct
    
    sk = socket.socket()
    sk.connect(('127.0.0.1',8088))
    buffer = 1024
    # 为了黏包,必须自定制报头
    header = {
          'filepath':r'C:\Users\xxxxxx\Desktop','filename':r'xxx.txt','filesize':None}
    file_path = os.path.join(header['filepath'],header['filename'])
    file_size = os.path.getsize(file_path)
    header['filesize'] = file_size
    # 将字典类型转换成字符串类型
    json_header = json.dumps(header)
    # 为了该报头能传送,需要序列化并且转为bytes
    bytes_header = json_header.encode('utf-8')
    header_len = len(bytes_header)
    # 为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
    pack_len = struct.pack('i',header_len)
    # 先发报头的长度,4个bytes
    sk.send(pack_len)
    # 再发报头的字节格式
    sk.send(bytes_header)
    
    with open(file_path,'rb') as f:
        while file_size:
            if file_size >= buffer:
                content = f.read(buffer)
                # 然后发真实内容的字节格式
                sk.send(content)
                file_size -= buffer
            else:
                content = f.read(file_size)
                sk.send(content)
                break
    sk.close()
    
socket的更多方法介绍

服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 发送TCP数据
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件

  • socket.send()和socket.sendall()的用法
  1. send()的返回值是发送的字节数量,这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误则会抛出异常。
  2. 尝试发送string的所有数据,成功则返回None,失败则抛出异常。
    # 代码一
    sock.sendall('Hello world\n')
    # 代码二
    buffer = 'Hello world\n'
    while buffer:
        bytes = sock.send(buffer)
        buffer = buffer[bytes:]
    
    代码一和代码二是等价的
验证客户端链接的合法性

如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现
server端

import socket
import os
import hmac

sercet_key = b'tm'
sk = socket.socket()
sk.bind(('127.0.0.1',8088))
sk.listen()

def check_hf(conn):
    msg = os.urandom(32)
    conn.send(msg)
    h = hmac.new(sercet_key,msg)
    digest = h.digest()
    client_digest = conn.recv(1024)
    return hmac.compare_digest(digest,client_digest)

conn,addr = sk.accept()
ret = check_hf(conn)
if ret:
    print("合法的客户端请求")
else:
    print("不合法的客户端请求")

conn.close()
sk.close()

client端

import socket
import hmac
sercet_key = b'tm'
sk = socket.socket()
sk.connect(('127.0.0.1',8088))
msg = sk.recv(1024)

h = hmac.new(sercet_key,msg)
digest = h.digest()
sk.send(digest)
sk.close()
socketserver

server端

import socketserver
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999

    # 设置allow_reuse_address允许服务器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 创建一个server, 将服务地址绑定到127.0.0.1:9999
    server = socketserver.TCPServer((HOST, PORT),Myserver)
    # 让server永远运行下去,除非强制停止程序
    server.serve_forever()

client端

import socket

HOST, PORT = "127.0.0.1", 9999
data = "hello"

# 创建一个socket链接,SOCK_STREAM代表使用TCP协议
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
   sock.connect((HOST, PORT))          # 链接到客户端
   sock.sendall(bytes(data + "\n", "utf-8")) # 向服务端发送数据
   received = str(sock.recv(1024), "utf-8")# 从服务端接收数据

print("Sent:     {}".format(data))
print("Received: {}".format(received))
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/changqi008/article/details/89237563

智能推荐

win10 下 pycocotools的安装问题(ruuning bulid_ext 系统找不到指定的文件)-程序员宅基地

原版的cocoapi是不支持windows的,后来有人进行了改写。改写后可用于Windows系统了,改写的作者也给出了安装的方案:1、下载源码在终端运行如下指令:pip install git+https://github.com/philferriere/cocoapi.git#subdirectory=PythonAPI由于网络的问题有可能下载很慢或下载出错,所以可以直接区g...

文件包含漏洞_jsp文件包含漏洞_cxdh47的博客-程序员宅基地

文件包含漏洞 PHP包含 PHP中提供了四个文件包含的函数,分别是 include()、include once()、require()和require_once()。这四个函数都可以进行文件包含,但作用却不一样,其区别如下: 文件包含示例 本地包含 Local File Include (LFI) Index.php对ArrayUtil.php进行包含,并且使用PrintArr 函数 phpinfo.txt文件 _jsp文件包含漏洞

PgSQL——学习笔记15:ALTER TABLE 命令 & TRUNCATE TABLE_pgsql alter table_艺晨星的博客-程序员宅基地

目录ALTER TABLE 命令:1.添加列的语法如下:——ADD2.删除列的语法如下:——DROP COLUMN3.修改表中某列的 DATA TYPE(数据类型)的语法如下:——ALTER COLUMN4.给表中某列添加 NOT NULL 约束的语法如下:5.给表中某列添加 UNIQUE 约束的语法如下:6. 给表中某列添加 CHECK 约束的语法如下:7. 给表中某列添加 PRIMARY KEY 主键的语法如下:8.删除约束的语法如下:9.删除主键的语法如..._pgsql alter table

java keyword_Java之关键字(keyword)和保留字(reserved word)-程序员宅基地

呵呵,不管别的,来几条题目先,作对了你牛^_^1, which of the following are keywords or reserved words in java?a) if b)then c)goto d)while e)case f)sizeof2, which of the following are java key words?a)double b)Switch c)then..._java reserved页

斐波那契输出第n项(递归 非递归)_求斐波那契疏解的第n项,非递归-程序员宅基地

#include<stdio.h>#define M 100//递归法int Fibo1(int n){ if(n==1||n==2){ return 1;//递归终止条件} else { return Fibo1(n-1)+Fibo1(n-2);//递归 }}//非递归法in..._求斐波那契疏解的第n项,非递归

struts 2学习进阶 初识struts2(一)-程序员宅基地

好久没有写代码了,最近准备学习一下struts2,我准备把我的学习路程写下来。呵呵Struts2与Struts1有很大的不同,Struts2是与webwork共同开发的,在Struts2中有很多xwork的jar包。看资料上说Struts2的核心是拦截器。本人菜鸟一个,还指望着敲代码混饭吃呢。废话不多,那就开始吧。在apache的网站上下载struts2的包,其中apps文件夹中包含

随便推点

在Linux服务器上配置tensorflow-gpu版(最详细教程)_linux安装tensorflow-程序员宅基地

由于基于tensorflow的神经网络在CPU上运行速度太慢,在GPU上运行速度会快很多,但是在linux系统上tensorflow-GPU运行配置比较麻烦,有很多坑需要踩,网上的很多教程和自己使用的环境不匹配,所以针对这个情况自己做一个总结,总结一下tensorflow-gpu版本在linux服务器上的配置过程,及其中遇到的问题,给大家提供参考和交流。环境:linux+Anac..._linux安装tensorflow

springCloud Feign服务之间远程调用丢失消息头原因_fegin 服务间调用 压测-程序员宅基地

原因:因为浏览器请求第一个模块携带的token和各类消息头在第一个模块调用第二个模块的时候会重构请求,这个请求是没有消息头,导致第一个请求的消息头到第二个模块会丢失解决方案:使用请求拦截器 保存第一个请求的 headers@Componentpublic class FeignAuthRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTempla_fegin 服务间调用 压测

ILRuntime热更新,生成dll到StreamingAssets,会生成引用的unity内部dll_离遇匆匆的博客-程序员宅基地

在生成Dll的时候会把引用的unity内部Dll也给生成到StreamingAssets文件夹下。解决办法吧引用的程序集的属性 “复制到本地”=“false”。

软考复盘:我的一些复习经验分享-程序员宅基地

大家好,我是Edison。最近全身乏力头疼,38.5度高烧,好在症状较轻,经过一天躺平加吃了芬必得(简直神药)后,退烧了,也不乏力了,也就趁娃娃睡觉时间跟大家分享一下软考的复习经验,相信大家已经期待已久了。今年(2022)复习备考参加了软考高级资格中的系统架构设计师考试,为了多年前的一个所谓的高级职称资格的心愿,刚好也幸运地通过了考试(每个科目45分合格,需三个科目同时合格)。今天跟大家再次介绍一..._软考复习

双目视觉之相机标定_双目摄像头标定_静思心远的博客-程序员宅基地

双目视觉之相机标定目录一、三大坐标系1.1 图像坐标系到像素坐标系1.2 世界坐标系到摄像机坐标系1.3 摄像机坐标系到图像坐标系1.4 总结二、图片矫正2.1 径向畸变2.2 切向畸变三、张氏标定法四、使用opencv实现单目标定去年三四月份实验室做了一个机器人与视觉识别系统的项目,主要就是利用双目摄像头进行物体空间坐标定位,然后利用机器人进行抓取物体。当时我才研一,还是个菜鸡,项目主要是几个学长负责做的,我也就是参与打打酱油,混混经验。现在过了一年多了,机器人一直在实验室放着,空_双目摄像头标定

QQuickPaintedItem鼠标精准拾取(pick/select)研究-程序员宅基地

QT C++在2D图形方面已经做的很完善了,在PC端(Windows、Linux和MaC)上都有很好的表现。QT中的QML特别适合于移动端应用的开发,QML中的一些基本形状类型并不是一一地与Qt C++相对应,但是通过C++可以来扩展QML。QQuickPaintedItem继承自QQuickItem,提供了使用QPainter API的方法来扩展QML中的2D图形项。QQuickPa..._qquickpainteditem

推荐文章

热门文章

相关标签