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()
客户端

 

posted @ 2017-05-04 18:59  samyoung  阅读(174)  评论(0编辑  收藏  举报