python基础之socket编程part2---粘包和并发

粘包现象

基于tcp的套接字实现远程执行命令的操作(1.执行错误命令。2.执行ls。3.执行ipconfig)

#_*_coding:utf-8_*_
__author__ = ‘777‘
import socket
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(("127.0.0.1",8080))
phone.listen(5)
print("等待命令....")
while True:
    conn,addr=phone.accept()
    while True:
        try:
            date=conn.recv(1024)
            s=date.decode("utf8")
            res=subprocess.Popen("%s"%(s),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,)
            re1=res.stdout.read()
            re2=res.stderr.read()
            if len(re1)==0 and len(re2)==0:
                conn.send("此命令无显示信息。".encode("gbk"))
            else:
                conn.send(re1)
                conn.send(re2)
        except Exception:
            break
conn.close()
phone.close()

真正执行命令的服务端

#_*_coding:utf-8_*_
__author__ = ‘777‘
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
    msg=input("请输入你的CMD命令:")
    if not msg:continue
    phone.send(msg.encode("utf8"))
    data=phone.recv(1024)
    print(data.decode("gbk"))
phone.close()

远程控制客户端

运行时我们会发现程序会发生粘包

而udp的socket运行时永远不会发生粘包

注意:

res=subprocess.Popen(cmd.decode(‘utf-8‘),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

上述的结果的编码是以当前所在的系统为准的,如果是win,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

且只能从管道里读一次结果

什么是粘包

首先要知道:只要TCP有粘包现象,UDP永远不会粘包。

我们先来掌握下一个socket收发消息的原理:

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,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协议会帮你封装上消息头,实验略

udp的recvfrom是阻塞的,一个recvfrom(x)必须对一个一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

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

两种情况下会发生粘包:

1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

拆包的发生情况

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

补充问题一:为何tcp是可靠传输,udp是不可靠传输

基于tcp的数据传输请参考我的另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的

而udp发送数据,对端是不会返回确认信息的,因此不可靠

补充问题二:send(字节流)和recv(1024)及sendall

recv里指定的1024意思是从缓存里一次最多能拿出1024个字节的数据,不一定拿1024个字节

send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失

解决粘包的处理方法

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

1.二逼青年版本的解决方法(利用时间模块)

#买手机
import socket,time
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定电话卡
ip_port=(‘127.0.0.1‘,8080)
phone.bind(ip_port)
phone.listen(5)
conn,addr=phone.accept()

# data1=conn.recv(1024)
# data2=conn.recv(1024)

data1=conn.recv(1) #b‘h‘

time.sleep(5)
data2=conn.recv(1024) #b‘elloworldSB‘

print(‘第一个包‘,data1)
print(‘第二个包‘,data2)

服务端

#买手机
import socket,time
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定电话卡
ip_port=(‘127.0.0.1‘,8080)
phone.connect(ip_port)

phone.send(‘helloworld‘.encode(‘utf-8‘))
time.sleep(3)
phone.send(‘SB‘.encode(‘utf-8‘))

客户端

普通青年版本的解决方法

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

上面方法的弊端:

程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗

2.为了真正意义上的解决粘包问题,我们引入 struct模块

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

报头特点:

1.固定长度
2.包含对将要发送数据的描述信息

该模块可以把一个类型,如数字,转成固定长度的bytes

>>> struct.pack(‘i‘,1111111111111)

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

import json,struct
#假设通过客户端上传1T:1073741824000的文件a.txt

#为避免粘包,必须自定制报头
header={‘file_size‘:1073741824000,‘file_name‘:‘/a/b/c/d/e/a.txt‘,‘md5‘:‘8f6fbf8347faa4924a76856701edb0f3‘} #1T数据,文件路径和md5值

#为了该报头能传送,需要序列化并且转为bytes
head_bytes=bytes(json.dumps(header),encoding=‘utf-8‘) #序列化并转成bytes,用于传输

#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes=struct.pack(‘i‘,len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度

#客户端开始发送
conn.send(head_len_bytes) #先发报头的长度,4个bytes
conn.send(head_bytes) #再发报头的字节格式
conn.sendall(文件内容) #然后发真实内容的字节格式

#服务端开始接收
head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
x=struct.unpack(‘i‘,head_len_bytes)[0] #提取报头的长度

head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
header=json.loads(json.dumps(header)) #提取报头

#最后根据报头的内容提取真实的数据,比如
real_data_len=s.recv(header[‘file_size‘])
s.recv(real_data_len)

#coding:utf-8
#买手机
import socket
import struct
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定电话卡
ip_port=(‘192.168.16.114‘,8081)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(ip_port)
#开机
phone.listen(5)
#等待电话

#链接循环
while True:
    conn,addr=phone.accept()
    print(‘client addr‘,addr)
    #通讯循环
    while True:
        try:
            cmd=conn.recv(1024)
            res=subprocess.Popen(cmd.decode(‘utf-8‘),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
            out_res=res.stdout.read()
            err_res=res.stderr.read()
            data_size=len(out_res)+len(err_res)
            #发送报头
            conn.send(struct.pack(‘i‘,data_size))
            #发送数据部分
            conn.send(out_res)
            conn.send(err_res)

        except Exception:
            break

    conn.close()
phone.close()

服务端

#买手机
import socket
import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#拨通电话
# ip_port = (‘127.0.0.1‘, 8080)
ip_port = (‘192.168.16.114‘, 8081)
phone.connect(ip_port)
# 通信循环
while True:
    # 发消息
    cmd = input(‘>>: ‘).strip()
    if not cmd: continue
    phone.send(bytes(cmd, encoding=‘utf-8‘))

    #收报头
    baotou=phone.recv(4)
    data_size=struct.unpack(‘i‘,baotou)[0]

    # 收数据
    recv_size=0
    recv_data=b‘‘
    while recv_size < data_size:
        data=phone.recv(1024)
        recv_size+=len(data)
        recv_data+=data

    print(recv_data.decode(‘utf-8‘))
phone.close()

客户端

我们发现此方法也有弊端:就是当发送的数据超过我们使用的打包方式的规定范围是就不好使了。所以:

我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

发送时:

先发报头长度

再编码报头内容然后发送

最后发真实内容

接收时:

先手报头长度,用struct取出来

根据取出的长度收取报头内容,然后解码,反序列化

从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容

#coding:utf-8
#买手机
import socket
import struct
import json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定电话卡
ip_port=(‘192.168.16.114‘,8081)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(ip_port)
#开机
phone.listen(5)
#等待电话

#链接循环
while True:
    conn,addr=phone.accept()
    print(‘client addr‘,addr)
    #通讯循环
    while True:
        try:
            cmd=conn.recv(1024)
            res=subprocess.Popen(cmd.decode(‘utf-8‘),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
            out_res=res.stdout.read()
            err_res=res.stderr.read()
            data_size=len(out_res)+len(err_res)
            head_dic={‘data_size‘:data_size}
            head_json=json.dumps(head_dic)
            head_bytes=head_json.encode(‘utf-8‘)

            #part1:先发报头的长度
            head_len=len(head_bytes)
            conn.send(struct.pack(‘i‘,head_len))
            #part2:再发送报头
            conn.send(head_bytes)
            #part3:最后发送数据部分
            conn.send(out_res)
            conn.send(err_res)

        except Exception:
            break

    conn.close()
phone.close()

终极版服务端

# 买手机
import socket
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 拨通电话
# ip_port = (‘127.0.0.1‘, 8080)
ip_port = (‘192.168.16.114‘, 8081)
phone.connect(ip_port)
# 通信循环
while True:
    # 发消息
    cmd = input(‘>>: ‘).strip()
    if not cmd: continue
    phone.send(bytes(cmd, encoding=‘utf-8‘))

    # part1:先收报头的长度
    head_struct=phone.recv(4)
    head_len=struct.unpack(‘i‘,head_struct)[0]

    # part2:再收报头
    head_bytes=phone.recv(head_len)
    head_json=head_bytes.decode(‘utf-8‘)

    head_dic=json.loads(head_json)
    print(head_dic)
    data_size = head_dic[‘data_size‘]

    #part3:收数据
    recv_size = 0
    recv_data = b‘‘
    while recv_size < data_size:
        data = phone.recv(1024)
        recv_size += len(data)
        recv_data += data

    print(recv_data.decode(‘utf-8‘))
phone.close()

终极版客户端

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((‘127.0.0.1‘,8080))

client.run()

客户端

socketserver实现并发

基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环

socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)

server类:

request类:

继承关系:

以下述代码为例,分析socketserver源码:

ftpserver=socketserver.ThreadingTCPServer((‘127.0.0.1‘,8080),FtpServer)ftpserver.serve_forever()

查找属性的顺序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

  1. 实例化得到ftpserver,先找类ThreadingTCPServer的__init__,在TCPServer中找到,进而执行server_bind,server_active
  2. 找ftpserver下的serve_forever,在BaseServer中找到,进而执行self._handle_request_noblock(),该方法同样是在BaseServer中
  3. 执行self._handle_request_noblock()进而执行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然后执行self.process_request(request, client_address)
  4. 在ThreadingMixIn中找到process_request,开启多线程应对并发,进而执行process_request_thread,执行self.finish_request(request, client_address)
  5. 上述四部分完成了链接循环,本部分开始进入处理通讯部分,在BaseServer中找到finish_request,触发我们自己定义的类的实例化,去找__init__方法,而我们自己定义的类没有该方法,则去它的父类也就是BaseRequestHandler中找....

源码分析总结:

基于tcp的socketserver我们自己定义的类中的

  1.   self.server即套接字对象
  2.   self.request即一个链接
  3.   self.client_address即客户端地址

基于udp的socketserver我们自己定义的类中的

  1.   self.request是一个元组(第一个元素是客户端发来的数据,第二部分是服务端的udp套接字对象),如(b‘adsf‘, <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>)
  2.   self.client_address即客户端地址

# import socket

# phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机
# phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# phone.bind(("127.0.0.1",8080))#绑定手机卡
# phone.listen(5)#开机    最大挂起几个链接

# print("starting....")
# while True:#连接循环
#     conn,addr=phone.accept()#等待电话连接
#     print("电话线路是",conn)
#     print("客户端手机号是",addr)
#     while True:#通信循环
#         try:#此方法为win系统解决单方面断开问题的方法
#             date=conn.recv(1024)#收消息    最大收取字节数
#             # date=addr.recv(1024)#收消息
#             # if not date:break#此为linux方法
#             print("客户端发来的消息是",date)
#             conn.send(date.upper())
#         except Exception:
#             break
# conn.close()
# phone.close()
#__________________________________以上不支持多个客户进行交互,即不能实现并发__________________________________

import socketserver
#Ftpserver(conn, client_addr, obj)
class FTPsever(socketserver.BaseRequestHandler):#通信
    def handle(self):
        print("----------",self)
        print(self.request)
        while True:
            date=self.request.recv(1024)
            print(date)
            self.request.send(date.upper())

if __name__ == ‘__main__‘:
    obj=socketserver.ThreadingTCPServer(("127.0.0.1",8080),FTPsever)
    obj.serve_forever()#链接循环
#_______________________________以上利用模块socketserver实现(多线程)并发运行__________________________________

并发服务器

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

while True: #通信循环
    msg=input(‘>>: ‘).strip()
    if not msg:continue
    phone.send(msg.encode(‘utf-8‘))
    # print(‘has send===========>‘)
    data=phone.recv(1024)
    # print(‘has recv===========>‘)
    print(data)

phone.close()

客户端1

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

while True: #通信循环
    msg=input(‘>>: ‘).strip()
    if not msg:continue
    phone.send(msg.encode(‘utf-8‘))
    # print(‘has send===========>‘)
    data=phone.recv(1024)
    # print(‘has recv===========>‘)
    print(data)

phone.close()

客户端2

以上是基于tcp的并发实现。

下面是基于udp的并发实现:

import socketserver

class FtpServer(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.request[0])
        print(self.request[1])
        self.request[1].sendto(‘歇会儿吧‘.encode(‘utf-8‘),self.client_address)

if __name__ == ‘__main__‘:
    obj=socketserver.ThreadingUDPServer((‘127.0.0.1‘,8080),FtpServer)
    obj.serve_forever()

基于udp的socketserver

import socket

udpclient=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server_ip_port=(‘127.0.0.1‘,8080)
while True:
    inp=input(">>: ")
    udpclient.sendto(inp.encode(‘utf-8‘),server_ip_port)

    data,server_addr=udpclient.recvfrom(1024)
    print(data.decode(‘utf-8‘))

udp客户端1

import socket

udpclient=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

server_ip_port=(‘127.0.0.1‘,8080)
while True:
    inp=input(">>: ")
    udpclient.sendto(inp.encode(‘utf-8‘),server_ip_port)

    data,server_addr=udpclient.recvfrom(1024)
    print(data.decode(‘utf-8‘))

udp客户端2

FTP练习:上传下载文件改进版(实现并发功能)

import socketserver
import struct
import json
import subprocess
import os

class MYTCPServer(socketserver.BaseRequestHandler):

    max_packet_size = 8192

    coding=‘utf-8‘
    BASE_DIR=os.path.dirname(os.path.abspath(__file__))

    server_dir=‘file_upload‘

    def handle(self):
        while True:
            try:
                head_struct = self.request.recv(4)
                if not head_struct:break

                head_len = struct.unpack(‘i‘, head_struct)[0]
                head_json = self.request.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.BASE_DIR,
            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.request.recv(self.max_packet_size)
                f.write(recv_data)
                recv_size+=len(recv_data)
                print(‘recvsize:%s filesize:%s‘ %(recv_size,filesize))

if __name__ == ‘__main__‘:
    obj=socketserver.ThreadingTCPServer((‘127.0.0.1‘,8080),MYTCPServer)
    obj.serve_forever()

FTP服务端

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((‘127.0.0.1‘,8080))

client.run()

FTP客户端1

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((‘127.0.0.1‘,8080))

client.run()

FTP客户端2

时间: 2024-10-10 13:12:47

python基础之socket编程part2---粘包和并发的相关文章

python/socket编程之粘包

python/socket编程之粘包 粘包: 只有TCP有尿包现象,UDP永远不会粘包. 首先需要掌握一个socket收发消息的原理 发送端可以是1k,1k的发送数据而接受端的应用程序可以2k,2k的提取数据,当然也有可能是3k或者多k提取数据,也就是说,应用程序是不可见的,因此TCP协议是面来那个流的协议,这也是容易出现粘包的原因而UDP是面向笑死的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任一字节的数据,这一点和TCP是很同的.怎样定义消息呢?认为对方一次

Python 基础之socket编程(二)

Python 基础之socket编程(二) 昨天只是对socket编程做了简单的介绍,只是把socket通信的框架搭建起来,要对其中的功能进行进一步的扩充,就来看看今天的料哈! 一.基于tcp的套接字 1. tcp的服务端 ss = socket() #创建服务器套接字 ss.bind() #把地址绑定到套接字 ss.listen() #监听链接 inf_loop: #服务器无限循环 cs = ss.accept() #接受客户端链接 comm_loop: #通讯循环 cs.recv()/cs.

python基础之socket编程

python基础之socket编程   一 TCP/IP五层模型 在每一层都工作着不同的设备,比如我们常用的交换机就工作在数据链路层的,一般的路由器是工作在网络层的. 在每一层实现的协议也各不同,即每一层的服务也不同.下图列出了每层主要的协议. 各层功能 注明:ARP和RAPR两个到底属于哪一层呢? 由于IP协议使用了ARP协议,所以经常把ARP协议划到网络层,但是ARP协议是为了从网络层使用的IP地址解析出在数据链路层使用的MAC地址,所以有些地方也把ARP协议划分到数据链路层,但是一般情况下

Python 基础之socket编程(三)

python 基础之socket编程(三) 前面实现的基于socket通信只能实现什么呢?在tcp协议的通信中就是一个用户说一句,服务端给你回一句,你再给服务端说一句,服务端再给你回一句,就这样一直友好的玩耍下去了.等等,又有一个用户来了,他呢也想和和服务端进行一下交流,于是他就给服务端发送了一条消息,之后等呀等不知过了多久,任然没有等到服务端给他发挥的消息,只有什么时候他就可以和服务端愉快的玩耍了呢?这个就需要第一个用户退出和服务器的链接,此时第二个客户端才会和服务端建立起链接,此时此刻,他才

python基础之socket编程 (转自林海峰老师)

阅读目录 一 客户端/服务器架构 二 osi七层 三 socket层 四 socket是什么 五 套接字发展史及分类 六 套接字工作流程 七 基于TCP的套接字 八 基于UDP的套接字 九 粘包现象 十 什么是粘包 十一 解决粘包的low比处理方法 十二 峰哥解决粘包的方法 十三 认证客户端的链接合法性 十四 socketserver实现并发 十五 作业 一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B

Socket编程--TCP粘包问题

TCP是个流协议,它存在粘包问题 产生粘包的原因是: TCP所传输的报文段有MSS的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送. 由于链路层最大发送单元MTU,在IP层会进行数据的分片. 应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区.而发送缓冲区有一个SO_SNDBUF的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送. 粘包问题的解决 ①:发送定长包 这里需要封装两个函数: ssize_t readn(int

java socket编程解决粘包和丢包问题

##socket 丢包粘包解决方式 采用固定头部长度(一般为4个字节),包头保存的是包体的长度 header+body 包头+包体 思路是:先读出一个包头,得到包体的长度,解析出包体 public class SocketServer { public static void main(String args[]) { ServerSocket serverSocket; try { serverSocket = new ServerSocket(); serverSocket.bind(new

python基础20 ------python基础之socket编程

一.C/S架构和B/S架构的简介 略 二.osi七层模型 略 三.socket层 1.如图所示: socket层是存在于应用层和传输层直接抽象出来的一层. 2.socket层是什么? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的 TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.所以,我们无需 深入理解tcp/udp协议,socke

python基础之socket编程-------基于tcp的套接字实现远程执行命令的操作

远程实现cmd功能: import socket import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.bind(("127.0.0.1",8080)) phone.listen(5) print("starting....") while True: conn,addr=phone.accept() while True: try: date=conn.recv