首页 > 代码库 > Socket函数编程(二)

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:continue12     phone.send(bytes(cmd,encoding=utf-8))13     #收消息14     data = http://www.mamicode.com/phone.recv(1024)  #819215     print(data.decode(gbk))16 phone.close()

#输出 长于1024字节的不行

 

server端

import socketimport subprocessphone=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 socket2 import subprocess3 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:continue12     phone.send(bytes(cmd,encoding=utf-8))13     #收报头14     baotou = phone.recv(4)          #819215     data_size=struct.unpack(i,baotou)[0]16 17     #收数据18     recv_size=019     recv_data=http://www.mamicode.com/b‘‘20     while recv_size < data_size:21         data=http://www.mamicode.com/phone.recv(1024)22         recv_size+=len(data)23         recv_data+=data24     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:break20             res = subprocess.Popen(cmd.decode(utf-8),21                                    shell=True,22                                    stderr=subprocess.PIPE,23                                    stdout=subprocess.PIPE24                                    )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             break35 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:break21             res = subprocess.Popen(cmd.decode(utf-8),22                                    shell=True,23                                    stderr=subprocess.PIPE,24                                    stdout=subprocess.PIPE25                                    )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             break42 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:continue13     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=030     recv_data=http://www.mamicode.com/b‘‘31     while recv_size < data_size:32         data=http://www.mamicode.com/phone.recv(1024)33         recv_size+=len(data)34         recv_data+=data35     print(recv_data.decode(utf-8))36 phone.close()
报头类似于字典,先做字典,字典转成json格式,保证发过去还有结构的。把json字符串转成bytes格式,有了它可以发送报头的。

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

 

FTP上传下载

技术分享服务端
import socketimport structimport jsonimport subprocessimport osclass 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 socketimport structimport jsonimport osclass 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()
客户端

 

Socket函数编程(二)