zoukankan      html  css  js  c++  java
  • 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()
    客户端
  • 相关阅读:
    bootstrap-table 数据表格行内修改
    java文件上传(单文件 多文件)与删除
    bootstrap-table之通用方法( 时间控件,导出,动态下拉框, 表单验证 ,选中与获取信息)
    bootstrap-table 大量字段整体表单上传之时间处理
    Java实习问题记录
    Playbook剧本初识
    自动化运维工具-Ansible基础
    性能优化概述
    Rewrite基本概述
    Nginx常见问题
  • 原文地址:https://www.cnblogs.com/jiangshitong/p/6809014.html
Copyright © 2011-2022 走看看