网络编程(三)——通信循环、链接循环、粘包问题

通信循环、链接循环、粘包问题

一、通信循环

服务端和客户端可以进行连续的信息交流

from socket import *

ser_socket = socket(AF_INET, SOCK_STREAM)

ser_socket.bind((‘127.0.0.1‘, 8886))

ser_socket.listen(5)

conn, addr = ser_socket.accept()

while True:
    try:               # 抛出异常,若不抛出处理,一旦客户端强行退出,服务端就会报错
        data = conn.recv(1024)
        print(data.decode(‘utf-8‘))

        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()

ser_socket.close()

通信循环服务端

from socket import *

cli_socket = socket(AF_INET, SOCK_STREAM)

cli_socket.connect((‘127.0.0.1‘, 8886))

#通信循环,可以多次输入
while True:
    msg = input(‘>>>>:‘).strip()
    if len(msg) == 0:            # 如果输入为空,给服务端发送信息之后,服务端什么都没接受,一直处于阻塞状态
        continue
    cli_socket.send(msg.encode(‘utf-8‘))

    data = cli_socket.recv(1024)
    print(data.decode(‘utf-8‘))

cli_socket.close()

通信循环客户端

tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制

二、链接循环

可以启动多个客户端,但是只有一个客户端是处于连接状态,其余部分在半连接池等待连接,等待的数量不能超过半连接池的最大监听数量

from socket import *

ser_socket = socket(AF_INET, SOCK_STREAM)

ser_socket.bind((‘127.0.0.1‘, 8886))

ser_socket.listen(5)

#链接循环,可以同时启动最多6个客户端,但是只有一个处于连接状态,其余最多5个在半连接池等待。只有当连接状态的客户端断开连接,下一个客户端才进入连接
while True:
    conn, addr = ser_socket.accept()

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)
            print(data.decode(‘utf-8‘))

            conn.send(data.upper())
        except ConnectionResetError:
            break

    conn.close()

ser_socket.close()

链接循环服务端

from socket import *

cli_socket = socket(AF_INET, SOCK_STREAM)

cli_socket.connect((‘127.0.0.1‘, 8886))

while True:
    msg = input(‘>>>>:‘).strip()
    if len(msg) == 0:
        continue
    cli_socket.send(msg.encode(‘utf-8‘))

    data = cli_socket.recv(1024)
    print(data.decode(‘utf-8‘))

cli_socket.close()

链接循环客户端

三、粘包问题

1、模拟ssh远程执行命令

from socket import socket, AF_INET, SOCK_STREAM
import subprocess

ser_socket = socket(AF_INET, SOCK_STREAM)

ser_socket.bind((‘127.0.0.1‘, 8882))

