网络编程学习笔记

昨日内容回顾

  • 软件开发架构
1.cs架构
2.bs架构
发展趋势:统一接口
  • 网络编程简介
1.远程数据交互
2.物理连接介质是远程数据交互必备的条件
  • OSI七层协议
1.七层:应表会传网数物
    应用层
    表示层
    会话层
    传输层
    网络层
    数据链路层
    物理连接层
2.以太网协议
    mac地址
3.IP协议
    ip地址
        公网IP:基于互联网就可以直接访问
            需要花钱购买(实名注册)
        私网IP:不能直接被互联网访问
        	正常情况下我们都是私网IP
4.PORT协议
    端口号
  • 网络相关名词
1.局域网
2.交换机
3.路由器
4.广播与单播
5.广播风暴
6.网址(URL)
	域名(www.baidu.com)		ip:port

今日内容概要

  • TCP与UDP协议
  • socket套接字编程
  • 代码优化
  • 半连接池
  • TCP黏包问题及解决思路

今日内容详细

作业讲解

域名解析
域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
DNS服务器
进行域名(domain name)和与之相对应的IP地址 (IP address)转换的服务器。

TCP与UDP协议

TCP和UDP都属于传输层,规定了数据传输所遵循的规则

ps:数据传输能够遵循的协议有很多 TCP和UDP是较为常见的两个

TCP协议

TCP类似于打电话:你一句我一句 有来有往
三次握手
    建立双向通道
    ps:洪水攻击
      同时让大量的客户端朝服务端发送建立TCP连接的请求
四次挥手
    断开双向通道
    中间的两步不能合并(需要有检查的时间)	
"""
基于TCP传输数据非常的安全 因为有双向通道(描述不够准确)
  基于TCP传输数据,数据不容易丢失!!! 不容易丢失的原因在于二次确认机制
	基于TCP发送的消息会在本地先保存该消息,确认对方收到然后接收反馈信息才会删除,否则在一定的时间内会频繁的多次发送直到对方确认或者超时为止
"""

image

image

UDP协议

UDP类似于发短信:只要发送了 不管别人看没看到 也不管回不回复
  是不可靠的、不需要建立双下通道,数据传输效率高,但是可能会丢失
    基于UDP协议发送数据 没有任何的通道也没有任何的限制
	  UDP发送数据没有TCP安全(没有二次确认机制)

应用层

主要取决于程序员自己采用什么策略和协议
常见协议有:HTTP HTTPS FTP...

socket套接字编程

socket套接字简介

编写一个cs架构的程序,实现数据交互,需要编写代码操作OSI七层,相当的复杂。由于操作OSI七层是所有cs架构的程序都需要经历的过程,所以有固定的模块。socket套接字是一门技术,socket模块提供了快捷方式,不需要自己处理每一层。

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

socket模块应用

服务端

"""运行程序的时候  肯定是先确保服务端运行 之后才是客户端"""
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
"""
recv和send接收和发送的都是bytes类型的数据
"""
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.循环通信
    给数据交互环节添加循环即可
    # 服务端
    while True:
    data = sock.recv(1024)  # 听别人说话
    print(data.decode('utf8'))
    msg = input('请回复消息>>>:').strip()
    sock.send(msg.encode('utf8'))  # 回复别人说的话

    # 客户端
    while True:
    msg = input('请输入你需要发送的消息>>>:').strip()
    client.send(msg.encode('utf8'))  # 给服务端发送消息
    data = client.recv(1024)  # 接收服务端回复的消息
    print(data.decode('utf8'))
4.服务端能够持续提供服务
    不会因为客户端断开连接而报错
      异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
    while True:
        try:
            data = sock.recv(1024)  # 听别人说话
            if len(data) == 0:
                break
            print(data.decode('utf8'))
            msg = input('请回复消息>>>:').strip()
            if len(msg) == 0:
                msg = '太忙 暂无消息'
            sock.send(msg.encode('utf8'))  # 回复别人说的话
        except Exception:
            break
'''
 客户端如果异常断开 服务端代码应该重新回到accept等待新的客人
 '''
