zoukankan      html  css  js  c++  java
  • 低级别网络接口-socket的应用和粘包现象

    套接字的类型

           基于文件类型的套接字家族: AF_UNIX

                  基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

     

      基于网络类型的套接字家族; AF_INET

     

    TCP   socket 通信

    服务端配置

    import socket
    server = socket.socket() #创建一个服务器对象
    ip_port = ('192.168.15.79',8080)#创建一个元组
    server.bind(ip_port) #将服务器和ip地址进行绑定
    server.listen(3)  #设置服务器可以同时监听多少个客户端
    con,addr =server.accept()#接收客户端发送过来的东西,如果协商成功,就建立相应的隧道
    # print(con)#两者协商通信的协议,使用的ip地址和端口号
    # #<socket.socket fd=96, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.15.79', 8080), raddr=('192.168.15.79', 57945)>
    print(addr)#('192.168.15.79', 57945) 连接的客户端的地址和端口号
    Client = con.recv(1024)#设置接收的最大的文件的大小,并将接收到的信息进行赋值

    print('客户端消息:',Client)
    con.send(b'you are over')#向客户端发送相关的信息,但只能发送呢bytes类型
    con.close()
    server.close()

    客户端配置

    import socket
    client = socket.socket()#创建客户端
    Server_ip = ('192.168.15.79',8080) #设置连接服务器的ip地址和端口
    client.connect(Server_ip) #连接服务器
    client.send(b'cisoc')#连接完成后向服务端发送信息
    cc = client.recv(1024) #设置接收文件的大小
    print(cc)
    client.close()

    使用TCP连接的时候,如果一个TCP服务端与多个客户端连接,但是服务端只能与第一客户端连接,

    tcp属于长连接,长连接就是一直占用着这个链接,这个连接的端口被占用了,第二个客户端过来连接的时候,他是可以连接的,但是处于一个占线的状态,就只能等着去跟服务端建立连接,当服务端关闭与第一个客户端的连接之后,重新等待连接才能与第二个客户端进行连接

    一对多通信

    import  socket
    Ser_scoket = socket.socket()
    Ser_scoket_ip  = ('192.168.11.70',8080)
    Ser_scoket.bind(Ser_scoket_ip)
    Ser_scoket.listen()
    while 1:
        conn,addr = Ser_scoket.accept()
        while 1:
            Msg_to_Cli = input('服务端>>>').encode('utf-8')
            conn.send(Msg_to_Cli)
            if Msg_to_Cli == 'byebye'.encode('utf-8'):
                break
    
            Msg_from_Cli = conn.recv(1024).decode('utf-8')
            print('来自%s的消息%s' % (addr, Msg_from_Cli))
            if Msg_from_Cli == 'byebye':
                break
    
        conn.close()
    一对多服务端
    import socket
    client_scoket = socket.socket()
    Server_ip = ('192.168.11.70',8080)
    client_scoket.connect(Server_ip)
    
    while 1:
        Msg_from_Ser = client_scoket.recv(1024).decode('utf-8')
        print('来自[【%s】的消息:%s' % (Server_ip, Msg_from_Ser))
        if Msg_from_Ser == 'byebye':
            break
    
        Msg_to_Ser = input('客户端>>>').encode('utf-8')
        client_scoket.send(Msg_to_Ser)
        if Msg_to_Ser == 'byebye'.encode('utf-8'):
            break
    
    client_scoket.close()
    客户端

    UDP socket通信

    在UDP的配置中主要是两条命令

    sendto  >>>>  两个参数是1.要发送的信息  目标地址

    recvfrom >>> 将接收到的两个东西进行解包,第一个是接收到的信息,发送这条信息的IP地址

    type = sock.SOCK_DGRAM  指定为UDP的类型

     

     粘包现象

    每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

     

    每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。socket对象write()/send() 函数处理完数据后,都会数据往输出缓冲区中扔,写入到缓冲区后,socket对象就认为自己处理完数据,函数可以正常返回,再由TCP协议(传输层协议)将数据从输入缓冲区发送到目标机器。

    由于TCP协议与 write()/send()是相关的两个个体,TCP协议按照自己的优化机制,对数据进行打包传输,这这就导致了多次写入的数据却被一次性发送,例如第一次发送的是12,第二次发送的是13, TCP在缓冲区读取的时候按照包的大小直接读取,并发送,在对端收到的就是1213连起来的数字
     

    这些I/O缓冲区特性可整理如下:

     

    1.I/O缓冲区在每个TCP套接字中单独存在;

    2.I/O缓冲区在创建套接字时自动生成;

    3.即使关闭套接字也会继续传送输出缓冲区中遗留的数据;

    4.关闭套接字将丢失输入缓冲区中的数据

    5.默认的大小是一般是8K

     

    发生粘包的两种情况

         1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔短,数据小,被当做一个包发出去)

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

     

    粘包现象第一种情况:

    服务端配置

    import socket
    server = socket.socket()
    server_ip = ('192.168.11.70',8080)
    server.bind(server_ip)
    server.listen(3)
    conn,addr = server.accept()
    while 1:
        conn.send('12'.encode('utf-8'))
        conn.send('99'.encode('utf-8'))
    View Code

    客户端配置

    import socket
    client = socket.socket()
    server_ip = ('192.168.11.70',8080)
    client.connect(server_ip)
    while 1:
    
        gg = client.recv(1024).decode('utf-8')
        ff = client.recv(1024).decode('utf-8')
        print(gg,ff)
    View Code

    粘包现象第二种情况:

    服务端配置

    import socket
    import subprocess
    
    server = socket.socket()
    ip_port = ('192.168.11.70',8080)
    server.bind(ip_port)
    server.listen(2)
    con,addr = server.accept()
    # print(addr)
    
    while 1 :
    
        client_cmd = con.recv(1024).decode('utf-8')
        # print('*****')
        sub = subprocess.Popen(
            client_cmd,
            shell=True,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,)
        # print('********')
        cmd_res = sub.stdout.read()
        print(type(cmd_res))
        con.send(cmd_res)
        print('结果长度>>>', len(cmd_res))
    View Code

     客户端配置  >>>输入输出比较大的系统命令的时候就会出现粘包的现象 eg:ipconfig /all

    mport socket
    client = socket.socket()
    ip_port = ('192.168.11.70',8080)
    client.connect(ip_port)
    while 1:
        client_cmd = client.send(input('请输入系统指令>>>').encode('utf-8'))
        cmd_result = client.recv(1024).decode('gbk')
        print(cmd_result)
    View Code

    解决粘包现象:

    1. 获取文件的传输大小,两边统一接收和传输的数据流大小

    1.1利用struct模块,发送报头的方式来区分文件的传输 

     通过使用 struct.pack('i',len(文件长度))  返回值是后面的值的bytes类型,恒为4字节

    低端在处理的时候先接受4字节,然后通过struct.upack(‘i’,字节) 获得文件的长度

    但是一般这样子处理的扩展性比较差,一般是通过在发送端定义一个字典,对所有的要发送的文件进行概括,之后使用json 对字典进行序列化并进行编码,之后再使用struct 进行处理,让对端能够正确接收到字典,接收到字典后,双方在按照商定的大小进行接收和发送  eg:下面的例子

    1.2   在发送端直接使用len 计算长度后,直接将数字发送到对端,对端按照发送过来的数字直接设置,进行接收(存在问题是如果文件过大也会导致,缓冲区爆满而出错)

    1.3   不管文件大还是小,直接对字节码进行按照商定好的大小进行切片发送()

    对端则按照商定好的大小进行接收(扩展差点,)

    最优解决方案

    服务端配置:

    import socket
    import os
    import json
    import struct
    
    server = socket.socket()
    server_ip  = ('192.168.11.70',8080)
    server.bind(server_ip)
    server.listen(3)
    conn,addr = server.accept()
    # print(os.path.getsize('D:python全栈第二阶段网络编程day28粘包服务端.py'))
    dic  = {'filename':'粘包服务端','size':os.path.getsize('D:python全栈第二阶段网络编程day28粘包服务端.py')}
    dic_JSON = json.dumps(dic).encode('utf-8')
    Head_Byte = struct.pack('i',len(dic_JSON))
    conn.send(Head_Byte+dic_JSON)
    with open(r'D:python全栈第二阶段网络编程day28粘包服务端.py','rb') as f :
        while 1:
            file_Read = f.read(8192)
            if file_Read:
                conn.send(file_Read)
            else:break
    View Code

    客户端配置

    import socket
    import struct
    import json
    
    client = socket.socket()
    server_ip = ('192.168.11.70',8080)
    client.connect(server_ip)
    
    dic_size = struct.unpack('i',client.recv(4))[0]
    dic_json = client.recv(dic_size).decode('utf-8')
    dic = json.loads(dic_json)
    with open('py.py','wb') as f:
        if dic['size']:
            size_rev = client.recv(8192)
            f.write(size_rev)
            dic['size'] -=len(size_rev)
    View Code

     

  • 相关阅读:
    软件质量见解
    Vim 简明教程【转载】
    Actor Mailbox
    Unity对齐工具
    静态AOP Fody PropertyChanged
    棋牌分布式架构
    死锁
    curl 获取自定义数据
    WPF RichTextBox添加一条有颜色的记录
    arp -s 添加失败:拒绝访问
  • 原文地址:https://www.cnblogs.com/vivi0403/p/10014841.html
Copyright © 2011-2022 走看看