08-27-周三_17-09-29

This commit is contained in:
2025-08-27 17:10:05 +08:00
commit 86df397d8f
12735 changed files with 1145479 additions and 0 deletions

View File

@@ -0,0 +1,612 @@
# 核心概念
- Socket套接字网络通信的端点支持不同主机间的进程通信。
- 协议类型:
- TCP
- UDP
- 地址族:...
# TCP 协议工作流程
![img-socket编程1](套接字的工作流程/socket编程1.png)
## 服务端
**实现步骤:**
1. 创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_STREAM)`
2. 绑定地址与端口​​:`bind(('0.0.0.0', 12345))`0.0.0.0 表示监听所有 IP
3. ​​监听连接​​:`listen(5)` (参数为最大等待连接)
4. 接收连接​​:`accept()` 返回客户端 Socket 和地址
5. 数据交换​​:`send()``recv()` 方法收发数据
6. 关闭连接​​:`close()` 释放资源
## 客户端
**实现步骤:**
1. ​创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_STREAM)`
2. ​连接服务器​​:`connect(('127.0.0.1', 12345))`
3. 数据交换​​:同服务器端 `send()``recv()`
## 示例代码
```python
# server.py 代码
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
conn, client_addr = server.accept()
print(conn, client_addr, sep="\n")
from_client_data = conn.recv(1024)
print(from_client_data.decode("utf-8"))
conn.send(from_client_data.upper())
conn.close()
server.close()
# client.py 代码
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))
client.send("hello".encode("utf-8"))
from_server_data = client.recv(1024)
print(from_server_data)
client.close()
```
# UDP 协议工作流程
<img src="套接字的工作流程/socket编程2.png" alt="img-socket编程2" style="zoom:80%;" />
## 服务端
**实现步骤:**
1. 创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`
2. 绑定地址与端口​​:`bind(('0.0.0.0', 12345))`
3. 数据交换​​:`sendto()``recvfrom()` 方法收发数据
## 客户端
**实现步骤:**
1. 创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`
2. 数据交换​​:同服务器端 `sendto()``recvfrom()`
## 示例代码
```python
# server.py
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(("127.0.0.1", 8000))
from_client_data, addr = server.recvfrom(1024)
print(from_client_data, addr, sep="\n")
server.sendto(from_client_data.upper(), addr)
server.close()
# client.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto("hello".encode("utf-8"), ("127.0.0.1", 8000))
from_server_data, addr = client.recvfrom(1024)
print(from_server_data, addr, sep="\n")
client.close()
```
# 相关方法
服务端套接字函数
| 函数 | 含义 |
| :---------- | :-------------------------------------------- |
| `s.bind()` | 绑定(主机,端口号)到套接字 |
| `s.listen()` | 开始 TCP 监听 |
| `s.accept()` | 被动接受 TCP 客户的连接,(阻塞式)等待连接的到来 |
客户端套接字函数
| 函数 | 含义 |
| :-------------- | :------------------------------------------------------- |
| `s.connect()` | 主动初始化 TCP 服务器连接 |
| `s.connect_ex()` | `connect()`函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数
| 函数 | 含义 |
| :--------------- | :------------------------------------------------------------ |
| `s.recv()` | 接收TCP数据 |
| `s.send()` | 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完) |
| `s.sendall()` | 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完) |
| `s.recvfrom()` | 接收UDP数据 |
| `s.sendto()` | 发送UDP数据 |
| `s.getpeername()` | 连接到当前套接字的远端的地址 |
| `s.getsockname()` | 当前套接字的地址 |
| `s.getsockopt()` | 返回指定套接字的参数 |
| `s.setsockopt()` | 设置指定套接字的参数 |
| `s.close()` | 关闭套接字 |
面向锁的套接字方法
| 函数 | 含义 |
| :--------------- | :---------------------------- |
| `s.setblocking()` | 设置套接字的阻塞与非阻塞模式 |
| `s.settimeout()` | 设置阻塞套接字操作的超时时间 |
| `s.gettimeout()` | 得到阻塞套接字操作的超时时间 |
面向文件的套接字的函数
| 函数 | 含义 |
| :------------ | :---------------------------- |
| `s.fileno()` | 套接字的文件描述符 |
| `s.makefile()` | 创建一个与该套接字相关的文件 |
# 粘包问题
## 原因
**TCP 协议特性**
- 流式传输TCP 将数据视为连续字节流,不会自动分割数据包,导致接收端无法区分消息边界
- ​缓冲区机制​​:发送端和接收端均存在缓冲区,发送端可能将多个小数据包合并发送,接收端可能将多次接收的数据合并处理
- Nagle算法优化TCP 为提高传输效率,会合并多个小数据包(例如间隔短且数据量小)后发送
**​​代码逻辑缺陷**
- 连续发送​​:在未明确分隔消息的情况下连续调用 `send()`,导致数据在缓冲区中合并
- 接收不完整​​:接收端 `recv()` 方法未动态调整缓冲区大小,导致部分数据残留
## 示例代码
### Demo1
通过 **​​连续发送小数据包****接收缓冲区合并** 的场景模拟粘包现象:
```python
# 服务端代码(连续发送两次数据)​
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
print(f"客户端 {addr} 已连接")
try:
# 连续发送两次数据(未处理粘包)
conn.send(b'Hello') # 第一次发送
conn.send(b'World') # 第二次发送(可能合并到缓冲区)
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码(一次性接收数据)​
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 一次性接收数据(可能合并两次发送的内容)
data = client.recv(1024)
print(f"接收到的数据: {data.decode()}") # 预期输出可能是 'HelloWorld'
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
**现象解释**
1. Nagle 算法​:发送端连续调用 `send()` 发送小数据包,可能将两次发送合并为一个包。
2. 缓冲区机制:接收端 `recv(1024)` 的缓冲区较大,一次性读取了所有待接收数据
### Demo2
通过 **​大缓冲区发送 + 小缓冲区接收** 的场景模拟接收不完整问题:
```python
# 服务端代码(发送 10KB 大数据包)​
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
try:
# 发送 10KB 数据(超过常规接收缓冲区)
big_data = b'A' * 1024 * 10 # 10KB
conn.sendall(big_data)
print(f"已发送 {len(big_data)} 字节数据")
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码(仅接收 4KB 数据)
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 设置接收缓冲区为 4KB且仅接收一次
partial_data = client.recv(4096) # 只接收一次
print(f"实际接收长度: {len(partial_data)} 字节") # 输出 4096残留 6KB 未接收
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
**现象解释**
1. TCP 流式特性TCP 将数据视为连续字节流,无边界标识
2. ​​缓冲区限制​​:`recv()` 方法一次性读取的字节数受参数限制,未循环接收导致残留
## 解决方案
固定包头法:发送端在数据前附加一个固定长度的包头,标明数据长度,接收端根据包头解析完整数据。
### struct 模块
把一个类型,如数字,转成固定长度的 bytes
![img-struct模块](套接字的工作流程/struct模块.png)
**常见用法**
```python
import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 18334)
print(ret, len(ret))
# 通过unpack反解回来 返回一个元组回来
ret1 = struct.unpack('i',ret)[0]
print(ret1)
```
### Demo1
```python
# 服务端代码(连续发送两次数据)​
import socket
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
print(f"客户端 {addr} 已连接")
try:
data = b'Hello'
header = struct.pack("!I", len(data))
conn.send(header + data) # 第一次发送
data = b'World'
header = struct.pack("!I", len(data))
conn.send(header + data) # 第二次发送
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 接收固定长度包头
header = client.recv(4)
data_size = struct.unpack("!I", header)[0]
received_data = client.recv(data_size)
print(f"实际接收长度: {len(received_data)} 字节")
print(received_data)
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
### Demo2
```python
# 服务端代码(发送 10KB 大数据包)​
import socket
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
try:
# 发送 10KB 数据
big_data = b'A' * 1024 * 10 # 10KB
# 发送固定长度包头
header = struct.pack("!I", len(big_data))
conn.sendall(header + big_data)
print(f"已发送 {len(big_data)} 字节数据")
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 接收固定长度包头
header = client.recv(4)
data_size = struct.unpack("!I", header)[0]
# 接收完整数据
received_data = b""
while len(received_data) < data_size:
remaining = data_size - len(received_data)
chunk = client.recv(min(remaining, 1024))
if not chunk:
break
received_data += chunk
print(f"实际接收长度: {len(received_data)} 字节")
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
# 实践案例
## 循环通信
服务端和客户端之间可以循环进行通信。
```python
# server.py
import socket
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
print("服务端已启动,等待连接...")
conn = None
try:
while True:
conn, client_addr = server.accept()
print(f"客户端 {client_addr} 已连接")
while True:
header = conn.recv(4)
data_size = struct.unpack("!I", header)[0]
received_data = b""
while len(received_data) < data_size:
remaining = data_size - len(received_data)
chunk = conn.recv(min(remaining, 1024))
if not chunk:
break
received_data += chunk
if (
not received_data
or received_data.decode("utf-8").strip().upper() == "EXIT"
):
break
print(f"收到消息: {received_data.decode('utf-8')}")
response = received_data.decode("utf-8").upper()
header = struct.pack("!I", len(received_data))
conn.send(header + response.encode("utf-8"))
except ConnectionResetError:
print("客户端异常断开")
except KeyboardInterrupt:
print("服务端主动终止")
except Exception as e:
print(f"未知异常: {e}")
finally:
if conn:
conn.close()
server.close()
# client.py
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))
try:
while True:
msg = input("请输入消息 (输入 EXIT 退出): ")
if not msg.strip():
print("消息不能为空,请重新输入")
continue
if msg.strip().upper() == "EXIT":
break
header = struct.pack("!I", len(msg.encode("utf-8")))
client.send(header + msg.encode("utf-8"))
header = client.recv(4)
data_size = struct.unpack("!I", header)[0]
received_data = b""
while len(received_data) < data_size:
remaining = data_size - len(received_data)
chunk = client.recv(min(remaining, 1024))
if not chunk: break
received_data += chunk
print(f"服务端返回: {received_data.decode('utf-8')}")
except KeyboardInterrupt:
print("客户端主动终止")
except BrokenPipeError:
print("服务端已关闭连接,终止发送")
except Exception as e:
print(f"异常 {e}")
finally:
client.close()
```
## 远程命令执行
客户端发送执行指令给服务端,服务端接收到指令并执行后将结果返回给客户端。
```python
import socket
import subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
print("服务端已启动,等待连接...")
conn = None
try:
while True:
conn, client_addr = server.accept()
print(f"客户端 {client_addr} 已连接")
while True:
cmd = conn.recv(1024)
if not cmd or cmd.decode('utf-8').strip().upper() == 'EXIT':
break
print(f"收到的命令: {cmd.decode('utf-8')}")
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
conn.send(correct_msg + error_msg)
except ConnectionResetError:
print("客户端异常断开")
except KeyboardInterrupt:
print("服务端主动终止")
except Exception as e:
print(f"未知异常: {e}")
finally:
if conn:
conn.close()
server.close()
```
## 时间服务端
```python
# server.py
import socket
import time
import struct
# 创建UDP套接字设置端口复用
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 避免端口占用
server_socket.bind(('', 8090))
print("时间服务器已启动,等待客户端请求...")
try:
while True:
# 接收客户端请求(空消息即可触发)
data, client_addr = server_socket.recvfrom(1024)
print(f"收到来自 {client_addr} 的请求")
# 获取当前时间并封装为二进制数据
current_time = int(time.time()) # 获取时间戳(秒级)
packed_time = struct.pack("!I", current_time) # 转为网络字节序的4字节数据提升传输效率
# 发送时间数据
server_socket.sendto(packed_time, client_addr)
except KeyboardInterrupt:
print("\n服务端主动终止")
finally:
server_socket.close()
# client.py
import socket
import struct
import time
# 创建UDP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 8090)
try:
# 发送空消息触发时间请求
client_socket.sendto(b'', server_address)
print("已发送时间请求,等待响应...")
# 接收时间数据最多等待2秒
client_socket.settimeout(2)
data, _ = client_socket.recvfrom(1024)
# 解析时间数据
if len(data) == 4:
timestamp = struct.unpack("!I", data)[0] # 解析网络字节序数据
print(f"服务器时间: {time.ctime(timestamp)}")
else:
print("错误:收到无效时间数据")
except socket.timeout:
print("请求超时,服务端未响应")
except ConnectionRefusedError:
print("连接被拒绝,请检查服务端是否运行")
except KeyboardInterrupt:
print("\n客户端主动终止")
finally:
client_socket.close()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,48 @@
# 操作系统基础
操作系统Operating System简称 OS是管理和控制计算机硬件与软件资源的计算机程序是直接运行在“裸机”上的最基本的系统软件任何其他软件都必须在操作系统的支持下才能运行。
<img src="操作系统基础/操作系统.png" alt="img-操作系统" />
操作系统应该分成两部分功能
1. 隐藏了丑陋的硬件调用接口(键盘、鼠标、音箱等等怎么实现的,就不需要你管了),为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。
例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节)
2. 将应用程序对硬件资源的竞态请求变得有序化
例如很多应用软件其实是共享一套计算机硬件比方说有可能有三个应用程序同时需要申请打印机来输出内容那么a程序竞争到了打印机资源就打印然后可能是b竞争到打印机资源也可能是c这就导致了无序打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序。
# socket
回顾一下五层通讯流程
<img src="操作系统基础/五层通讯.png" alt="img-五层通讯" />
但实际上从传输层开始以及以下,都是操作系统帮咱们完成的,下面的各种包头封装的过程
![img-socket抽象层](操作系统基础/socket抽象层.png)
Socket又称为套接字它是应用层与TCP/IP协议族通信的中间软件抽象层它是一组接口。在设计模式中Socket其实就是一个门面模式它把复杂的TCP/IP协议族隐藏在Socket接口后面对用户来说一组简单的接口就是全部让Socket去组织数据以符合指定的协议。当我们使用不同的协议进行通信时就得使用不同的接口还得处理不同协议的各种细节这就增加了开发的难度软件也不易于扩展(就像我们开发一套公司管理系统一样,报账、会议预定、请假等功能不需要单独写系统,而是一个系统上多个功能接口,不需要知道每个功能如何去实现的)。于是UNIX BSD就发明了socket这种东西socket屏蔽了各个协议的通信细节使得程序员无需关注协议本身直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用通过系统调用我们可以方便的使用磁盘文件操作使用内存而无需自己去进行磁盘读写内存管理。socket其实也是一样的东西就是提供了tcp/ip协议的抽象对外提供了一套接口同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。
其实站在你的角度上看socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。也有人将socket说成ip+port因为ip是用来标识互联网中的一台主机的位置而port是用来标识这台机器上的一个应用程序。 所以我们只要确立了ip和port就能找到一个应用程序并且使用socket模块来与之通信。
# 套接字家族
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种或者称为有两个种族,分别是基于文件型的和基于网络型的。
## 基于文件类型的套接字家族
套接字家族的名字AF_UNIX
unix一切皆文件基于文件的套接字调用的就是底层的文件系统来取数据两个套接字进程运行在同一机器可以通过访问同一个文件系统间接完成通信
## 基于网络类型的套接字家族
套接字家族的名字AF_INET
(还有AF_INET6被用于ipv6还有一些其他的地址家族不过他们要么是只用于某个平台要么就是已经被废弃或者是很少被使用或者是根本没有实现所有地址家族中AF_INET是使用最广泛的一个python支持很多种地址家族但是由于我们只关心网络编程所以大部分时候我么只使用AF_INET)
## 课后作业
- [扩展] 阅读计算机网络相关书籍

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB