• Socket函数编程(二)


    粘包问题

    1.修改数据长度:

    client端

     1 import socket
     2 
     3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 #拨通电话
     5 ip_port = ('127.0.0.1',8081)
     6 phone.connect(ip_port)
     7 
     8 while True:
     9     #发消息
    10     cmd = input('>>: ').strip()
    11     if not cmd:continue
    12     phone.send(bytes(cmd,encoding='utf-8'))
    13     #收消息
    14     data = phone.recv(1024)  #8192
    15     print(data.decode('gbk'))
    16 phone.close()

    #输出 长于1024字节的不行

    server端

    import socket
    import subprocess
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)       #SOCK_STREAM tcp
    # ip_port=('127.0.0.1',808)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    ip_port=('127.0.0.1',8081)
    phone.bind(ip_port)
    
    phone.listen(5)   #开机
    while True:
        conn,client_addr=phone.accept()
    
        while True:
            try:
                cmd = conn.recv(1024)
                if not cmd:break
                res = subprocess.Popen(cmd.decode('utf-8'),
                                       shell=True,
                                       stderr=subprocess.PIPE,
                                       stdout=subprocess.PIPE
                                       )
                # err=res.stderr.read()
                # if err:
                #     cmd_res=err
                # else:
                #     cmd_res=res.stdout.read()
                conn.send(res.stdout.read())
                conn.send(res.stderr.read())
            except Exception:
                break
    
        conn.close()
    phone.close()

    client端

    1 import socket
    2 import subprocess
    3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    4 #拨通电话
    5 ip_port = ('127.0.0.1',8080)
    6 phone.connect(ip_port)
    7 
    8 phone.send('helloworld'.encode('utf-8'))       
    9 phone.send('SB'.encode('utf-8'))

    #输出

    第一个包 b'helloworldSB'
    第二个包 b''

    服务器端

     1 import  socket
     2 import subprocess
     3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)       #SOCK_STREAM tcp
     4 ip_port=('127.0.0.1',8080)
     5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
     6 
     7 phone.bind(ip_port)
     8 phone.listen(5)   #开机
     9 conn,client_addr=phone.accept()
    10 
    11 data1=conn.recv(1024)
    12 data2=conn.recv(1024)
    13 
    14 print('第一个包',data1)
    15 print('第二个包',data2)

    改进

    2.修改时间长度

    client端

     1 import socket,time
     2 import subprocess
     3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 #拨通电话
     5 ip_port = ('127.0.0.1',8080)
     6 phone.connect(ip_port)
     7 
     8 phone.send('helloworld'.encode('utf-8'))    #客户端都是发到自己的缓存里了
     9 time.sleep(3)           #网络延迟没有三秒长
    10 phone.send('SB'.encode('utf-8'))

    server端

     1 import  socket
     2 import subprocess,time
     3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)       #SOCK_STREAM tcp
     4 ip_port=('127.0.0.1',8080)
     5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
     6 
     7 phone.bind(ip_port)
     8 phone.listen(5)   #开机
     9 conn,client_addr=phone.accept()
    10 
    11 data1=conn.recv(1)          #conn.recv(1024)
    12 time.sleep(5)
    13 data2=conn.recv(1024)
    14 
    15 print('第一个包',data1)
    16 print('第二个包',data2)

    TCP流式协议,

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

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

    1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
    3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

    tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

    所以,发包时一定要给包定义一个头部。

    3.stuct模块解决粘包

    为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

    报头: 1.固定长度 2.包含对发送数据的描述信息

    自定义报头(普通)

    client端

     1 import socket
     2 import struct
     3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     4 #拨通电话
     5 ip_port = ('192.168.16.134',8080)
     6 phone.connect(ip_port)
     7 #通信循环
     8 while True:
     9     #发消息
    10     cmd = input('>>: ').strip()
    11     if not cmd:continue
    12     phone.send(bytes(cmd,encoding='utf-8'))
    13     #收报头
    14     baotou = phone.recv(4)          #8192
    15     data_size=struct.unpack('i',baotou)[0]
    16 
    17     #收数据
    18     recv_size=0
    19     recv_data=b''
    20     while recv_size < data_size:
    21         data=phone.recv(1024)
    22         recv_size+=len(data)
    23         recv_data+=data
    24     print(recv_data.decode('utf-8'))
    25 phone.close()

    server端

     1 #/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 import socket
     4 import struct
     5 import subprocess
     6 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
     7 ip_port=('192.168.16.134',8080)
     8 
     9 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    10 phone.bind(ip_port)
    11 
    12 phone.listen(5) 
    13 while True:
    14     conn,client_addr=phone.accept()
    15 
    16     while True:
    17         try:
    18             cmd = conn.recv(1024)
    19             if not cmd:break
    20             res = subprocess.Popen(cmd.decode('utf-8'),
    21                                    shell=True,
    22                                    stderr=subprocess.PIPE,
    23                                    stdout=subprocess.PIPE
    24                                    )
    25             out_res=res.stdout.read()
    26             err_res=res.stderr.read()
    27             data_size=str(len(out_res)+len(err_res)).encode('utf-8')
    28             #发送报头
    29             conn.send(struct.pack('i',data_size))
    30             #发送数据部分
    31             conn.send(out_res)
    32             conn.send(err_res)
    33         except Exception:
    34             break
    35 
    36     conn.close()
    37 phone.close()

    struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

    自定义报头(jason)

    server端

     1 #/usr/bin/env python
     2 # -*- coding:utf-8 -*-
     3 import socket
     4 import struct
     5 import subprocess
     6 import json
     7 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
     8 ip_port=('192.168.16.134',8080)
     9 
    10 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    11 phone.bind(ip_port)
    12 
    13 phone.listen(5) 
    14 while True:
    15     conn,client_addr=phone.accept()
    16 
    17     while True:
    18         try:
    19             cmd = conn.recv(1024)
    20             if not cmd:break
    21             res = subprocess.Popen(cmd.decode('utf-8'),
    22                                    shell=True,
    23                                    stderr=subprocess.PIPE,
    24                                    stdout=subprocess.PIPE
    25                                    )
    26             out_res=res.stdout.read()
    27             err_res=res.stderr.read()
    28             data_size=len(out_res)+len(err_res)
    29             head_dic={'data_size':data_size}
    30             head_json=json.dumps(head_dic)
    31         head_bytes=head_json.encode('utf-8')        
    32         #part1:先发报头的长度
    33         head_len=len(head_bytes)
    34         conn.send(struct.pack('i',head_len))
    35             #part2:再发送报头
    36             conn.send(head_bytes)
    37             #part3:最后发送数据部分
    38             conn.send(out_res)
    39             conn.send(err_res)
    40         except Exception:
    41             break
    42 
    43     conn.close()
    44 phone.close()

    client端

     1 import socket
     2 import struct
     3 import json
     4 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     5 #拨通电话
     6 ip_port = ('192.168.16.134',8080)
     7 phone.connect(ip_port)
     8 #通信循环
     9 while True:
    10     #发消息
    11     cmd = input('>>: ').strip()
    12     if not cmd:continue
    13     phone.send(bytes(cmd,encoding='utf-8'))
    14 
    15     #part1:先收报头的长度
    16     head_struct=phone.recv(4)
    17     print(struct.unpack('i',head_struct))
    18     head_len=struct.unpack('i',head_struct)[0]
    19 
    20     #part2:再收报头
    21     head_bytes=phone.recv(head_len)
    22     head_json=head_bytes.decode('utf-8')
    23 
    24     head_dic=json.loads(head_json)
    25     print(head_dic)
    26     data_size=head_dic['data_size']
    27 
    28     #part3收数据
    29     recv_size=0
    30     recv_data=b''
    31     while recv_size < data_size:
    32         data=phone.recv(1024)
    33         recv_size+=len(data)
    34         recv_data+=data
    35     print(recv_data.decode('utf-8'))
    36 phone.close()
    报头类似于字典,先做字典,字典转成json格式,保证发过去还有结构的。把json字符串转成bytes格式,有了它可以发送报头的。

    发送时:服务器端struct模块把报头长度打成bytes,先发报头长度,在发报头,再发真实数据。
    接收时:客户端通过struct模块拿到报头长度,再recv收到报头长度,解码,反序列化得到字典,再从字典里拿到真实数据

    FTP上传下载

    服务端
    import socket
    import struct
    import json
    import subprocess
    import os
    
    class MYTCPServer:
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_STREAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        request_queue_size = 5
    
        server_dir='file_upload'
    
        def __init__(self, server_address, bind_and_activate=True):
            """Constructor.  May be extended, do not override."""
            self.server_address=server_address
            self.socket = socket.socket(self.address_family,
                                        self.socket_type)
            if bind_and_activate:
                try:
                    self.server_bind()
                    self.server_activate()
                except:
                    self.server_close()
                    raise
    
        def server_bind(self):
            """Called by constructor to bind the socket.
            """
            if self.allow_reuse_address:
                self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind(self.server_address)
            self.server_address = self.socket.getsockname()
    
        def server_activate(self):
            """Called by constructor to activate the server.
            """
            self.socket.listen(self.request_queue_size)
    
        def server_close(self):
            """Called to clean-up the server.
            """
            self.socket.close()
    
        def get_request(self):
            """Get the request and client address from the socket.
            """
            return self.socket.accept()
    
        def close_request(self, request):
            """Called to clean up an individual request."""
            request.close()
    
        def run(self):
            while True:
                self.conn,self.client_addr=self.get_request()
                print('from client ',self.client_addr)
                while True:
                    try:
                        head_struct = self.conn.recv(4)
                        if not head_struct:break
    
                        head_len = struct.unpack('i', head_struct)[0]
                        head_json = self.conn.recv(head_len).decode(self.coding)
                        head_dic = json.loads(head_json)
    
                        print(head_dic)
                        #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
                        cmd=head_dic['cmd']
                        if hasattr(self,cmd):
                            func=getattr(self,cmd)
                            func(head_dic)
                    except Exception:
                        break
    
        def put(self,args):
            file_path=os.path.normpath(os.path.join(
                self.server_dir,
                args['filename']
            ))
    
            filesize=args['filesize']
            recv_size=0
            print('----->',file_path)
            with open(file_path,'wb') as f:
                while recv_size < filesize:
                    recv_data=self.conn.recv(self.max_packet_size)
                    f.write(recv_data)
                    recv_size+=len(recv_data)
                    print('recvsize:%s filesize:%s' %(recv_size,filesize))
    
    
    tcpserver1=MYTCPServer(('127.0.0.1',8080))
    
    tcpserver1.run()
    
    
    
    
    
    
    #下列代码与本题无关
    class MYUDPServer:
    
        """UDP server class."""
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_DGRAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        def get_request(self):
            data, client_addr = self.socket.recvfrom(self.max_packet_size)
            return (data, self.socket), client_addr
    
        def server_activate(self):
            # No need to call listen() for UDP.
            pass
    
        def shutdown_request(self, request):
            # No need to shutdown anything.
            self.close_request(request)
    
        def close_request(self, request):
            # No need to close anything.
            pass
    
    服务端

    import socket
    import struct
    import json
    import os
    
    
    
    class MYTCPClient:
        address_family = socket.AF_INET
    
        socket_type = socket.SOCK_STREAM
    
        allow_reuse_address = False
    
        max_packet_size = 8192
    
        coding='utf-8'
    
        request_queue_size = 5
    
        def __init__(self, server_address, connect=True):
            self.server_address=server_address
            self.socket = socket.socket(self.address_family,
                                        self.socket_type)
            if connect:
                try:
                    self.client_connect()
                except:
                    self.client_close()
                    raise
    
        def client_connect(self):
            self.socket.connect(self.server_address)
    
        def client_close(self):
            self.socket.close()
    
        def run(self):
            while True:
                inp=input(">>: ").strip()
                if not inp:continue
                l=inp.split()
                cmd=l[0]
                if hasattr(self,cmd):
                    func=getattr(self,cmd)
                    func(l)
    
    
        def put(self,args):
            cmd=args[0]
            filename=args[1]
            if not os.path.isfile(filename):
                print('file:%s is not exists' %filename)
                return
            else:
                filesize=os.path.getsize(filename)
    
            head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
            print(head_dic)
            head_json=json.dumps(head_dic)
            head_json_bytes=bytes(head_json,encoding=self.coding)
    
            head_struct=struct.pack('i',len(head_json_bytes))
            self.socket.send(head_struct)
            self.socket.send(head_json_bytes)
            send_size=0
            with open(filename,'rb') as f:
                for line in f:
                    self.socket.send(line)
                    send_size+=len(line)
                    print(send_size)
                else:
                    print('upload successful')
    
    
    client=MYTCPClient(('192.168.16.134',8080))
    
    client.run()
    客户端
  • 相关阅读:
    一些你可能用到的代码
    iOS 键盘下去的方法
    iOS设计模式汇总
    随笔
    Spring cloud config 分布式配置中心 (三) 总结
    Spring cloud config 分布式配置中心(二) 客户端
    Spring cloud config 分布式配置中心(一) 服务端
    jdbcUrl is required with driverClassName spring boot 2.0版本
    JpaRepository接口找不到 spring boot 项目
    解决IntelliJ “Initialization failed for 'https://start.spring.io'
  • 原文地址:https://www.cnblogs.com/jiangshitong/p/6809014.html
Copyright © 2020-2023  润新知