编写项目之前需要遵循的代码层面上的规范(代码运行的流程、环节、步骤)
客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的
c/s架构 | 描述 | 例子 |
---|---|---|
c(client) | 客户端 | 即将去消费的客人 |
s(server) | 服务端 | 提供服务的商店 |
客户端:泛指客户端应用程序(app软件),程序需要先安装才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大
服务端具备的特征:
浏览器端与服务器端架构,这种架构是从用户层面来划分,通过浏览器充当各个服务端的客户端,用于想要体验服务不需要下载指定的客户端
本质:c/s架构
b/s架构 | 描述 |
---|---|
b(broswer) | 浏览器 |
s(server) | 服务器 |
c/s架构 | b/s架构 | |
---|---|---|
优势 | 下载对应的客户端可以在客户端软件内高度定制相关服务 | 不需要下载客户端,可以快速体验服务 |
优势 | 使用前必须先下载客户端,比较繁琐 | 定制花里胡哨的功能较为繁琐 |
基于互联网编写代码,程序可以实现远程数据交互
网络编程的本质是为了解决计算机之间的远程交互
学习完网络编程之后,可以编写一个c/s架构的软件
任何先进的技术一般都源于军事,网络编程由美国军方开发
没有网络编程的时候,如果两台计算机之间要交互数据只能用硬盘拷贝
计算机之间要想实现远程数据交互,首要条件就是要有物理连接介质
例:
物品 | 物理连接介质 |
---|---|
早期电话 | 电话线 |
大屁股电脑 | 网线 |
笔记本电脑 | 网卡 |
规定了计算机涉及到数据远程交互时必须要经过的部件或流程(每一层都有各自的功能和规范),所有的计算机在涉及到网络传输必须要有相同的零部件
主要取决于程序员自己采用什么策略和协议
常见的协议:HTTP、HTTPS、FTP…
端口协议:规定了一台计算机上的每一个正在运行的应用程序都必须有一个端口号,端口号相当于是计算机用来管理多个应用程序的标记
端口号特征:
端口号范围:0-65535
端口号范围 | 使用 |
---|---|
0-1024 | 一般是操作系统内部需要使用 |
1024-8000 | 一般是常见的软件已经使用了 |
8000+ | 平时写代码使用8000之后的端口号 |
端口号动态分配
同一时间同一台计算机端口号不能冲突
名字 | 描述 |
---|---|
IP | 用于标识全世界任意一台接入互联网的计算机 |
PORT | 用于标识一台计算机上的某个应用程序 |
IP+PORT | 用于标识全世界任意一台接入互联网的计算机上的某一个具体的程序 |
规定了数据传输所遵循的规则
协议 | 描述 |
---|---|
TCP | 类似于打电话:你一句我一句,有来有往 |
UDP | 似于发短信:只要发送了就什么都不管了 |
三次握手:建立双向通道,同时让大量的客户端朝服务端发送建立TCP连接的请求
四次挥手:断开双向通道,中间两步不能合并(需要有检查的时间)
优势:基于UDP协议发送数据,没有任何的通道也没有任何限制
劣势:UDP发送数据没有TCP安全,因为没有二次确认机制
规定了任何接入互联网的计算机都必须有一个IP地址
每个IP都自带定位(IP代理)
eg:可以根据mac地址查找计算机(基于mac地址实现)
保证物理连接介质的条件,传递电信号(主要研究插网线情况)
名词 | 描述 |
---|---|
交换机 | 能够让接入交换机的多台计算机实现彼此互联 |
以太网通信(mac通信) | 有了交换机之后,根据电脑的mac地址就可以实现数据交互 |
局域网 | 有某个固定区域组成的网络 |
广域网 | 可以看成是更大区域的局域网 |
路由器 | 将多个局域网连接到一起的设备 |
网址(URL) | 统一资源定位符,本质:IP+PORT(ip:port) |
套接字家族名字: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.数据交互 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()
客户端与服务端不能同时执行同一个,不能同时收或发
input获取用户数据(主要是编码解吗)
给数据交互环节添加循环
需求:不会因为客户端断开连接而报错
方法:异常捕获
判断是否为空,是则重新输入(主要针对客户端)
from socket import SOL_SOCKET, SO_REUSEADDR server.aetsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 加在bind前面
针对接收的消息加判断处理即可
主要为了做缓冲,避免太多的无效等待
sever.listen(5)
# 服务端 import socket # 1.创建一个socket对象 server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字 # 2.绑定一个固定的地址(ip\port) server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问) server.listen(5) sock, address = server.accept() print(sock.recv(5)) print(sock.recv(5)) print(sock.recv(5)) # 客户端 import socket # 1.产生一个socket对象 client = socket.socket() # 2.连接服务端(拼接服务端的ip和port) client.connect(('127.0.0.1', 8080)) client.send(b'jason') client.send(b'kevin') client.send(b'jerry')
流式协议:所有的数据类似于水流连接在一起
不知道即将要接收的数据量多大,如果知道的话不会产生黏包
struct模块无论数据长度是多少,都可以打包成固定长度,然后基于该固定长度可以反向解析出真实长度
注意:struct模块针对数据量特别大的数字没有办法打包
import struct info = 'something to send' print(len(info)) # 17 数据原本的长度 res = struct.pack('i', len(info)) # 将数据原本的长度打包 print(len(res)) # 打包之后的长度是4 ret = struct.unpack('i', res) # 将打包之后固定长度为4的数据拆包 print(ret[0]) # 13 又得到了原本数据的长度
思路:
发送 | 接收 |
---|---|
1.先将真实数据的长度制作成固定长度 4 2.先发送固定长度的报头 3.再发送真实数据 |
1.先接收固定长度的报头 4 2.再根据报头解压出真实长度 3.根据真实长度接收 |
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
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