python-基于tcp协议的套接字(加强版)及粘包问题

简介: 一、基于tcp协议的套接字(通信循环+链接循环) 服务端应该遵循:   1.绑定一个固定的ip和port   2.一直对外提供服务,稳定运行   3.能够支持并发   基础版套接字: from socket import * server = socket(AF_INET, SOCK_STREAM) server.

一、基于tcp协议的套接字(通信循环+链接循环)

服务端应该遵循:

  1.绑定一个固定的ip和port

  2.一直对外提供服务,稳定运行

  3.能够支持并发

 

基础版套接字:

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

conn, client_addr = server.accept()

# 通信循环
while True:
    data = conn.recv(1024)
    conn.send(data.upper())

conn.close()
server.close()
server
from socket import *

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

# 通信循环
while True:
    msg=input('>>: ').strip()
    client.send(msg.encode('utf-8'))
    data=client.recv(1024)
    print(data)

client.close()
client

以上的程序存在两个bug

  1.当客户端单方面终止程序时,服务端抛出异常(linux可以用判断是否为空来处理)

  2.recv收到空时,一直在等待。

解决方法:

  1.异常捕获

  2.再输入时进行判断

改进版:

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8082))
server.listen(5)

conn, client_addr = server.accept()
print(client_addr)

# 通信循环
while True:
    try:
        data = conn.recv(1024)
        if len(data) == 0:break # 针对linux系统
        print('-->收到客户端的消息: ',data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()
server
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    msg=input('>>: ').strip() #msg=''
    if len(msg) == 0:continue
    client.send(msg.encode('utf-8')) #client.send(b'')
    # print('has send')
    data=client.recv(1024)
    # print('has recv')
    print(data)

client.close()
client

链接循环:服务器改进

from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8082))
server.listen(5)

conn, client_addr = server.accept()
print(client_addr)

# 通信循环
while True:
    try:
        data = conn.recv(1024)
        if len(data) == 0:break # 针对linux系统
        print('-->收到客户端的消息: ',data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()
server

模拟ssh实现远程执行命令

from socket import *
import subprocess

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 链接循环
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通信循环
    while True:
        try:
            cmd = conn.recv(1024) #cmd=b'dir'
            if len(cmd) == 0: break  # 针对linux系统
            obj=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=obj.stdout.read()
            stderr=obj.stderr.read()
            print(len(stdout) + len(stderr))
            conn.send(stdout+stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()
server
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    cmd_res=client.recv(1024000)
    print(cmd_res.decode('gbk'))

client.close()
client

recv其实是和本地计算机要数据,所以解码的时候应该用gbk格式

二、粘包

注:只有tcp存在粘包现象,udp永远不存在粘包。

tcp是面向流的协议,发送端可以1k,1k的发送数据,而接收端可以2k,2k的提取数据,发送方往往收集到足够多的数据后才生成一个tcp段,若连续几次需要发送的数据都很少,通常tcp会根据(Nagle)优化算法,把这些数据合成一个TCP段后发出去,这样接收方就收到了粘包数据。

总的来说,粘包问题就是因为接收方不知道消息之间的界限,不知道一次性提取多少字节造成的。

解决方法:(服务端)为字节流加上一个报头,将这个报头(字典形式)json序列化,编码。然后用struct发送报头的长度,再发送报头,最后发送真实数据

     (客户端)先解出报头的长度(struct),再接收报头,将拿到的报头解码再反序列化,得到报头字典,最后接收真正的数据

# 服务端必须满足至少三点:
# 1. 绑定一个固定的ip和port
# 2. 一直对外提供服务,稳定运行
# 3. 能够支持并发
from socket import *
import subprocess
import struct
import json

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8081))
server.listen(5)

# 链接循环
while True:
    conn, client_addr = server.accept()
    print(client_addr)

    # 通信循环
    while True:
        try:
            cmd = conn.recv(1024)  # cmd=b'dir'
            if len(cmd) == 0: break  # 针对linux系统
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            # 1. 先制作报头
            header_dic = {
                'filename': 'a.txt',
                'md5': 'asdfasdf123123x1',
                'total_size': len(stdout) + len(stderr)
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode('utf-8')

            # 2. 先发送4个bytes(包含报头的长度)
            conn.send(struct.pack('i', len(header_bytes)))
            # 3  再发送报头
            conn.send(header_bytes)

            # 4. 最后发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()
server
from socket import *
import struct
import json

client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8081))

