网络编程

网络编程

1.两种软件开发架构

在编写项目前需遵循代码层面上的规范(代码运行流程、环节、步骤..)

1)C/S架构

​ Client 客户端
​ Server 服务端

计算机上下载的各个互联网公司app 其实就是'客户端软件'
通过客户端软件就可以体验到互联网公司服务端中提供的服务

客户端 看作 去消费的客人
服务端 看作 提供服务的店

'''
服务端需具备的条件:
  1.24小时不间断提供服务
  2.固定的地址(不能经常改变)
  3.能够同一时间服务多个客户(高并发)
'''

2)B/S架构

​ Browser 浏览器
​ Server 服务器

'B/S 本质还是 C/S架构'
用浏览器充当服务端的客户端 用户想体验服务不用下载app

3)两种架构的优劣势

1)C/S架构
      优势:不同公司的客户端可由不同公司独立开发,可高度定制化客户端功能
      劣势:需要下载才能使用,较繁琐
2)B/S架构
      优势:不需要下载,直接就可使用
      劣势:无法高度定制化,需遵守很多规则

4)架构发展趋势

今后发展趋势:统一接口
微信小程序
支付宝小程序

让CS和BS交错使用统一接口,避免了各自的劣势

2.网络编程前戏

1)什么是网络编程

基于网络编写代码 程序可以实现远程数据交互

2)网络编程的目的

本质就是为了解决计算机之间远程数据交互

3)网络编程的起源

最早起源于美国军事领域,由于想实现计算机之间的数据交互只能用硬盘坐飞机互相交换。后来发明了网络编程

4)网络编程的必备条件

1.早期的电话  需要  '电话线'
2.大屁股电脑  需要  '网线'
3.笔记本电脑  需要  '网卡'

所以实现数据的远程交互必备的条件是物理连接介质

3.网络相关专业名词

1)交换机

能够让接入交换机的多台计算机彼此互联起来

2)广播

首次查找接入同一个交换机的其他计算机 需朝交换机中“吼一嗓子”,所有计算机都能收到该“声音”(向所有设备发消息)

3)单播

首次被查找的计算机回应查找它的计算机 并附带自己的mac地址(点对点通信)

4)广播风暴

接入同一台交换机的多台计算机同时发广播

5)局域网

单个交换机组成的网络。局域网内可以用mac地址通信

6)广域网

范围更大的局域网

7)互联网

由所有的局域网、广域网连接到一起形成的网络

8)路由器

将多个局域网连接到一起的设备

4.OSI七层协议

规定了所有计算机在远程数据交互时必须要经过的处理流程、制造过程中必须拥有相同的功能硬件

应用层
表示层
会话层
传输层
网络层
数据链路层
物理连接层
'应、表、会、传、网、数、物'

# 可合并成五层
应用层(应用层、表示层、会话层)
传输层
网络层
数据链路层
物理连接层

# 可合并成四层
应用层(应用层、表示层、会话层)
传输层
网络层
网络接口层(数据链路层、物理连接层)

"""
接收网络消息  数据从下往上传递
发送网络消息  数据从上往下传递
"""

image

1)物理连接层

主要用于确保计算机之间的物理连接介质 接收数据(bytes类型、二进制)

2)数据链路层

规定了电信号的分组方式

以太网协议

​ 规定计算机在出厂时必须有一块网卡 网卡上有一串数字(mac以太网地址)
​ 该数字相当于计算机的身份证号,独一无二
​ 该数字是12位16进制数组成(前6位是厂商编号 后6位是生产流水号)

3)网络层

IP协议

​ 规定了任何接入互联网的计算机都必须有一个IP地址(身份证)
​ mac地址是物理地址,可以看成是永远无法修改的
​ IP地址是动态分配的 不同场所的IP是不同的

