• socket用法(TCP, UDP)


    socket : 通络通信过程中,信息拼接的工具(中文:套接字)
    开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉), 在bind方法之前加上这句话,可以让一个端口重复使用: sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

    TCP: 服务端和客户端简单的一次性通信

    # 服务端
    import socket
    # 1. 创建socket对象
    sk = socket.socket()
    
    # 2. 绑定对应IP和端口(注册网络,让其他主机能够找到)
    sk.bind(("127.0.0.1",9000))  # 127.0.0.1 代表本地IP
    
    # 3. 开启监听
    sk.listen()
    
    # 4. 建立三次握手 addr接收客户端的IP和端口号
    conn,addr = sk.accept()
    
    # 5. 收发数据(recv里面的参数单位是字节,表示一次最多接收多少数据)
    res = conn.recv(1024)
    print(res)
    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",9000))
    
    # 3. 发送数据
    sk.send("今天下了一天的雨".encode("utf-8"))
    
    # 4. 关闭连接
    sk.close()

    TCP: 服务端和客户端循环通信

    # TCP循环通信:
    # 服务端
    import socket
    # 1. 创建一个socket对象
    sk = socket.socket()
    # 2. 在网络中注册主机(绑定IP和端口)
    sk.bind(("127.0.0.1",9000))
    # 3. 监听
    sk.listen()
    # 4. 建立三次握手
    # 5. 接收数据
    while True:   # 保证服务端永不关闭,随时能跟客户端连接
        conn, addr = sk.accept()
        while True:
            res = conn.recv(1024)
            print(res.decode("utf-8"))
            str1 = input("服务端想给客户端发送: ")
            conn.send(str1.encode("utf-8"))
            if str1.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. 发送数据
    while True:
        strvar = input("客户端想给服务端发送的消息为: ")
        sk.send(strvar.encode("utf-8"))
        res = sk.recv(1024)
        if res.upper() == b"Q":
            break
        print(res.decode("utf-8"))
    
    # 4.断开连接
    sk.close()

    TCP的黏包现象: 

    (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
    缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。


    (2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
    导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包

    黏包现象一:
    在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
    黏包现象二:
    在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
    总结:
    发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
    核心是因为tcp对数据无边界截取,不会按照发送的顺序判断

    # TCP的黏包现象
    # 服务端
    import socket
    sk = socket.socket()
    sk.bind(("127.0.0.1",9000))
    sk.listen()
    coon,addr = sk.accept()
    
    coon.send("下了一天的雨".encode())
    coon.send("适合发呆".encode())
    coon.close()
    sk.close()
    
    
    # 客户端
    import socket
    import time
    sk = socket.socket()
    sk.connect(("127.0.0.1",9000))
    time.sleep(0.1)
    
    res1 = sk.recv(1024)
    print(res1.decode())
    res2 = sk.recv(1024)
    print(res2.decode())
    # 下了一天的雨适合发呆  两个数据粘在了一起,这就是数据的黏包
    
    sk.close()

    TCP黏包解决方式:

    # TCP的黏包现象
    # 服务端
    # 方式一:
    import socket
    sk = socket.socket()
    sk.bind(("127.0.0.1",9000))
    sk.listen()
    coon,addr = sk.accept()
    # 在发送真实数据之前,先发送真实数据的大小
    coon.send("18".encode())
    coon.send("下了一天的雨".encode())
    coon.send("适合发呆".encode())
    coon.close()
    sk.close()
    
    
    # 客户端
    import socket
    import time
    sk = socket.socket()
    sk.connect(("127.0.0.1",9000))
    time.sleep(0.1)
    
    res = sk.recv(2)
    n = int(res.decode())
    
    res1 = sk.recv(n)
    print(res1.decode())
    res2 = sk.recv(1024)
    print(res2.decode())
    # 下了一天的雨
    # 适合发呆
    
    sk.close()
    
    ##############
    # TCP的黏包现象
    # 服务端
    # 方式二:
    import socket
    sk = socket.socket()
    sk.bind(("127.0.0.1",9000))
    sk.listen()
    coon,addr = sk.accept()
    # 在发送真实数据之前,先发送真实数据的大小
    coon.send("00000018".encode())  # 以免客户端每次都要改第一次接收字节数
    coon.send("下了一天的雨".encode())
    coon.send("适合发呆".encode())
    coon.close()
    sk.close()
    
    
    # 客户端
    import socket
    import time
    sk = socket.socket()
    sk.connect(("127.0.0.1",9000))
    time.sleep(0.1)
    
    res = sk.recv(8)  # 将第一次接受的大小固定
    n = int(res.decode())
    
    res1 = sk.recv(n)
    print(res1.decode())
    res2 = sk.recv(1024)
    print(res2.decode())
    # 下了一天的雨
    # 适合发呆
    
    sk.close()
    
    # 但是方式一和方式二都太麻烦,每次都需要改动

    TCP黏包解决方式三(推荐)

    """
    pack: 把任意长度的数字转化成居右4个字节的固定长度的字节流
    
    unpack: 把4个字节值恢复成原本的数字,返回是元组
    # 整型用i表示, 浮点型用f表示, 字符串用s表示
    """
    
    # TCP的黏包现象
    # 服务端
    # 方式三: 引入struct
    import socket
    import struct
    sk = socket.socket()
    sk.bind(("127.0.0.1",9000))
    sk.listen()
    coon,addr = sk.accept()
    
    strvar = input("请输入数据:")
    msg = strvar.encode()
    length = len(msg)
    res = struct.pack("i",length)  # 将要发送的第一个数据的长度先用struct进行压缩
    coon.send(res)
    coon.send(strvar.encode())
    coon.send("适合发呆".encode())
    coon.close()
    sk.close()
    
    
    # 客户端
    import socket
    import time
    import struct
    sk = socket.socket()
    sk.connect(("127.0.0.1",9000))
    time.sleep(0.1)
    
    res = sk.recv(4)  # struct压缩的数据永远都是4个字节
    tup = struct.unpack("i",res)
    print(tup[0])  # 18
    
    res1 = sk.recv(tup[0])
    print(res1.decode())
    res2 = sk.recv(1024)
    print(res2.decode())
    # 下了一天的雨
    # 适合发呆
    
    sk.close()

    解决黏包场景: 应用场景在实时通讯时,需要阅读此次发的消息是什么
    不需要解决黏包场景: 下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.

    udp数据传输简单通信
    # dup 数据传输
    # 服务端
    import socket
    # 1. 创建一个socket对象 type=socket.SOCK_DGRAM表明创建的是UDP协议对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    # 2. 绑定IP和端口号
    sk.bind(("127.0.0.1",9000))
    
    # 4. 接收消息,(UDP作为服务端的时候,第一次肯定是接收消息)
    msg,cli_addr = sk.recvfrom(1024)
    print(msg.decode())  # 今天天气咋样啊?
    print(cli_addr) # ('127.0.0.1', 64877)
    
    # 发送消息给客户端
    msg = "下了一天的雨"
    sk.sendto(msg.encode(),cli_addr)
    
    # 5. 关闭连接
    sk.close()
    
    
    # 客户端
    import socket
    # 1. 创建socket对象
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    # 2. 发送数据
    msg = "今天天气咋样啊?"
    sk.sendto(msg.encode(),("127.0.0.1",9000))
    
    msg,ser_addr = sk.recvfrom(1024)
    print(msg.decode())  # 下了一天的雨
    print(ser_addr) # ('127.0.0.1', 9000)
    
    # 3. 断开连接
    sk.close()

    udp 数据传输循环通信

    # udp循环通信
    # 服务端
    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    sk.bind(("127.0.0.1",9000))
    
    while True:
        while True:
            msg,cli_addr = sk.recvfrom(1024)
            print(msg.decode())
    
            msg = input("服务端给客户端发送的消息: ")
            sk.sendto(msg.encode(), cli_addr)
            if msg.upper() == "Q":
                break
    
    sk.close()
    
    
    
    # 客户端
    import socket
    sk = socket.socket(type=socket.SOCK_DGRAM)
    
    while True:
        msg = input("客户端给服务端发送的消息: ")
        sk.sendto(msg.encode(),("127.0.0.1",9000))
    
        msg, ser_addr = sk.recvfrom(1024)
        if msg.upper() == b"Q":
            break
        print(msg.decode())
    
    sk.close()

    tcp协议:
    缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包
    优点:不限制数据包的大小,稳定传输不丢包

    udp协议:
    优点:接收时候数据之间有边界,传输速度快,不黏包
    缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

    tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
    但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
    而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包

  • 相关阅读:
    百度编辑器 Ueditor使用记录
    JS实现继承的几种方式
    IOS 浏览器上设置overflow: auto 不可滚动
    throw new Error('Cyclic dependency' + nodeRep)
    如何理解springaop
    SQL连接的分类
    Eclipse创建Maven-Web项目及解决 jre版本和web.xml版本问题
    SQL的几种连接:内连接、左联接、右连接、全连接、交叉连接
    Centos7下面安装eclipse
    Centos7 下编译 Openjdk8
  • 原文地址:https://www.cnblogs.com/fdsimin/p/13052153.html
Copyright © 2020-2023  润新知