zoukankan      html  css  js  c++  java
  • day30:TCP&UDP:socket

    目录

    1.TCP协议和UDP协议

    2.什么是socket?

    3.socket正文

      1.TCP基本语法

      2.TCP循环发消息

      3.UDP基本语法

      4.UDP循环发消息

    4.黏包

    5.解决黏包问题

      1.解决黏包方式一:先发送接下来要发送数据的大小

      2.解决黏包方式二:conn.send("00000100".encode())

      3.前戏:struct模块

      4.解决黏包方式三:使用struct模块

    1.TCP协议和UDP协议

    TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)

    优点:可靠,稳定,传输完整稳定,不限制数据大小

    缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认

    应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景

    UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)

    优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接

    缺点:不稳定,不能保证每次数据都能接收到

    应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景

    2.什么是socket?

    socket的意义:通络通信过程中,信息拼接的工具(中文:套接字)

    3.socket正文

    1.TCP基本语法

    服务端

    #  ### 服务端
    import socket
    # 1.创建一个socket对象
    sk = socket.socket()
    
    # 2.绑定对应的ip和端口号(让其他主机在网络中可以找得到)
    """127.0.0.1代表本地ip"""
    sk.bind( ("127.0.0.1",9001) )
    
    # 3.开启监听
    sk.listen()
    
    # 4.建立三次握手
    conn,addr  = sk.accept()
    
    # 5.处理收发数据的逻辑
    """recv 接受 send 发送"""
    res = conn.recv(1024)# 最多一次接受 1024 字节
    print(res.decode("utf-8"))
    
    # 6.四次挥手
    conn.close()
    
    # 7.退还端口
    sk.close()

    客户端

    # ### 客户端
    import socket
    # 1.创建一个socket对象
    sk = socket.socket()
    
    # 2.与服务器建立连接
    sk.connect( ("127.0.0.1",9001) )
    
    # 3.发送数据(只能发送二进制的字节流)
    sk.send("北京昨天迎来了暴雨,如今有车是不行的,还得有船".encode("utf-8"))
    
    # 4.关闭连接
    sk.close()

    2.TCP循环发消息

    服务端

    # ### 服务端
    import socket 
    # 1.创建socket对象
    sk = socket.socket()
    # # 在bind方法之前加上这句话,可以让一个端口重复使用
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    
    # 2.绑定ip和端口号(在网络中注册该主机)
    sk.bind( ("127.0.0.1" , 9000) )
    # 3.开启监听
    sk.listen()
    """
    print(conn)
    print(addr)
    conn:<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9002), raddr=('127.0.0.1', 53620)>
    addr:('127.0.0.1', 53620)
    """
    
    # 5.处理收发数据的逻辑 
    """send(字节流)"""
    """
    conn.send("我去北京先买船".encode("utf-8"))
    """
    while True:
        # 4.三次握手
        conn,addr = sk.accept()
        while True:
            res = conn.recv(1024)
            print(res.decode())
            strvar = input("请输入服务端要给客户端发送的内容")
            conn.send(strvar.encode())
            if strvar.upper() == "Q":
                break
    
    # 6.四次挥手
    conn.close()
    
    # 7.退还端口
    sk.close()

    客户端

    # ### 客户端
    import socket 
    # 1.创建socket对象
    sk = socket.socket()
    # 2.连接服务端
    sk.connect( ("127.0.0.1" , 9000) )
    # 3.收发数据
    """
    res = sk.recv(1024) # 一次最多接受1024个字节
    print(res.decode())
    """
    
    while True:
        strvar = input("请输入您要发送的内容:")
        sk.send(strvar.encode())
        res = sk.recv(1024)
        if res == b"q" or res == b"Q":
            break
        print(res.decode())
    
    # 4.关闭连接
    sk.close()

    3.UDP基本语法

    服务端

    # ### 服务端
    import socket
    # 1.创建udp对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    # 2.绑定地址端口号
    sk.bind( ("127.0.0.1",9000) )
    # 3.udp服务器,在一开始只能够接受数据
    msg,cli_addr = sk.recvfrom(1024)
    
    print(msg.decode())
    print(cli_addr)
    
    # 服务端给客户端发送数据
    msg = "我是你老娘,赶紧给我回家吃饭"
    sk.sendto(msg.encode(),cli_addr)
    
    # 4.关闭连接
    sk.close()

    客户端

    # ### 客户端
    import socket
    # 1.创建udp对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    # 2.收发数据的逻辑
    
    # 发送数据
    msg = "你好,你是mm还是gg"
    # sendto( 消息,(ip,端口号) )
    sk.sendto( msg.encode() ,  ("127.0.0.1",9000)  )
    
    # 接受数据
    msg,server_addr = sk.recvfrom(1024)
    print(msg.decode())
    print(server_addr)
    
    # 3.关闭连接
    sk.close()

    4.UDP循环发消息

    服务端

    # ### 服务端
    import socket
    # 1.创建udp对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    # 2.绑定地址端口号
    sk.bind( ("127.0.0.1",9000) )
    # 3.udp服务器,在一开始只能够接受数据
    while True:
        # 接受消息
        msg,cli_addr = sk.recvfrom(1024)
        print(msg.decode())
        message = input("服务端给客户端发送的消息是?:")
        # 发送数据
        sk.sendto(message.encode() , cli_addr)
    
    
    # 4.关闭连接
    sk.close()

    客户端

    # ### 客户端
    import socket
    # 1.创建udp对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    # 2.收发数据的逻辑
    while True:
        # 发送数据
        message = input("客户端给服务端发送的消息是?:")
        sk.sendto(message.encode(), ("127.0.0.1",9000) )
        
        # 接受数据
        msg,addr = sk.recvfrom(1024)
        print(msg.decode("utf-8"))
        
    
    # 3.关闭连接
    sk.close()

    4.黏包

    1.出现黏包的原因

    tcp协议在发送数据时,会出现黏包现象.

    1.数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,

    缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。

    2.在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度

    导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包

    2.黏包出现的两种情况

    黏包现象一:

    在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包

    黏包现象二:

    在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包

    总结:

    发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包

    核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

    3.黏包的应用场景

    解决黏包场景:

    应用场景在实时通讯时,需要阅读此次发的消息是什么

    不需要解决黏包场景:

    下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.

    5.解决黏包问题

    1.解决黏包方式一:先发送接下来要发送数据的大小

    服务端

    # ### 服务端
    import time
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind( ("127.0.0.1",9000) )
    sk.listen()
    
    conn,addr = sk.accept()
    
    
    # 处理收发数据的逻辑
    
    # 先发送接下来要发送数据的大小
    conn.send("5".encode()) # 发送5个字节
    # 发完长度之后,再发数据
    conn.send("hello".encode())
    conn.send(",world".encode())
    
    conn.close()
    sk.close()

    客户端

    # ### 客户端
    """
    黏包出现的两种情况:
        (1) 发送端发送数据太快
        (2) 接收端接收数据太慢
    """
    import socket
    import time
    sk = socket.socket()
    sk.connect( ("127.0.0.1",9000) )
    
    time.sleep(2) # 睡2s,让其接受速度慢一些,制造黏包效果
    # 处理收发数据的逻辑
    # 先接受接下来要发送数据的大小
    res = sk.recv(1) # res = "5"
    num = int(res.decode()) # num = 5
    # 接受num这么多个字节数
    res1 = sk.recv(num) # 一次最多只能接收5个字节
    res2 = sk.recv(1024)
    print(res1)
    print(res2)
    
    
    sk.close()

    2.解决黏包方式二:conn.send("00000100".encode())

    服务端:

    # ### 服务端
    import time
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind( ("127.0.0.1",9000) )
    sk.listen()
    
    conn,addr = sk.accept()
    
    
    # 处理收发数据的逻辑
    # 先发送接下来要发送数据的大小
    conn.send("00000100".encode())
    # 发完长度之后,再发数据
    msg = "hello" * 20
    conn.send(msg.encode())
    conn.send(",world".encode())
    
    conn.close()
    sk.close()

    客户端:

    # ### 客户端
    """
    黏包出现的两种情况:
        (1) 发送端发送数据太快
        (2) 接收端接收数据太慢
    """
    import socket
    import time
    sk = socket.socket()
    sk.connect( ("127.0.0.1",9000) )
    
    time.sleep(2)
    # 处理收发数据的逻辑
    # 先接受接下来要发送数据的大小
    res = sk.recv(8)
    num = int(res.decode())
    # 接受num这么多个字节数
    res1 = sk.recv(num)
    res2 = sk.recv(1024)
    print(res1)
    print(res2)
    
    
    sk.close()

    其实,这两种写法都存在一定的限制,并非最完美的解决方案

    下面介绍一个模块,用来完美的解决黏包现象

    3.前戏:struct模块

    struct模块里有两个方法:

    pack :把任意长度数字转化成具有固定4个字节长度的字节流

    unpack :把4个字节值恢复成原来的数字,返回最终的是元组

    import struct
    
    # pack
    # i => int 要转化的当前数据是整型
    res1 = struct.pack("i",999999999)
    print(res1,len(res1)) # b'xffxc9x9a;' 4
    res2 = struct.pack("i",1)
    print(res2,len(res2)) # b'x01x00x00x00' 4
    res3 = struct.pack("i",4399999)
    print(res3,len(res3)) # b'x7f#Cx00' 4
    # pack 的范围 -2147483648 ~ 2147483647 21个亿左右
    res4 = struct.pack("i",2100000000) 
    print(res4,len(res4)) # b'x00u+}' 4
    
    
    # unpack
    # i => 把对应的数据转换成int整型
    tup = struct.unpack("i",res)
    print(tup) # (2100000000,)
    print(tup[0]) # 2100000000

    4.解决黏包方式三:使用struct模块

    服务端

    # ### 服务端
    import time
    import socket
    import struct
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind( ("127.0.0.1",9000) )
    sk.listen()
    
    conn,addr = sk.accept()
    
    
    # 处理收发数据的逻辑
    strvar = input("请输入你要发送的数据")
    msg = strvar.encode()
    length = len(msg) # 你输入字符串的长度
    res = struct.pack("i",length) # 无论长度是多少,res都是固定4个字节长度的字节流
    print("---",res)
    
    # 第一次发送的是字节长度
    conn.send(res)
    
    # 第二次发送真实的数据
    conn.send(msg)
    
    # 第三次发送真实的数据
    conn.send("世界真美好123".encode())
    
    
    
    conn.close()
    sk.close()

    客户端

    # ### 客户端
    """
    黏包出现的两种情况:
        (1) 发送端发送数据太快
        (2) 接收端接收数据太慢
    """
    import socket
    import time
    import struct
    sk = socket.socket()
    sk.connect( ("127.0.0.1",9000) )
    
    time.sleep(2)
    # 处理收发数据的逻辑
    
    # 第一次接受的是字节长度
    n = sk.recv(4) # 接收到4个字节长度的字节流
    tup = struct.unpack("i",n) # 将4个字节长度的字节流转化成数字
    n = tup[0] # n就是长度
    
    
    # 第二次接受真实的数据
    res = sk.recv(n)
    print(res.decode())
    
    # 第三次接受真实的数据
    res = sk.recv(1024)
    print(res.decode())
    sk.close()

    struct如何做到控制接受字节数的呢?

  • 相关阅读:
    win8应用的编码UI测试
    什么是Peer Review
    Android开发环境的搭建
    运用int.parse()判断闰年代码实现
    等价类划分方法的应用之EditBox(二)
    等价类划分方法的应用之EditBox
    集成测试
    数据可视化简介
    关于processing
    白盒测试VS黑盒测试
  • 原文地址:https://www.cnblogs.com/libolun/p/13498625.html
Copyright © 2011-2022 走看看