网络编程
网络目的 : 数据的传输
网络数据传输是一个复杂的过程
OSI 七层模型 --》 网络通信标准化流程
应用层 : 提供用户服务, 具体内容由特定程序规定
表示层 : 数据的压缩优化
会话层 : 建立应用连接, 选择传输层服务
传输层 : 提供不同的传输服务, 流量控制
网络层 : 路由选择, 网络互连
链路层 : 提供链路交换, 具体消息以帧发送
物理层 : 物理硬件, 接口, 网卡, 线路
osi 七层模型优点 : 将功能分开, 降低了网络传输中的耦合性, 每一部分完成自己的功能. 可以在开发和实施的过程中各司其职. 实现高内聚和低耦合的功能.
高内聚 : 单个模块功能尽量单一
低耦合 : 模块之间尽量减少关联和影响
四层
应用层 : 应用层 表示层 会话层
传输层 : 传输层
网络层 : 网络层
物理链路层: 链路层和物理层
五层 (tcp/ip 模型)
应用层 : 应用层 表示层 会话层
传输层 : 传输层
网络层 : 网络层
链路层 : 链路层
物理层 : 物理层
协议 (网络协议): 在网络通信中, 各方必须遵守的规定. 包括建立什么样的连接, 消息结构等
应用层 : TFTP HTTP DNS SMTP
传输层 : TCP UDP
网络层 : IP
物理层 : IEEE
网络基本概念
1, 主机: "localhost" 表示本台计算机
网络上 : 只在本地测试使用
'localhost' '127.0.0.1'
如果想在网络上进行测试 (自动使用本地可用网卡 IP)
'0.0.0.0' '''172.60.50.93'
查看本地 IP 网络信息
Linux 上: ifconfig
win 上查看本地 IP: ipconfig
ping www.baidu.com --->14.215.177.38(百度的 IP 地址)
获取计算机名称
- socket.gethostname()
- 'tedu'
获取主机 IP
- socket.gethostbyname('localhost')
- '127.0.0.1'
2,IP 地址
在网络上用于区分一台计算
IPv4 : 点分十进制 e.g. 192.168.1.72 0-255
32 位二进制表示
IPv6 : 128
网络连接测试命令: ping 172.60.50.92
特殊 IP
127.0.0.1 本地测试 IP
0.0.0.0 本地网卡通用 IP
192.168.1.0 表示当前网段
192.168.1.1 表示网关
192.168.1.255 广播地址
获取服务器主机信息
- socket.gethostbyaddr("www.baidu.com")
- ('127.0.0.1', [], ['119.75.213.61'])
主机 别名 IP 地址
将 ip 十进制转化为二进制
- socket.inet_aton("192.168.1.2")
- b'\xc0\xa8\x01\x02'
将 ip 二进制转化为十进制
- socket.inet_ntoa(b"\xc0\xa8\x01\x02")
- '192.168.1.2'
域名 : 网络服务器 IP 地址的名称
url : 在网络上定位某个资源位置
3, 端口号 : 端口号是网络地址的一部分, 在一个系统中每个网络应用监听不同的端口, 以获取对应端口传输的信息
数字范围 : 1--65535
1--255 : 一些众所周知的端口
256--1023 : 系统应用
1024---65535 : 自用
推荐用 >10000 8888 9999 7777 6666
测试一个软件端口号
- socket.getservbyname('mysql')
- 3306
- socket.getservbyname('http')
- 80
- socket.getservbyname('ssh')
- 22
传输层服务
面向连接的传输服务 ---》 tcp 协议
传输特征: 提供可靠的传输服务
可靠性表现: 数据在传输过程中, 无失序, 无差错, 无重复, 无丢失
* 传输过程中有建立和断开连接的过程
三次握手: 建立数据传输两端的持续连接
1. 客户端向服务器发起连接请求 (我可以牵你手吗)
2. 服务器收到连接请求进行确认, 返回报文 (可以)
3. 客户端收到服务器确认进行连接创建 (牵手成功)
四次挥手: 断开连接的两端, 保证数据的传输完整
1. 主动方发送报文, 告知被动方要断开连接 (我们分手吧, 你准备好)
2. 被动方返回报文, 告知收到请求, 准备断开 (知道了)
3. 被动方再次发送报文, 告知准备完毕可以断开 (你分手吧)
4. 主动方发送报文完成断开 (分手了)
适用情况: 文件的上传下载, 网络情况良好, 需要必须保证可靠性的情况
比如 : 信息聊天, 文件上传下载, 邮件, 网页获取
面向无连接的传输服务 ---》 udp 协议
传输特征 :
* 不保证传输的可靠性
* 无需建立三次握手和四次挥手的连接断开过程
* 消息的收发比较自由, 不受其他约束 (请原谅我这一生放荡不羁爱自由)
适用情况 : 网络情况较差, 对可靠性要求不高, 收发消息的两端不适合建立固定连接
比如 : 网络视频, 群聊, 发送广播
套接字 ----socket
socket 模块的套接字属性
(s 表示一个套接字对象)
s.type 获取套接字类型# SocketKind.SOCK_STREAM 流式套接字
s.family 获取地址族类型# AddressFamily.AF_INET 获取地址族类型
s.fileno() 获取套接字的文件描述符 (每一个 IO 操作系统都会为其分配一个不同的正整数, 该正整数即为此 IO 操作系统的文件描述符)
s.getsockname() 获取套接字绑定的地址 #('192.168.191.3', 8888)
s.getpeername() 获取连接套接字另一端的地址 #('192.168.191.3', 7826)
s.setsockopt(level,optname,value) 设置套接字选项, 丰富修改原有套接字功能
参数: level 设置选项的类型 optname 每个选项类型中的子选项 value 为选项设置值
s.getsockopt(level,optname) 获取套接字选项的值
- from socket import *
- s = socket()
- print(s.type) # SocketKind.SOCK_STREAM 流式套接字
- print(s.family) # AddressFamily.AF_INET 获取地址族类型
- print(s.fileno()) # 376
- s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 设置端口可重用
- print(s.getsockopt(SOL_SOCKET,SO_REUSEADDR))# 获取选项值 1
- s.bind(("192.168.191.3",8888))
- print(s.getsockname()) # 获取绑定的地址 ('192.168.191.3', 8888)
- s.listen()
- c,addr = s.accept() # addr 也是链接客户端的地址
- print(c.getpeername()) # ('192.168.191.3', 7826) 获取链接套接字客户端地址
- data = c.recv(1024)
- print(data) # b'i'
- c.close()
- s.close()
- View Code
socket 套接字编程
套接字: 通过编程语言提供的函数接口进行组合, 更简单的完成基于 tcp 和 udp 通信的网络编程
套接字的分类
流式套接字 (SOCK_STREAM): 传输层基于 tcp 的协议进行通信
数据报套接字 (SOCK_DGRAM): 传输层基于 udp 的协议进行通信
底层套接字 (SOCK_RAM): 访问底层协议的套接字
网络收发缓冲区
1, 协调读写速度, 减少和磁盘交互
2,recv 和 send 实际上是从缓冲区获取内容, 和向缓冲区发送内容
recv() 特性
1, 如果连接断开, recv 会立即结束阻塞返回空字符串
2, 当接收缓存区为空时会阻塞
3, 如果 recv 一次接收不完缓冲区内容, 下次会继续接收, 确保数据不丢失
send() 特性
1, 如果另一端不存在还试图使用 send 进行发送则会产生 BrokenPipError 异常
2, 当发送缓冲区满时会阻塞
本地套接字
作用: 用于本地不同程序间的进行数据传输
本地套接字的创建流程
1, 创建套接字对象
sockfd = socket(AF_UNIX,SOCK_STREAM)
2, 绑定本地套接字文件, 如果文件不存在, 则自动创建文件 (绑定套接字文件)
sockfd.bind(file)
判断一个文件夹下是否有某个文件 os.path.exists('./tcp_client.py')
删除一个文件 os.remove(file) os.remove(file)
3, 监听 listen
4, 接收发送消息 recv send
- from socket import *
- import os
- sock_file = './sock' # 使用哪个文件作为套接字文件
- if os.path.exists(sock_file):# 判断文件是否已经存在
- os.unlink(sock_file)
- sockfd = socket(AF_UNIX,SOCK_STREAM) # 创建本地套接字
- sockfd.bind(sock_file) # 绑定
- sockfd.listen(5) # 监听
- while True:
- c,addr = sockfd.accept() # 建立连接
- while True:
- data = c.recv(1024)
- if not data:
- break
- print(data.decode())
- c.close()
- sockfd.close()
服务端
- from socket import *
- sock_file = "./sock" # 确保通信两端用相同的套接字文件
- sockfd = socket(AF_UNIX,SOCK_STREAM) # 创建套接字
- sockfd.connect(sock_file) # 链接
- while True:
- msg = input("Msg>>")
- if msg:
- sockfd.send(msg.encode())
- else:
- break
- sockfd.close()
客户端
TCP 粘包
产生原因: TCP 传输采用字节流的方式, 消息之间没有边界, 如果发送的速度比接收速度快, 会造成多次发送的内容被一次接收, 形成意义上的误解即粘包
产生条件: 当使用 send 快速的连续发送极有可能产生粘包
影响: 如果每次发送的内容代表一个独立的意思, 需要单独识别, 就会产生粘包. 但是如果多次发送的内容就是一个连续的整体, 此时就不需要处理.
如何处理:
1, 每次发送后加一个结尾标志, 接收端通过标志进行判断
2, 发送一个数据结构
3, 每次发送中间有一个短暂的延迟 (有一个间隔)
TCP 接收多个客户端连接, 且可以持续发送消息
- from socket import *
- sockfd = socket(AF_INET,SOCK_STREAM) #创建套接字
- sockfd.bind(('127.0.0.1',9999)) #绑定地址
- sockfd.listen(5) #设置监听
- while True: # 等待客户端连接
- print("Waiting for connect...")
- connfd,addr = sockfd.accept()
- print("Connect from",addr)
- while True: # 消息收发
- data = connfd.recv(1024)
- if not data:
- break
- print("Receive:",data.decode())
- n = connfd.send(b"Receive your message")
- print("send %d bytes"%n)
- connfd.close() # 关闭套接字
- sockfd.close()
- TCP-server
- from socket import *
- sockfd = socket() #创建套接字
- sockfd.connect(('127.0.0.1',9999)) #发起连接
- while True: #消息收发
- msg = input("Msg>>")
- if not msg:
- break
- sockfd.sendall(msg.encode())
- data = sockfd.recv(1024)
- print(data.decode())
- sockfd.close()
- TCP-client
- HTTP
http 协议 --> 超文本传输协议 应用层协议, HTTP 是基于 TCP 协议编码的.
用途: 网页的获取, 基于网站的数据传输, 基于 http 协议的数据传输
特点
应用层协议. 传输层使用 TCP 传输
无状态协议, 不记录用户的通信内容
http1.1---->http2.0 成熟稳定
工作模式:
使用 http 双方均遵守 http 协议规定发送接收消息体
请求方, 根据协议组织请求内容给对象
服务方, 收到内容按照协议解析
服务方, 将回复内容按照协议组织发送给请求方
请求方, 收到回复根据协议解析
HTTP 请求格式
格式: 请求行 \ 请求头 \ 空行 \ 请求体
1, 请求行 (熟悉格式及作用): 提供具体的请求类别, 请求内容
GET / index.html / HTTP/1.1
请求类别 请求内容 协议版本
请求种类 : GET 获取网络资源
POST 提交一定的附加数据, 得到返回 结果
HEAD 获取响应头
PUT 更新服务器资源
DELETE 删除服务器资源
CONNECT 预留
TRACE 测试
OPTIONS 获取服务器性能
2, 请求头: 对请求内容的具体描述, 以键值对的形式对请求信息进行描述
e.g.
- Accept: text/HTML
- Accept-Encoding: gzip, deflate, br
- Accept-Language: zh-CN,zh;q=0.9
- Cache-Control: max-age=0
- Connection: keep-alive
3, 空行
4, 请求体 : 具体的请求参数
(GET 参数, 或者 POST 提交的内容)
HTTP 响应格式
1, 响应行 反馈具体的响应情况
HTTP/1.1 200 OK
版本信息 响应码 附加信息
响应码 :
1xx 提示信息 表示请求已经接受
2xx 响应成功
3xx 响应需要重新请定向
4xx 客户端错误
5xx 服务器错误
常见响应码 :
200 成功
404 请求页面不存在
401 没有访问权限
500 服务器发生未知错误
503 服务器暂时无法执行
2, 响应头 对响应信息的具体描述
e.g.
- Cache-Control: private
- Connection: Keep-Alive
3, 空行
4, 响应体: 将客户想要的内容进行返回
搭建 HTTP 本地服务器
做的是一个本地服务端, 接收来自浏览器客户端的请求
# 返回第一行 GET / HTTP/1.1
接收 http 请求; 解析 http 请求
响应一个网页给客户端
- from socket import *
- # 处理客户端请求
- def handle(connfd):
- request = connfd.recv(4096) # 接收请求
- # 防止客户端断开 request 为空
- if not request:
- return
- request_line = request.splitlines()[0] # 返回第一行 GET / HTTP/1.1
- info = request_line.decode().split(' ')[1]
- if info == '/':
- with open('index.html') as f:
- response = "HTTP/1.1 200 OK\r\n" # 响应行
- response += "Content-Type:text/html\r\n" # 响应头
- response += '\r\n' # 空行
- response += f.read() # 响应体
- else:
- response = "HTTP/1.1 404 Not Found\r\n"
- response += "Content-Type:text/html\r\n"
- response += '\r\n'
- response += "<h1>Sorry...</h1>"
- connfd.send(response.encode()) # 发送给浏览器
- sockfd = socket() # 搭建 tcp 网络
- sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
- sockfd.bind(('0.0.0.0',8000)) # 绑定地址
- sockfd.listen(3) # 设置监听
- while True:
- connfd,addr = sockfd.accept() # 获取连接端和地址
- handle(connfd) # 处理客户端请求
在浏览器输入地址: 127.0.0.1:8888, 即可得到网页显示!
来源: https://www.cnblogs.com/LXP-Never/p/9432210.html