Tcp协议与Udp协议

规定了数据传输所遵循的规则
数据传输能够遵循的协议有很多 Tcp与Udp是比较为常见的两个

一、Tcp协议

    三次握手
    建立双向通道

image

    四次挥手
    断开双向通道

image

基于Tcp传输数据非常的安全 因为有双向通道是否正确?
  基于Tcp传输,数据不容易丢失 不容易丢失的原因在于二次确认机制
  每次发送数据都需要返回确认信息 否则会在一定的时间内反复发送

二、Udp协议

  基于Udp协议发送数据 没有任何的通道也没有任何的限制
  Udp发送数据没有Tcp安全(没有两次确认机制)

socket套接字

一、基于文件类型的套接字家族
  套接字家族的名字:AF_UNIX

二、基于网络类型的套接字家族
  套接字家族的名字:AF_INET

三、基于 Tcp 的scoket服务端与客户端
  运行程序的时候 确保是服务端先运行 之后才是客户端

简易版

服务端

import socket
# 1.创建一个socket对象
server = socket.socket()  # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080))  # 127.0.0.1本地回环地址(只允许自己的机器访问)
# 3.半连接池(暂且忽略)
server.listen(5)
# 4.开业 等待接客
sock, address = server.accept()
print(sock, address)  # sock是双向通道 address是客户端地址
# 5.数据交互
sock.send(b'hello big baby~')  # 朝客户端发送数据
data = sock.recv(1024)  # 接收客户端发送的数据 1024bytes
print(data)
# 6.断开连接
sock.close()  # 断链接
server.close()  # 关机

客服端

import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1', 8080))
# 3.数据交互
data = client.recv(1024)  # 接收服务端发送的数据
print(data)
client.send(b'hello sweet server')  # 朝服务端发送数据
# 4.关闭
client.close()

代码优化版本

1.send与recv
客户端与服务端不能同时执行同一个
	有一个收 另外一个就是发
	有一个发 另外一个就是收
		不能同时收或者发!!!
2.消息自定义
	input获取用户数据即可(主要编码解码)
3.循环通信
	给数据交互环节添加循环即可
4.服务端能够持续提供服务
	不会因为客户端断开连接而报错
	解决措施:使用异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
5.消息不能为空
	判断是否为空 如果是则重新输入(主要针对客户端)
6.服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
	这个错在苹果系统报的频繁 windows频率比较少
	from socket import SOL_SOCKET,SO_REUSEADDR
	server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加上此代码
7.客户端异常退出会发送空消息(针对mac linux)
	针对接收的消息加判断处理即可

服务端

import socket
from socket import SOL_SOCKET, SO_REUSEADDR

# 1.创建一个socket对象
server = socket.socket()  # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
server.bind(('127.0.0.1', 8080))  # 127.0.0.1本地回环地址(只允许自己的机器访问)
# 3.半连接池(暂且忽略)
server.listen(5)
# 4.开业 等待接客
while True:
    sock, address = server.accept()
    print(sock, address)  # sock是双向通道 address是客户端地址
    # 5.数据交互
    while True:
        try:
            msg = input('请输入发送给客户端的消息>>>:').strip()
            if len(msg) == 0: continue
            sock.send(msg.encode('utf8'))  # 朝客户端发送数据
            data = sock.recv(1024)  # 接收客户端发送的数据 1024bytes
            if len(data) == 0:  #
                break
            print(data.decode('utf8'))
        except ConnectionResetError:
            sock.close()
            break

客服端

import socket

# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1', 8080))
# 3.数据交互
while True:
    data = client.recv(1024)  # 接收服务端发送的数据
    print(data.decode('utf8'))
    msg = input('请输入发送给客户端的消息>>>:').strip()
    if len(msg) == 0:
        msg = '手抖了一下 暂无消息'
    client.send(msg.encode('utf8'))  # 朝服务端发送数据

半连接池

server.listen(5)
  设置最大等待人数
  主要是为了做缓冲 避免太多的无效等待

黏包问题

服务端代码
	sock.recv(1024)
	sock.recv(1024)
	sock.recv(1024)
客户端代码
	client.send(b'hello')
	client.send(b'joker')
	client.send(b'jason')
"""
执行的结果
 b'hellojokerjason'
 b''
 b''
"""

一、Tcp的特性
  流式协议:所有的数据类似于水流 连接在一起
    数据量很小 并且时间间隔很多 那么就会自动组织到一起

二、recv
  我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包现象

解决黏包问题

struct模块

import struct

info = '今天星期五了哎!'
print(len(info))  # 8  数据原来的长度
res = struct.pack('i', len(info))  # 将原来的数据长度打包
print(len(res))  # 4  打包之后的长度是4
ret = struct.unpack('i', res)  # 将打包之后固定长度为4的数据拆包
print(ret[0])  # 8  又得到了原来数据的长度

info1 = '过了今天就明天就是一个愉快的周末了,但还是得敲可恶选课系统'
print(len(info1))  # 29
res = struct.pack('i', len(info1))  # 将原来的数据长度打包
print(len(res))  # 4  打包之后的长度是4
ret = struct.unpack('i', res)
print(ret[0])  # 29

pack可以将无论数字长度是多少的 都可以打包成固定的长度
unpack可以将基于该固定的长度 反向解析出真实的长度
思路:
  服务端
  1.先将真实数据的长度制作成固定长度
  2.先发送固定长度的报头
  3.再发送真实数据

  客服端
  1.先接收固定长度的报头
  2.再根据报头解压出真实长度
  3.根据真实长度接收即可

代码实操

服务端

while True:
    sock,addr = server.accept()
    while True:
        # 1.先构造数据文件的字典
        file_dict = {
            'file_name':'精彩写真',
            'file_size':os.path.getsize(r'精彩写真'),
            'file_content':'美女图片',
            'file_root':'joker'
        }
        # 2.将文件字典打包成固定长度数据
        dict_json = json.dumps(file_dict)
        file_bytes_dict = len(dict_json.encode('utf8'))
        dict_len = struct.pack('i',file_bytes_dict)
        # 3.发送固定长度的字典报头
        sock.send(dict_len)
        # 4.发送真实字典数据  将字典序列化成json格式字符串
        sock.send(dict_json.encode('utf8'))
        # 5.发送真实数据
        with open(r'精彩写真.txt','rb')as f:
            for line in f:
                sock.send(line)
        break

客户端

import socket
import struct
import json

client = socket.socket()
client.connect(('127.0.0.1',8088))


while True:
    # 1.先接收长度为4的报头数据
    header_len = client.recv(4)
    # 2.根据报头解包出字典的长度
    dict_len = struct.unpack('i',header_len)[0]
    # 3.直接接收字典数据
    dict_data = client.recv(dict_len)  # bytes类型
    # 4.解码并反序列化出字典
    real_dict = json.loads(dict_data)
    # 5.从数据字典中获取真实数据的各项信息
    total_size = real_dict.get('file_size')
    # client.recv(total_size)  # 简单粗暴的方法 有多少一次性就读读多少
    file_size = 0
    with open(r'%s'%real_dict.get('file_name'),'wb')as f:
        while file_size < total_size:
            data = client.recv(1024)
            f.write(data)
            file_size += len(data)
 posted on 2022-08-07 12:33  Joker_Ly  阅读(113)  评论(0)    收藏  举报