第24天 socket收发消息的原理 tcp协议的Nagle算法 tcp自定义协议解决粘包问题 struct模块的使用 udp协议的使用 tcp与udp并发编程实现

为什么会产生粘包问题
只有tcp有粘包的现象,udp永远不会有粘包
两种情况下会发生粘包。
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

socket收发消息的原理
socket客户端向socket服务端发送消息,首先由客户端把消息发送给操作系统的缓存,由操作系统调用网卡发送给对方操作系统的缓存,
服务端的应用程序会从操作系统的缓存中接收数据。无论是发数据还是收数据,都是跟自己操作系统的缓存打交道。
所以说并不是一个send对应一个recv。
在这里插入图片描述

tcp协议的Nagle算法:
tcp协议会将数据量比较小,并且发送间隔比较短的多个数据合成一个数据块,进行封包一次性发送,这样可以提高效率。
这样,接收端就难于分辨出来了,所以可能会触发粘包问题。

struct模块的使用

import struct

header = struct.pack("i", 12312312)  # struct.pack可以将一个整型打包成bytes类型,并且bytes固定长度为4个字节
print(header)  # b'\xf8\xde\xbb\x00'
print(len(header))  # 4

total_size = struct.unpack("i", header)[0]  # struct.unpack会将打包好的bytes类型,反解成打包前的整型数字(12312312,)
print(total_size)  # 12312312


使用struct.pack中的“i”将int类型打包成bytes类型,数字有长度的限制,bytes的固定长度为4个字节,
使用struct.pack中的“q”将int类型打包成bytes类型,固定长度会增加一些,bytes的固定长度为8个字节

tcp自定义协议解决粘包问题代码展示
导入struct模块将int类型打包成bytes类型
导入struct模块将bytes类型反解成int类型

客户端

import socket
import struct

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    cmd = input("shu ru").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode("utf-8"))

    # 先接收数据的长度
    header = client.recv(4)
    total_size = struct.unpack("i", header)[0]  # struct.unpack会将打包好的bytes类型,反解成打包前的整型数字

    # 再接收真正的数据
    recv_size = 0
    res = b""
    while recv_size < total_size:  # 循环取值,直到把total_size取空为止
        data = client.recv(1024)  # 接收服务端传过来的数据bytes类型
        recv_size += len(data)  # 统计传过来的数据长度
        res += data  # 统计服务端过来的bytes类型的数据

    print(res.decode("gbk"))

服务端

import socket
import subprocess
import struct

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    print(client_addr)
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            cmd = data.decode("utf-8")
            obj = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()
            total_size = len(res1) + len(res2)

            # 先把数据的长度发过去
            header = struct.pack("i", total_size)    # struct.pack可以将一个total_size打包成bytes类型,并且bytes固定长度为4个字节
            conn.send(header)  # 数据的长度

            # 再发送真正的数据
            conn.send(res1)
            conn.send(res2)

        except Exception:
            break
    conn.close()

tcp自定义协议解决粘包问题升级版代码展示
导入报头说明,形式为字典,导入struct模块进行数据类型的转换,导入json模块进行编码与解码

客户端

粘包问题升级版
import socket
import struct
import json

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    cmd = input("shu ru").strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode("utf-8"))

    # 先收4个字节,提取header_bytes的长度
    header_bytes_len = struct.unpack("i", client.recv(4))[0]

    # 再收报头字典header——bytes,提取header_dic
    header_bytes = client.recv(header_bytes_len)
    header_json = header_bytes.decode("utf-8")
    header_dic = json.loads(header_json)
    print(header_dic)
    total_size = header_dic["total_size"]

    # 再接收真正的数据
    recv_size = 0
    res = b""
    while recv_size < total_size:  # 循环取值,直到把total_size取空为止
        data = client.recv(1024)  # 接收服务端传过来的数据bytes类型
        recv_size += len(data)  # 统计传过来的数据长度
        res += data  # 统计服务端传过来的bytes类型的数据

    print(res.decode("gbk"))

服务端

粘包问题升级版
import socket
import subprocess
import struct
import json

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)

while True:
    conn, client_addr = server.accept()
    print(client_addr)
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            cmd = data.decode("utf-8")
            obj = subprocess.Popen(cmd, shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )
            res1 = obj.stdout.read()
            res2 = obj.stderr.read()

            header_dic = {
    
    
                "filename": "a.txt",
                "total_size": len(res1) + len(res2),
                "md5": "55lala55"
            }
            header_json = json.dumps(header_dic)
            header_bytes = header_json.encode("utf-8")

            # 先发4个字节
            conn.send(struct.pack("i", len(header_bytes)))
            # 再发报头字典
            conn.send(header_bytes)
            # 最后再发送真正的数据
            conn.send(res1)
            conn.send(res2)

        except Exception:
            break
    conn.close()

udp协议的使用
udp是无链接的,先启动哪一端都不会报错
udp协议只收本次的收的最大范围,多余的数据会直接丢失,没有tcp协议的缓存与粘包这些特性,udp协议又被称为不可靠协议

客户端

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)   # udp协议又称为数据报

while True:
    msg = input("shuru").strip()
    client.sendto(msg.encode("utf-8"), ("127.0.0.1", 8080))
    data, client_addr = client.recvfrom(1024)
    print(data.decode("utf-8"))

client.close()

服务端

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    # udp协议又称为数据报
server.bind(("127.0.0.1", 8080))

while True:
    conn, client_addr = server.recvfrom(1024)
    print(conn.decode("utf-8"))
    server.sendto(conn.upper(), client_addr)

server.close()

并发与并行
并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)
并行:同时运行,只有具备多个cpu才能实现并行

tcp协议下并发功能实现的代码

客户端

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))

while True:
    msg = input("shu ru")
    if len(msg) == 0:
        continue
    client.send(msg.encode("utf-8"))
    data = client.recv(1024)
    print(data.decode("utf-8"))

socket.close()

服务端

import socketserver


class MyRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                data = self.request.recv(1024)    # self.request代表conn,表示连接通道
                print(data.decode("utf-8"))
                if len(data) == 0:
                    break
                self.request.send(data.upper())
            except Exception:
                break
        self.request.close()


server = socketserver.ThreadingTCPServer(("127.0.0.1", 8080), MyRequestHandler)
server.serve_forever()

udp协议下并发功能实现的代码

客户端

import socket

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

while True:
    msg = input("shuru").strip()
    client.sendto(msg.encode("utf-8"), ("127.0.0.1", 8080))
    data, server_addr = client.recvfrom(1024)
    print(data.decode("utf-8"))

服务端

客户端每发来一条消息,都会起一个线程,会把客户端发来的消息封装到对象里面去,交给一个线程来运行handle方法

import socketserver


class MyRequestHanlder(socketserver.BaseRequestHandler):
    def handle(self):
        data, server = self.request  # data代表客户端发来的数据,server是套接字对象
        server.sendto(data.upper(), self.client_address)  # 服务端给客户端回消息


server = socketserver.ThreadingUDPServer(("127.0.0.1", 8080), MyRequestHanlder)  # 这行代码等同于定义了一个套接字对象,绑定了ip跟端口
server.serve_forever()  # 这行代码代表了循环给客户端收发消息

猜你喜欢

转载自blog.csdn.net/Yosigo_/article/details/112795174