ser_socket.listen(5)
while True:
    conn, addr = ser_socket.accept()
    while True:
        try:
            data = conn.recv(1024)
            obj = subprocess.Popen(data.decode(‘utf-8‘),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            conn.send(stdout + stderr)
        except ConnectionResetError:
            break
    conn.close()

ser_socket.close()

服务端

from socket import socket, AF_INET, SOCK_STREAM

cli_socket = socket(AF_INET, SOCK_STREAM)

cli_socket.connect((‘127.0.0.1‘, 8882))

while True:
    msg = input(‘>>>‘).strip()
    if len(msg) == 0:
        continue
    cli_socket.send(msg.encode(‘utf-8‘))
    data = cli_socket.recv(1024)
    print(data.decode(‘gbk‘))      #  Windows系统,默认编码gbk,所以用gbk解码

cli_socket.close()

客户端

2、产生粘包原因

(1)所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

(2)此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

在上面的例子中,如果执行命令tasklist,那么就会存在粘包问题。由于TCP协议是流式协议,所以数据都以流的形式传输。假如数据大小是123456,可是已经设定了接收的大小 是1024,所以只接受了数据中的一小部分,但是,剩余部分数据并不会消失,会一直存在于操作系统中,所以下一次接收数据的时候是优先从剩余数据中接收。这样所有数据就乱套了,这就是粘包问题。

3、发生粘包的两种情况

(1)发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

from socket import *

ser_socket = socket(AF_INET, SOCK_STREAM)

ser_socket.bind((‘127.0.0.1‘, 8886))

ser_socket.listen(5)

conn, addr = ser_socket.accept()

data = conn.recv(1024)
print(‘第一次接收:‘, data.decode(‘utf-8‘))
data1 = conn.recv(5)
print(‘第二次接收:‘, data1.decode(‘utf-8‘))
data2 = conn.recv(1024)
print(‘第三次接收:‘, data2.decode(‘utf-8‘))

conn.send(data.upper())

conn.close()

ser_socket.close()

服务端

from socket import *

cli_socket = socket(AF_INET, SOCK_STREAM)

cli_socket.connect((‘127.0.0.1‘, 8886))

cli_socket.send(‘hello‘.encode(‘utf-8‘))
cli_socket.send(‘world‘.encode(‘utf-8‘))
cli_socket.send(‘object‘.encode(‘utf-8‘))

# data = cli_socket.recv(1024)
# print(data.decode(‘utf-8‘))

cli_socket.close()

客户端

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

  例如:模拟ssh远程执行命令,若执行tasklist命令,在客户端,无法几次性全部接受执行结果,所以剩余结果会在下一次执行命令式优先接收

from socket import socket, AF_INET, SOCK_STREAM
import subprocess

ser_socket = socket(AF_INET, SOCK_STREAM)

ser_socket.bind((‘127.0.0.1‘, 8882))

ser_socket.listen(5)
while True:
    conn, addr = ser_socket.accept()
    while True:
        try:
            data = conn.recv(1024)
            obj = subprocess.Popen(data.decode(‘utf-8‘),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            conn.send(stdout + stderr)
        except ConnectionResetError:
            break
    conn.close()

ser_socket.close()

服务端

from socket import socket, AF_INET, SOCK_STREAM

cli_socket = socket(AF_INET, SOCK_STREAM)

cli_socket.connect((‘127.0.0.1‘, 8882))

while True:
    msg = input(‘>>>‘).strip()
    if len(msg) == 0:
        continue
    cli_socket.send(msg.encode(‘utf-8‘))
    data = cli_socket.recv(1024)
    print(data.decode(‘gbk‘))

cli_socket.close()

客户端

4、解决粘包问题的方法

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。

(1)简单版本

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

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=struct.pack(‘i‘,len(stdout) + len(stderr))
            # 2. 再发送报头
            conn.send(header)
            # 3. 最后发送真实的数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break

    conn.close()

server.close()

服务端

from socket import *
import struct

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. 先收报头,从报头里解出数据的长度
    header=client.recv(4)
    total_size=struct.unpack(‘i‘,header)[0]
    #2. 接收真正的数据
    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()

客户端

(2)终极版本

# 服务端必须满足至少三点:
# 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()

服务端

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()

客户端

原文地址:https://www.cnblogs.com/linagcheng/p/9579685.html

时间: 2024-10-16 22:37:43

网络编程(三)——通信循环、链接循环、粘包问题的相关文章

网络编程 套接字socket 及 粘包

网络编程 套接字socket 及 粘包 sockt 初识 五层协议 : 从传输层包括传输层以下 , 都是操作系统帮我们封装的各种head socket套接字充当的就是内置模块的角色 socket 套接字,它存在于传输层与应用层之间的抽象层 避免你学习各层的接口以及协议的使用, socket已经封装好了所有的接口 . 直接使用这些接口或者方法即可 , 使用起来方便,提升开发效率 socket 就是一个模块 , 通过使用学习模块提供的功能 , 建立客户端与服务端的通信 套接字的工作流程(基于TCP和

网络编程(socket)中的粘包处理

服务端 import socket,os service=socket.socket()service.bind(('localhost',1024)) #绑定要监听的端口service.listen()###监听端口con,adder=service.accept()#等对方的连接,把对方的连接在本地生成一个实例并赋值个给conwhile True: data=con.recv(1024).decode('utf-8')##接收对方传过来的值(接收的最大值为2222个字节)并且赋值 x=os.

网络编程模型及网络编程三要素

网络模型 计算机网络之间以何种规则进行通信,就是网络模型研究问题. 网络模型一般是指 OSI(Open SystemInterconnection开放系统互连)参考模型 TCP/IP参考模型 网络模型7层概述: 1.物理层:主要定义物理设备标准,如网线的接口类型.光纤的接口类型.各种传输介质的传输速率等.它的主要作用是传输比特流(就是由1.0转化为电流强弱来进行传输,到达目的地后在转化为1.0,也就是我们常说的数模转换与模数转换).这一层的数据叫做比特. 2. 数据链路层:主要将从物理层接收的数

网络编程:Http通信与Socket通信

http://note.youdao.com/share/?id=f14d304548003f65e34255d3ddf9df31&type=note 网络编程:Http通信与Socket通信(移动互联核心) 知识点概述: 1.Socket通信:面向连接(TCP)和无连接的(UDP) 2.HttpURLConnection 接口:Get和Post方式 3.HttpClient接口:Get和Post方式 知识点详述: 1 Socket通信 Socket称为”套接字“,用于描述IP地址和端口,它是支

winform网络编程(三)

TcpClient类和TcpListener类 (1)TcpClient的用途: 用于在同步阻止模式下通过网络来链接.发送和接受流数据,在此情况下,必须有侦听此连接的请求,而侦听的任务就交给TcpListener实例或Socket实例 (2)TcpClient的两种方法连接到侦听器 第一种:创建一个TcpClient,并调用3个可用的Connect方法之一 第二种:使用远程主机的主机名和端口号创建TcpClient,此构造函数将自动尝试一个连接 (3)TcpClient的常用属性和方法 Avai

网络编程三素概述

1.1网络编程概述计算机网络 ●是指将地理位置不同的具有独立功能的多 台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统 网络编程●在网络通信协议下, 实现网络互连的不同计算机上运行的程序间可以进行数据交换 网络编程三要素 IP地址●要想让网络中的计算 机能够互相通信,必须为每台计算机指定一个标识号, 通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号.也就是设备的标识 端口●网

iOS网络编程(三) 异步加载及缓存图片----&gt;SDWebImage

@SDWebImage提供一个UIImageView的类别以支持加载来自网络的远程图片.具有缓存管理.异步下载.同一个URL下载次数控制和优化等特征. @SDWebImage的导入1.https://github.com/rs/SDWebImage 下载SDWebImage开源包2.将类包拖入工程,再导入MapKit.framework.ImageIO.framework两个框架3.SDWebImage是支持ARC的,在MRC的工程中要注意,可参考MRC工程配置ARC4.注意:SDWebImag

java网络编程Socket通信详解

Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Socket.像大家熟悉的QQ.MSN都使用了Socket相关的技术.下面就让我们一起揭开Socket的神秘面纱. Socket编程 一.网络基础知识(参考计算机网络)            关于计算机网络部分可以参考相关博客:           <TCP/IP协议栈及OSI参考模型详解> http://wangdy.blog.51cto.com/3845563/

网络编程三

一 .TCP上传大文件 Server端 import socket import json 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 = conn.recv(4) dict_size = struct.unpack('i',header

请讲普通话——一场关于异构平台通信的风波(粘包&#183;大小端方式&#183;网络字节序)

一.引子 前段时间用StriveEngine做一个信息采集系统,服务器是Windows的,客户端是各种单片机,以及Unix等等平台.这些异构的平台,被我召集起来“加强对话, 扩大共识, 深化合作”.都说有人的地方就有江湖,讲真,机器世界也一样!这些异构的平台,平日里各自为政,井水不犯河水,倒也相安无事.如今群雄会盟,共商大计,如我所料,势必会上演一波真正的血雨腥风! 就像新闻联播里常说的,“加强对话, 扩大共识, 深化合作”,首先得“加强对话”吧. 看着各位爷陆续到场,我稍稍清了清嗓子,不揣冒昧