网络tcp与udp协议

TCP协议

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

UDP协议

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头

TCP粘包问题

出现的原因

1、tcp是流式协议,数据像水流一样粘在一起,没有任何边界区分
2、收数据没收干净,有残留,就会跟下次结果混淆在一起

解决粘包问题的核心法门

每次都收干净,不要有任何残留

解决粘包问题的思路

1、拿到数据的总大小
2、recv_size = 0,循环接收,每接收一次,recv_size+=接收长度
3、直到recv_size = total_size,结束循环

TCP客户端与服务端代码(解决粘包问题终极版)

客户端

import json
import struct
from socket import *

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

while True:
    cmd = input('请输入命令>>:').strip()
    if len(cmd) == 0: continue
    client.send(cmd.encode('utf-8'))

    # 接收端
    # 1、先收4个字节,从中提取接下来要收的头的长度
    x = client.recv(4)
    header_len = struct.unpack('i', x)[0]

    # 2、接收头,并解析
    json_str_bytes = client.recv(header_len)
    json_str = json_str_bytes.decode('utf-8')
    header_dic = json.loads(json_str)
    print(header_dic)
    total_size = header_dic["total_size"]

    # 3、接收真实的数据
    recv_size = 0
    while recv_size < total_size:
        recv_data = client.recv(1024)
        recv_size += len(recv_data)
        print(recv_data.decode('utf-8'), end='')
    else:
        print()

服务端

# 服务端应该满足两个特点:
# 1、一直对外提供服务
# 2、并发地服务多个客户端
import json
import struct
import subprocess
from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 就是它,在bind前加
server.bind(('127.0.0.1', 8080))
server.listen(5)

#  服务端应该做两件事
# 第一件事:循环地从板连接池中取出链接请求与其建立双向链接,拿到链接对象
while True:
    conn, client_addr = server.accept()

    # 第二件事:拿到链接对象,与其进行通信循环
    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0: break
            obj = subprocess.Popen(cmd.decode('utf-8'),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE
                                   )

            stdout_res = obj.stdout.read()
            stderr_res = obj.stderr.read()
            total_size = len(stdout_res) + len(stderr_res)

            # 1、制作头
            header_dic = {
                "filename": "a.txt",
                "total_size": total_size,
                "md5": "123123xi12ix12"
            }

            json_str = json.dumps(header_dic)
            json_str_bytes = json_str.encode('utf-8')

            # 2、先把头的长度发过去
            x = struct.pack('i', len(json_str_bytes))   # i 类型储存的长度绝对够用
            conn.send(x)

            # 3、发头信息
            conn.send(json_str_bytes)

            # 4、再发真实的数据
            conn.send(stdout_res)
            conn.send(stderr_res)

        except Exception:
            break
    conn.close()

posted @ 2023-10-21 20:49  PiggThird  阅读(39)  评论(0)    收藏  举报