基于tcp协议通讯的套接字以及粘包问题

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

由来:

1,没有通讯循环的话,服务端链接一个客户端,客户端只能发送一个请求,链接就断开了,所有把通讯循环加在向服务端发送信息之前,这样就可以客户端向服务端发送请求,执行完之后还能发送请求了.

2,没有链接循环的话:一个服务端只能链接一台客户端,当客户端突然断开的时候,服务端就直接崩溃了,客户端不用动,循环加在服务端请求链接之前

代码如下:

客户端
import socket


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

while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    date = phone.recv(1024)
    print(date.decode('gbk'))

phone.close()
服务端
from socket import *
import subprocess

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

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

    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd:break
            obj = subprocess.Popen(cmd.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()

server.close()

二,模拟ssh实现远程控制(解决粘包问题)

由来:

1,当客户端没有收到反馈后一直在发送请求,服务端会把客户端发来的请求呈流水状一起接收,也就是,

tcp协议通过negle算法会将数据量较小,发送时间间隔较短的多个数据包合并一个发送这样的状态称之为粘包问题

我们把接收一方的大小设定成1024,如果超出1024,会堆积在客户端的操作系统内,直到客户端再次发出请求,需要接收数据的时候,直接把上次堆积的数据显示在客户端,这样数据就会乱,不知道是哪个请求返回的数据

这里需要引入一个struct模块,可以把数据分成报头和真是数据两部分,struck如下可以把data数字缩减到4个字节,字节发送到客户端,客户端用unpack收到一个元组,元组第一个值是真是数据的长度

import struct
obj = struck.pack('i',data)
res = struck.unpack('i',obj)[0]

客户端的代码如下:

from socket import *
import struct

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

while True:
    cmd = input('>>>:').strip()
    if not cmd: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()

服务端代码如下:

from socket import *
import struck
import subprocess

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

while True:
    conn,client_addr = server.accept()

    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd:break
            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 = struck.pack('i',len(stdout)+len(stderr))
#            2,在发送报头
            conn.send(header)
#            3,最后发送真是数据
            conn.send(stdout)
            conn.send(stderr)
        except ConnectionResetError:
            break
    conn.close()

server.close()

这样就会产生一个问题,如果后续文件中需要真是数据的其他信息却无法获得,所以我们把报头改成字典形式,字典存在total_size的key,它存放的是真实数据的大小,这样我们就可以存放更多的真实数据的信息.可报头也没有办法区分,所以在报头前面先发送一个报头的长度,用循环取出报头的内容,再接着取真实数据,每次取数据的时候防止数据内容太大,把程序整崩溃,所以我们每次循环取值

自定义报头的思路:

1,先发固定的报头长度(shuct可以将任意类型转换成固定的bytes比如:[i:4个字节][q:8个字节])

2,先发送自定义的报头(报头中包含真正数据的一些信息,如文件名,文件大小,md5值等等)

3,最后发送真是数据

经过改良后的客户端:

from socket import *
import struct
import json

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

while True:
    cmd = input('>>>:').strip()
    if not cmd:continue
    client.send(cmd.encode('utf-8'))
    # 1,先接收4个bytes,解出报头长度
    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)
    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()

经过改良后的服务端:

from socket import *
import struck
import subprocess
import json

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

while True:
    conn,client_addr = server.accept()

    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd: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(struck.pack('i',len(header_bytes)))

            # 3,再发送报头
            conn.send(header_bytes)

            # 4,最后发送真实数据
            conn.send(stdout)
            conn.send(stderr)


        except ConnectionResetError:
            break
    conn.close()

server.close()

猜你喜欢

转载自blog.csdn.net/qq_42737056/article/details/82350099