5.消息不能为空
    判断是否为空 如果是则重新输入(主要针对客户端) 
# 客户端
while True:
    msg = input('请输入你需要发送的消息>>>:').strip()
    # 用户如果什么都不输 直接回车 那么不该往下走
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf8'))  # 给服务端发送消息
# 服务端
msg = input('请回复消息>>>:').strip()
if len(msg) == 0:
    msg = '太忙 暂无消息'
6.服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
	from socket import SOL_SOCKET,SO_REUSEADDR
	server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
7.客户端异常退出会发送空消息(针对mac linux)
	针对接收的消息加判断处理即可

半连接池

在py文件默认同一时间只能运行一次,如果想单独分开运行多次 就需要用半连接池。

作用:设置的最大等待人数可以节省资源,提高效率。

server.listen(5)

image

TCP黏包问题

服务端代码
print(sock.recv(1024))
print(sock.recv(1024))
print(sock.recv(1024))

客户端代码
client.send(b'jason')
client.send(b'kevin')
client.send(b'tony')

"""
三次打印的结果
  b'hellojasonkevin'
  b''
  b''
"""
# 问题:传入的3次数据被合成一个

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

解决黏包问题思路

可以利用struct模块精准的获取数据的大小

import struct

info = '下午上课 以后可能是常态!'
print(len(info))  # 13  数据原本的长度
# pack可以将任意长度的数字打包成固定长度
res = struct.pack('i', len(info))  # 将数据原本的长度打包 第一个参数是格式 写i就可以了
print(len(res))  # 4  打包之后的长度是4
# unpack可以将固定长度的数字解包成打包之前数据真实的长度
ret = struct.unpack('i', res)  # 将打包之后固定长度为4的数据拆包
print(ret[0])  # 13  又得到了原本数据的长度

info1 = '打起精神啊 下午也需要奋斗 也需要认真听 客服困难 你困我也困!!!'
print(len(info1))  # 34
res = struct.pack('i', len(info1))  # 将数据原本的长度打包
print(len(res))  # 4  打包之后的长度是4
ret = struct.unpack('i', res)
print(ret[0])  # 34

"""
struct模块无论数据长度是多少 都可以帮你打包成固定长度
然后基于该固定长度 还可以反向解析出真实长度

    思路
        1.先将真实数据的长度制作成固定长度 4
        2.先发送固定长度的报头
        3.再发送真实数据

        1.先接收固定长度的报头  4
        2.再根据报头解压出真实长度 
        3.根据真实长度接收即可
"""
struct模块针对数据量特别大的数字没有办法打包!!!

终极解决方案:利用字典存储数据,不但有数据的长度,还可以携带额外的数据。

服务端

1.先构造一个数据的详细字典  
2.对字典数据进行打包处理  得到一个固定长度的数据  4
3.将上述打包之后的数据发送给客户端
4.将字典数据发送给客户端
5.将真实数据发送给客户端

import socket
import os
import struct
import json

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    sock, address = server.accept()
    while True:
        # 1.先构造数据文件的字典
        file_dict = {
            'file_name': 'Jason合集.txt',
            'file_size': os.path.getsize(r'../XXX视频合集.txt'),
            'file_desc': '内容很精彩 一定不要错过',
            'file_root': 'jason'
        }
        # 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.发送真实字典数据
        sock.send(dict_json.encode('utf8'))
        # 5.发送真实数据
        with open(r'../XXX视频合集.txt', 'rb') as f:
            for line in f:
                sock.send(line)
        break

客户端

1.先接收固定长度的数据  4
2.根据固定长度解析出即将要接收的字典真实长度
3.接收字典数据
4.根据字典数据 获取出真实数据的长度
5.接收真实数据长度

import socket
import struct
import json

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

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)  # b'{"file_name":123123123}'
    # 4.解码并反序列化出字典
    real_dict = json.loads(dict_data)
    print(real_dict)
    # 5.从数据字典中获取真实数据的各项信息
    total_size = real_dict.get('file_size')  # 32423423423
    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 @   空白o  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示