# 通信循环
while True:
    cmd=input('>>: ').strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode('utf-8'))
    #1. 先收4bytes,解出报头的长度
    header_size=struct.unpack('i',client.recv(4))[0]

    #2. 再接收报头,拿到header_dic
    header_bytes=client.recv(header_size)
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']

    #3. 接收真正的数据
    cmd_res=b''
    recv_size=0
    while recv_size < total_size:
        data=client.recv(1024)
        recv_size+=len(data)
        cmd_res+=data

    print(cmd_res.decode('gbk'))

client.close()
client

补充:struct模块

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

1、 struct.pack
struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。其函数原型为:struct.pack(fmt, v1, v2, ...),参数fmt是格式字符串,关于格式字符串的相关信息在下面有所介绍。v1, v2, ...表示要转换的python值。

2、 struct.unpack
struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组。 

import struct

obj1=struct.pack('i',13321111)
print(obj1,len(obj1))#b'\x97C\xcb\x00' 4

res1=struct.unpack('i',obj1)
print(res1[0])#13321111
View Code

 

焚膏油以继晷,恒兀兀以穷年。
相关文章
|
25天前
|
网络协议 网络安全 网络虚拟化
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算
本文介绍了十个重要的网络技术术语,包括IP地址、子网掩码、域名系统(DNS)、防火墙、虚拟专用网络(VPN)、路由器、交换机、超文本传输协议(HTTP)、传输控制协议/网际协议(TCP/IP)和云计算。通过这些术语的详细解释,帮助读者更好地理解和应用网络技术,应对数字化时代的挑战和机遇。
69 3
|
1月前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
49 13
|
1月前
|
网络协议 算法 网络性能优化
计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
计算机网络常见面试题(一):TCP/IP五层模型、应用层常见的协议、TCP与UDP的区别,TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议、ARP协议
|
2月前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
151 3
|
2月前
|
XML JSON 网络协议
【TCP/IP】自定义应用层协议,常见端口号
【TCP/IP】自定义应用层协议,常见端口号
34 3
|
2月前
|
测试技术 网络安全 数据安全/隐私保护
Paramiko是一个用于处理SSHv2协议的Python库
Paramiko是一个用于处理SSHv2协议的Python库
37 3
|
3月前
|
数据采集 JSON API
🎓Python网络请求新手指南:requests库带你轻松玩转HTTP协议
本文介绍Python网络编程中不可或缺的HTTP协议基础,并以requests库为例,详细讲解如何执行GET与POST请求、处理响应及自定义请求头等操作。通过简洁易懂的代码示例,帮助初学者快速掌握网络爬虫与API开发所需的关键技能。无论是安装配置还是会话管理,requests库均提供了强大而直观的接口,助力读者轻松应对各类网络编程任务。
122 3
|
3月前
|
机器学习/深度学习 JSON API
HTTP协议实战演练场:Python requests库助你成为网络数据抓取大师
在数据驱动的时代,网络数据抓取对于数据分析、机器学习等至关重要。HTTP协议作为互联网通信的基石,其重要性不言而喻。Python的`requests`库凭借简洁的API和强大的功能,成为网络数据抓取的利器。本文将通过实战演练展示如何使用`requests`库进行数据抓取,包括发送GET/POST请求、处理JSON响应及添加自定义请求头等。首先,请确保已安装`requests`库,可通过`pip install requests`进行安装。接下来,我们将逐一介绍如何利用`requests`库探索网络世界,助你成为数据抓取大师。在实践过程中,务必遵守相关法律法规和网站使用条款,做到技术与道德并重。
55 2
|
3月前
|
数据采集 存储 JSON
从零到一构建网络爬虫帝国:HTTP协议+Python requests库深度解析
在网络数据的海洋中,网络爬虫遵循HTTP协议,穿梭于互联网各处,收集宝贵信息。本文将从零开始,使用Python的requests库,深入解析HTTP协议,助你构建自己的网络爬虫帝国。首先介绍HTTP协议基础,包括请求与响应结构;然后详细介绍requests库的安装与使用,演示如何发送GET和POST请求并处理响应;最后概述爬虫构建流程及挑战,帮助你逐步掌握核心技术,畅游数据海洋。
73 3
|
3月前
|
数据采集 网络协议 API
HTTP协议大揭秘!Python requests库实战,让网络请求变得简单高效
【9月更文挑战第13天】在数字化时代,互联网成为信息传输的核心平台,HTTP协议作为基石,定义了客户端与服务器间的数据传输规则。直接处理HTTP请求复杂繁琐,但Python的`requests`库提供了一个简洁强大的接口,简化了这一过程。HTTP协议采用请求与响应模式,无状态且结构化设计,使其能灵活处理各种数据交换。
86 8