网络编程二

  • TCP与UDP协议
  • socket套接字编程
  • 通信相关操作(cs架构软件)
  • TCP黏包问题及解决思路

传输层

1.PORT协议
2.TCP协议与UDP协议
	规定了数据传输所遵循的规则
ps:数据传输能够遵循的协议有很多 TCP和UDP是较为常见的两个
    
    
TCP协议
	三次握手
   		建立双向通道
         ps:洪水攻击
           同时让大量的客户端朝服务端发送建立TCP连接的请求
 	四次挥手
    	断开双向通道
         中间的两步不能合并(需要有检查的时间)	
"""
基于TCP传输数据非常的安全 因为有双向通道
	基于TCP传输数据,数据不容易丢失!!! 不容易丢失的原因在于二次确认机制
		每次发送数据都需要返回确认消息 否则在一定的时间会反复发送
"""  

UDP协议
	基于UDP协议发送数据 没有任何的通道也没有任何的限制
		UDP发送数据没有TCP安全(没有二次确认机制)
"""
TCP类似于打电话:你一句我一句 有来有往
UDP类似于发短信:只要发送了 不管别人看没看到 也不管回不回复
"""

image

socket套接字

基于文件类型的套接字家族
	套接字家族的名字:AF_UNIX
基于网络类型的套接字家族
	套接字家族的名字:AF_INET

"""运行程序的时候  肯定是先确保服务端运行 之后才是客户端"""
#服务端
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.数据交互
msg = input('请输入发送给客户端的消息>>>:').strip()  # 自定义消息
sock.send(msg.encode('utf8'))  # 朝客户端发送数据需要编码
data = sock.recv(1024)  # 接收客户端发送的数据 1024bytes
print(data.decode('utf8'))   #接收打印需要解码
# 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.decode('utf8'))
msg = input('请输入发送给客户端的消息>>>:').strip()
client.send(msg.encode('utf8'))  # 朝服务端发送数据
# 4.关闭
client.close()

代码优化

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

半连接池

server.listen(5)
	主要是为了做缓冲 避免太多无效等待

1.TCP特性
流式协议:所有的数据类似于水流 连接在一起的
ps:数据量很小 并且时间间隔很多 那么就会自动组织到一起
2.recv
我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包

struct模块无论数据长度是多少 都可以帮你打包成固定长度

然后基于该固定长度 还可以反向解析出真实长度
思路
        1.先将真实数据的长度制作成固定长度 4
        2.先发送固定长度的报头
        3.再发送真实数据
    
        1.先接收固定长度的报头  4
        2.再根据报头解压出真实长度 
        3.根据真实长度接收即可
"""
struct模块针对数据量特别大的数字没有办法打包!!!

基于局域网下载功能代码实现

服务端

import json
import os.path
import socket
import struct
from socket import  SOL_SOCKET,SO_REUSEADDR
# 1创建一个socket对象准备接客
server = socket.socket()  # 产生客户端对象
# 2.绑定固定ip地址
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1', 8088))  # 绑定一个固定的ip地址地址是元组
# 3.半连接池
server.listen(5)
# 4.开门接客
sock, address = server.accept()
while True:
    file_dict = {'file_name':'少妇合集.mp3','file_size':os.path.getsize('/Users/ltianhao/Desktop/许茹芸-留低锁匙 (广东).mp3'),
                 'file_deco':'内容很精彩不要错过','file_path':r'/Users/ltianhao/Desktop/许茹芸-留低锁匙 (广东).mp3'}
    file_json = json.dumps(file_dict)  # 将字典序列化
    file_bytes = len(file_json.encode('utf8'))  # 编码之后计算编码之后的数量
    dict_len = struct.pack('i',file_bytes)  # 将编码之后的数量打包成报头
    sock.send(dict_len)  # 发送报头
    sock.send(file_json.encode('utf8'))  # 发送真实的字典
    with open(r'/Users/ltianhao/Desktop/许茹芸-留低锁匙 (广东).mp3','rb') as f:
        for i in f:
            print(i)
            sock.send(i)  # 循环发送每次发一行
    break

客户端

import socket
import struct
import json
import os
client = socket.socket()
client.connect(('127.0.0.1',8088))
while True:
    header_len = client.recv(4)
    dict_len = struct.unpack('i',header_len)[0]
    dict_data = client.recv(dict_len)  #
    real_dict = json.loads(dict_data)
    total_size = real_dict.get('file_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)
    print('文件已读取完毕')
    break
posted @ 2022-08-07 19:01  懒羊羊A  阅读(35)  评论(0)    收藏  举报