👻IP地址
IPV4:点分十进制
最小:0.0.0.0
最大:255.255.255.255
随着社会发展,同时上网的人越来越多,IPV4无法满足人们日常生活
IPV6:点分十六进制
号称可以给地球每一粒沙分一个IP地址

IP地址可以跨局域网传输,用来标识全世界独一无二的一台计算机

👻IP特征
每个IP自带定位。只有用IP代理才可以隐藏ip。但是IP代理也可能会被查到,所以可以用多层代理

4)传输层

(1)PORT端口协议

规定了端口层面所遵循的规则

​ 规定了一台计算机上每个正在运行的应用程序都必须有一个端口号
​ 端口号相当于是计算机用来管理多个应用程序的标记

👻端口号特征
范围:0~65535
特征:动态分配
建议:0~1024 一般是操作系统内部需要用的
1024~8000 一般是常见软件的端口号
8000之后的 我们平时写代码可以用8000之后的端口号

"""
【URL】:统一资源定位符(网址)
【URL本质】:就是由IP和PORT组成的

【IP:PORT】:能够定位全世界独一无二的一台计算机上面的某一个应用程序
【域名解析】:将网址解析成IP:PORT
"""
# 但是一般对于网址我们不用IP:PORT 而是用更为好记的域名(网址)如:www.baidu.com
(2)TCP与UDP协议

规定了数据传输层面所遵循的规则
数据传输可以遵循的协议有很多 最常见的是TCP和UDP

1.TCP协议

可靠协议、流式协议

1)三次握手 建立双向通道
    
2)四次挥手 断开双向通道

image

👻syn洪水攻击
当服务端同一时间收到无数个客户端请求会导致服务端处于SYN_RCVD状态

👻服务端如何区分不同客户端的请求
对每个客户端请求做唯一标识

👻TCP传输数据非常安全的原因是因为有双向通道吗?
错,是因为有二次确认反馈机制。给对方发消息后会保留一个副本,直到对方回应收到了消息才会删除。否则会在固定时间内隔一段时间反复发送

2.UDP协议

不可靠协议、数据报协议

# 基于UDP发送的数据没有任何通道限制

使用UDP是因为简单快捷,只需要指定对方的地址就可以发消息
3.TCP与UDP的区别

​ TCP可以看成是打电话:你一句我一句
​ UDP可以看成是漂流瓶:只要发送了,不管有没有收到有没有回复,反正我发了

5)应用层

​ 应用层相当于是程序员自己写的应用程序 里面的协议有很多
​ 常见的有:HTTP、HTTPS、FTP..

5.socket套接字模块

如果需要编写基于网络进行数据交互的程序,就意味着我们需要自己通过代码来操作OSI七层,很繁琐。所以有了socket模块,类似于操作系统,会把复杂繁琐的接口提供成简单快捷的接口。

# 没有socket模块就相当于电脑没有操作系统

1.基于文件类型的套接字家族(本地) #【不用】
    AF_UNIX
2.基于网络类型的套接字家族(联网) #【常用】
    AF_INET

image

1)socket实现本地客户端与服务端交互 【初步基础代码】

#【服务端】:

import socket

# 1.产生一个socket对象
server = socket.socket()  # 括号内不写参数默认是TCP协议
# 2.绑定固定的地址(ip和端口号)
server.bind(('127.0.0.1', 8080))  # 本地回环地址(只允许自己的计算机访问)
# 3.半连接池(类似餐厅外面等待的座位,预防洪水攻击)
server.listen(5)
# 4.等待客户连接
sock, addr = server.accept()  # sock就是双向通道 addr就是客户端ip地址和端口号
# 5.接收客户端发送过来的1024字节
data = sock.recv(1024)
print(f'来自客户端{addr}的消息:', data.decode('utf8'))
# 6.给客户端发送消息(bytes)
sock.send('我在'.encode('utf8'))
# 7.关闭双向通道
sock.close()
# 8.关闭服务端
server.close()
——————————————————————————————————————————
【客户端】

import socket

