• TCP Socket 套接字 和 粘包问题


    一、Scoket 套接字

    Scoket是应用层(应用程序)与TCP/IP协议通信的中间软件抽象层,它是一组接口。也可以理解为总共就三层:应用层,scoket抽象层,复杂的TCP/IP协议

    基于TCP协议的scoket   tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

    scoket 简单版本   send和recv是相辅相成的,必须要配对使用。recv是跟内存要数据,至于数据来源你无需考虑。

    注意send和recv:  send发送的数据只能是二进制数据,recv只能填写数字,表示接收数据大小      (******)

    TCP特点会将数据量比较小的并且时间间隔比较短的数据一次性打包发送给对方

    server端

    import socket
    
    server = socket.socket()  #创建一个服务端对象
    server.bind(('127.0.0.1',8080))  # bind((host,port)) #绑定ip和端口
    server.listen(5)  半连接 池限制客户端连接用户个数
    
    conn, addr = server.accept()  #等待数据 conn是传输通道(双向通道)  addr是客户端地址
    data = conn.recv(1024) #recv接收客户端传过来的数据,注意recv里面只能填数字
    print(data)
    conn.send(b'hello baby~') #send给客户端发送信息,二进制数据
    
    conn.close() #关闭通道
    server.close() #关闭服务端

    client端

    import socket
    
    client = socket.socket()  #创建客户端对象
    client.connect(('127.0.0.1',8080))  #连接服务端的ip和port
    
    client.send(b'hello world!') #send向服务端发送消息,二进制数据
    data = client.recv(1024)   #recv接收服务端的消息,recv只能接收数字,这个数字代表接收数据大小
    print(data)
    
    client.close() #关闭客户端

    二、解决通信循环的问题

    服务端需要满足这两点:

      1.要有固定的ip和port

      2.24小时不间断提供服务

    上面的简单版本的socket发送的信息是有限的,虽然它是满足了有固定的ip和port,但是它不是一直可以访问,所以在客户端和服务端分别写了循环,在客户端不断输入,在服务端不断接收打印。

    server端   内层循环是不断接收从客户端传过来的数据      判断如果有客户端断开连接抛出异常 ConnectionRestError,然后break

    import socket
    
    server = socket.socket()  # 生成一个对象
    server.bind(('127.0.0.1',8080))  # 绑定ip和port
    server.listen(5)  # 半连接池
    
    while True:
        conn, addr = server.accept()  # 等到别人来  conn就类似于是双向通道
        while True:
            try:
                data = conn.recv(1024)
                print(data)  # b''
                if len(data) == 0:break  #针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b'',这步可写可不写
                conn.send(data.upper())
            except ConnectionResetError as e:   #客户端异常退出错误接收
                print(e)
                break
        conn.close()

    client端

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))   #连接服务端
    
    while True:
        msg = input('>>>:').encode('utf-8')  #客户端输入
        if len(msg) == 0:continue   #判断如果输入为空,continue再一次输入
        client.send(msg)  #给服务端传送数据,msg必须是二进制格式数据
        data = client.recv(1024)  #接收服务端的传送数据
        print(data)

    三、TCP粘包问题

    发生粘包:发送方执行发送命令(数据量过大),接收方得到的结果很可能只有一部分,在执行下次接收命令的时候又接收到之前执行的另外一部分结果,这种显现就是粘包。

    官方解释会发生粘包的两种情况:

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

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

    总结:粘包现在只发生在tcp协议中:

    1、从表面上看,粘包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

    2、实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。

    粘包的解决方案:

    问题根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕如何让发送端在发送数据前,把要发送数据的大小让接收端知道,然后接收端来一个死循环接收所有数据。我们可以借助struct模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接收这个固定长度字节的内容看一看接下来要接收的信息大小,,那么最终接收的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据。

    我们还可以把struct创建的报头做成字典,把真实数据存在字典里面,然后json序列化,然后用struct将序列化后的数据长度打包。

    解决粘包问题思路:

      服务端:

        1.先制作一个发送给客户端的字典(这个字典里面包括要传输数据的大小)

        2.制作字典的报头(使用struct.pack)

        3.发送字典的报头

        4.发送字典

        5.发送真实数据

    import socket
    import subprocess
    import struct
    import json
    
    server = socket.socket()  #创建一个对象
    server.bind(('127.0.0.1',8080))  #绑定ip和端口
    server.listen(5)  #半连接池  限制请求用户个数
    
    while True:
        conn, addr = server.accept()  #等待 conn是传输通道(双向通道)  addr是客户端的地址
        while True:
            try:
                cmd = conn.recv(1024)  #recv(第一次接收)
                if len(cmd) == 0:break  #针对mac和linux客户端异常退出之后 服务端不会报错 只会一直接受 b''
                cmd = cmd.decode('utf-8')  #网络传输的是二进制数据,需要decode转换成字符串数据
                #获取数据
                obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()
                d = {'name':'jason','file_size':len(res),'info':'asdhjkshasdad'}
                # dumps成字符串,后面好变成二进制数据传输
                json_d = json.dumps(d)
                # 1.先制作一个字典的报头
                header = struct.pack('i',len(json_d))
                # 2.发送字典报头
                conn.send(header)  #send(第一次传)
                # 3.发送字典
                conn.send(json_d.encode('utf-8'))
                # 4.再发真实数据
                conn.send(res)
    
            except ConnectionResetError:
                break
        conn.close()

      客户端:

        1.先接收字典的报头

        2.解析拿到字典的数据长度

        3.接收字典

        4.从字典中获取真实数据的长度

        5.接收真实数据

    import socket
    import struct
    import json
    
    client = socket.socket()  #创建一个对象
    client.connect(('127.0.0.1',8080))  #连接服务端的ip和端口
    
    while True:
        msg = input('>>>:').encode('utf-8')  #客户端手动输入
        if len(msg) == 0:continue   #判断如果用户没有输入,返回继续让用户输入
        client.send(msg)  #把输入的传给服务端   send(第一次传)
        # 1.先接受字典报头
        header_dict = client.recv(4) # recv (第一次接受)
        # 2.解析报头 获取字典的长度
        dict_size = struct.unpack('i',header_dict)[0]  # 解包的时候一定要加上索引0
        # 3.接收字典数据
        dict_bytes = client.recv(dict_size)
        dict_json = json.loads(dict_bytes.decode('utf-8'))
        # 4.从字典中获取信息
        print(dict_json)
        recv_size = 0
        real_data = b''
        while recv_size < dict_json.get('file_size'):  # real_size = 102400
            data = client.recv(1024)
            real_data += data
            recv_size += len(data)
        print(real_data.decode('gbk'))
  • 相关阅读:
    关于this的指向问题
    blued面经
    数美(sm)面经
    xue球 面经
    jquery中的$("#id")与document.getElementById("id")的区别
    如何知道iframe文件下载download完成
    前端linux基础
    Vue.js 初级面试题
    React 面试题
    从输入URL到页面加载的过程
  • 原文地址:https://www.cnblogs.com/wangcuican/p/11317617.html
Copyright © 2020-2023  润新知