# 1.产生一个socket对象
client = socket.socket()  # 括号内不写参数默认是TCP协议
# 2.绑定服务端的ip和端口号
client.connect(('127.0.0.1', 8080))
# 3.给服务端发送消息(bytes)
client.send('服务端在吗'.encode('utf8'))
# 4.接收服务端发来的1024字节
data = client.recv(1024)
print('来自服务端的消息:', data.decode('utf8'))
# 5.断开与服务端的链接
client.close()

2)socket实现本地客户端与服务端交互 【优化版代码】

1.聊天内容自定义
    采用input获取用户输入
2.聊天循环起来
    把聊天部分用循环包起来
3.用户发送的消息不能为空
    服务端与客户端必须一个收recv 一个发send
    #解决:客户端在发消息前判断是否为空 服务端提示管理员不要发空消息
4.服务端多次重启可能会报错(mac最常见)
    #报错:Address already in use  端口号被占用
    #解决:
    #   1.改端口号
    #   2.加代码
    """
    from socket import SOL_SOCKET,SO_REUSEADDR  # 把这条加在最顶头
    server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 把这条代码在bind上一行加上即可
    """
5.当客户端异常断开后 让服务端继续服务其他客人
   windows服务端会直接报错
   mac服务端会一段时间反复接收空消息延迟报错
   #解决:
   #   异常处理、空消息判断
#【服务端】:

import socket
from socket import SOL_SOCKET, SO_REUSEADDR

# 产生一个socket对象
server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 绑定固定的ip和端口号
server.bind(('127.0.0.1', 8080))
# 半连接池
server.listen(5)
while True:
    # 等待客户连接
    sock, addr = server.accept()
    while True:
        try:
            # 接收客户发来的1024字节
            data = sock.recv(1024)
            # 判断如果为空则结束
            if len(data)==0:
                break
            print(f'来自客户端{addr}的消息:', data.decode('utf8'))
            # 给客户端发送消息(bytes)
            msg = input('输入要发送给客户端的消息(不要为空):').strip()
            sock.send(msg.encode('utf8'))
        except BaseException:
            break
——————————————————————————————————————————
#【客户端】:

import socket

# 产生一个socket对象
client = socket.socket()
# 绑定服务端的ip和端口号
client.connect(('127.0.0.1', 8080))
while True:
    # 给服务端发送消息(bytes)
    msg = input('输入要发送给服务端的消息:').strip()
    # 判断如果消息为空则重新输入
    if len(msg) == 0:
        print('不能发送空消息')
        continue
    client.send(msg.encode('utf8'))
    # 接收服务端发来的1024字节
    data = client.recv(1024)
    print('来自服务端的消息:', data.decode('utf8'))

6.半连接池概念

server.listen(5)
	主要是为了做缓冲 避免太多无效等待
    6个(接待1个 等待5个)客户端可以同时发送消息,但是只有1对1断掉后服务端才能收到客户端2的消息
    #类似于饭店外放的等待座位,允许等待5桌,多余的报错不允许访问。

7.黏包现象

1)什么是黏包现象

服务端连续执行三次recv 客户端连续执行三次send
服务端应该收到三条消息 但是收到的却是三条合在一起的消息,这种现象称为黏包现象

解决黏包现象的关键点:如何明确即将接收的数据具体多大

image

2)stuct模块打包举例

打包的是数字!!!

import struct

# 打包
info = b'abcde'
# print(len(info))  # 5(真实字典的长度)
# 把数据打包成固定4长度 i式固定的打包模式
res = struct.pack('i', len(info))
print(len(res))  # 4

# 拆包
res1 = struct.unpack('i', res)
# print(res1)  # (5,)
print(res1[0])  # 5(此时res1是一个元组索引0就是真实长度)

3)解决黏包问题方法

用struct模块 不论数据长度是多少都可以打包成固定长度,且可以基于该长度反向拆包出真实长度

#解决问题初次方案:
    客户端:
        1.将真实数据转成'bytes'类型
        2.利用struct模块把真实的长度制作成固定长度的包
        3.将固定长度的报头发送给服务端 服务端只需要在recv()括号内填写固定长度的报头数字即可
        4.然后再发送真实数据
    服务端:
        1.先接收固定长度的报头
        2.利用struct模块反向解析出真实数据长度
        3.recv接收真实数据长度即可
"""
   问题1:struct模块无法打包数据量较大的数据(更换模式也不行)
   问题2:报头能否传递更多信息(大小、简介等..)
"""
"""
   解决:用字典作为报头,用json模块把字典转为bytes类型
"""
d1 = {
    'file_name': 'xx视频文件.avi',
    'file_size': 1231532155,
    'file_info': '超级好看',
}
import struct
import json

data = json.dumps(d1)
# print(len(data.encode('utf8')))  # 113(真实字典的长度)
res = struct.pack('i', len(data.encode('utf8')))
print(len(res))  # 4
#解决问题终极方案: 
    客户端:
        1.构造真实数据的信息字典(数据长度、名称、简介)
        2.利用struct模块把字典打包处理得到固定长度4
        3.将固定长度的报头发送给服务端(拆包出来的是字典长度)
        4.发送字典数据,取出真实数据长度
        5.再发送真实是数据
    服务端:
        1.先接收固定长度的报头
        2.解析出字典的真实长度并接收字典数据
        3.通过字典获取到真实数据的长度
        4.recv接收真实数据长度

4)代码实操

#【服务端】:
import json
import socket
import struct

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
sock, addr = server.accept()
# 1.接收字典报头
d1_head = sock.recv(4)
# 2.解析报头得出字典数据长度
d1_len = struct.unpack('i', d1_head)[0]
# 3.接收字典数据
d1_bytes = sock.recv(d1_len)
d1 = json.loads(d1_bytes)
# 4.获取真实数据长度
file_size = d1.get('file_size')
with open(d1.get('file_name'), 'wb')as f:
    f.write(sock.recv(file_size))
    
#【客户端】:
import socket
import os
import json
import struct

client = socket.socket()
client.connect(('127.0.0.1', 8080))
# 1.获取真实数据大小
file_size = os.path.getsize(r'D:\pythonProject1\day36\陈老师视频合集.txt')
# 2.制作真实数据的字典数据
d1 = {
    'file_name': '视频合集.txt',
    'file_size': file_size,
    'file_info': '陈老师的所有视频合集'
}
# 3.制作字典报头
d1_bytes = json.dumps(d1).encode('utf8')
dict_head = struct.pack('i', len(d1_bytes))
# 4.发送字典报头
client.send(dict_head)
# 5.发送字典
client.send(d1_bytes)
# 6.发送真实数据
with open(r'D:\pythonProject1\day36\陈老师视频合集.txt', 'rb')as f:
    for i in f:
        client.send(i)

8.UDP协议

UDP不会出现多个消息发送合并

#【服务端】:
import socket

server = socket.socket(type=socket.SOCK_DGRAM)  # UDP协议
server.bind(('127.0.0.1', 8080))

while True:
    # 接收客户端的消息
    data, addr = server.recvfrom(1024)
    print(f'客户端:{addr}发来的消息:', data.decode('utf8'))
    # 发送给客户端的消息
    msg=input('输入要发送给客户端的消息:').strip()
    server.sendto(msg.encode('utf8'), addr)
————————————————————————————————————————————————
#【客户端】:
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8080)
while True:
    msg=input('输入要发送给服务端的消息:').strip()
    client.sendto(msg.encode('utf8'), server_addr)
    data, addr = client.recvfrom(1024)
    print(f'来自服务端{addr}发来的消息:', data.decode('utf8'))
posted @ 2022-11-15 16:39  oreox  阅读(340)  评论(0编辑  收藏  举报