嵌入式学习Day33
bs模型与cs模型的区别
**bs模型(Browser/Server模型)和cs模型(Client/Server模型)**是两种常见的软件架构模式,它们在结构、部署和维护等方面存在显著差异。以下是两者的主要区别:
架构差异
bs模型基于浏览器作为客户端,所有业务逻辑和数据处理集中在服务器端,客户端仅负责展示和用户交互。典型的bs应用包括网页邮箱、在线办公工具等。
cs模型需要安装独立的客户端软件,客户端承担部分业务逻辑和数据处理,服务器端负责核心数据管理和服务提供。常见的cs应用包括桌面游戏、传统ERP系统等。
部署与维护
bs模型的客户端无需安装,只需浏览器即可访问,升级和维护仅在服务器端完成,降低了用户端的维护成本。
cs模型的客户端需要单独安装和升级,每次更新可能涉及用户端的软件更新,维护成本相对较高。
性能与资源占用
bs模型的性能依赖于网络环境和服务器负载,客户端资源占用较少,适合轻量级应用。
cs模型可以充分利用客户端硬件资源,处理复杂计算和图形渲染时性能更优,适合对性能要求较高的应用
跨平台兼容性
bs模型天然具备跨平台特性,只要设备支持现代浏览器即可运行,兼容性较好。
cs模型通常需要针对不同操作系统开发特定版本,跨平台支持相对复杂。
网络依赖
bs模型高度依赖网络连接,离线状态下功能受限或无法使用。
cs模型部分功能可离线运行,数据同步可在网络恢复后进行。
-
性能表现
- BS资源消耗集中在服务器端 $$ T_{\text{响应}} = T_{\text{网络}} + T_{\text{服务器处理}} $$
- CS可分担计算到客户端($本地计算 + 数据缓存$)
-
安全性
- CS可采用私有加密协议($AES-256 + 自定义封装$)
- BS依赖HTTPS等标准安全机制
-
典型应用
- BS:Web邮箱、在线文档
- CS:大型游戏、专业设计软件
选择依据:
- 当需要快速迭代、跨平台访问时优先BS
- 对性能要求高、需要复杂交互时考虑CS
P2P模型概述
P2P(Peer-to-Peer)模型是一种分布式网络架构,节点(peers)直接通信与共享资源,无需依赖中央服务器。其核心特点是去中心化、高扩展性和容错性,广泛应用于文件共享、区块链、实时通信等领域。
关键特点
- 去中心化:节点平等参与,无单点故障风险。
- 资源共享:节点既是资源提供者(server)又是消费者(client)。
- 自组织性:网络动态调整,节点可自由加入或退出。
常见类型
- 纯P2P:无中心节点,如比特币网络。
- 混合P2P:部分依赖服务器协调,如Skype。
- 结构化P2P:使用分布式哈希表(DHT)组织节点,如Chord协议。
- 非结构化P2P:随机连接节点,如Gnutella。
TCP:
三次握手四次挥手示意图:
TCP三次握手
TCP三次握手是建立连接的过程,确保双方能够正常通信。具体步骤如下:
客户端发送SYN报文,序列号为x,进入SYN_SENT状态。SYN报文表示请求建立连接。
服务器收到SYN报文后,回复SYN+ACK报文,序列号为y,确认号为x+1,进入SYN_RCVD状态。SYN+ACK报文表示确认客户端的请求并同意建立连接。
客户端收到SYN+ACK报文后,发送ACK报文,序列号为x+1,确认号为y+1,进入ESTABLISHED状态。服务器收到ACK报文后也进入ESTABLISHED状态。
客户端 -> 服务器: SYN=1, seq=x
服务器 -> 客户端: SYN=1, ACK=1, seq=y, ack=x+1
客户端 -> 服务器: ACK=1, seq=x+1, ack=y+1
TCP四次挥手
TCP四次挥手是终止连接的过程,确保双方数据完全传输完毕。具体步骤如下:
客户端发送FIN报文,序列号为u,进入FIN_WAIT_1状态。FIN报文表示请求断开连接。
服务器收到FIN报文后,回复ACK报文,确认号为u+1,进入CLOSE_WAIT状态。客户端收到ACK报文后进入FIN_WAIT_2状态。
服务器发送FIN报文,序列号为v,进入LAST_ACK状态。FIN报文表示同意断开连接。
客户端收到FIN报文后,回复ACK报文,确认号为v+1,进入TIME_WAIT状态。服务器收到ACK报文后关闭连接。客户端等待2MSL后也关闭连接。
客户端 -> 服务器: FIN=1, seq=u
服务器 -> 客户端: ACK=1, ack=u+1
服务器 -> 客户端: FIN=1, seq=v
客户端 -> 服务器: ACK=1, ack=v+1
常见问题
三次握手确保双方收发能力正常,避免资源浪费。四次挥手确保数据完全传输,防止数据丢失。TIME_WAIT状态防止最后一个ACK丢失导致服务器无法关闭。
tcp常规流程图:
TCP服务端核心函数解析
在Linux系统中,创建TCP服务端主要涉及以下关键函数,每个函数在通信流程中承担特定角色:
socket()
创建通信端点,返回文件描述符。参数指定协议族和套接字类型:
int socket(int domain, int type, int protocol);
domain
: 通常为AF_INET
(IPv4)或AF_INET6
(IPv6)type
:SOCK_STREAM
表示TCP协议protocol
: 通常为0,让系统自动选择
错误时会返回-1,并设置errno。典型用法:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {perror("socket creation failed");exit(EXIT_FAILURE);
}
bind()
将套接字与特定IP地址和端口绑定:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
: socket()返回的文件描述符addr
: 指向包含地址信息的结构体(需转换为sockaddr*
)addrlen
: 地址结构体长度
地址结构体典型配置:
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡
servaddr.sin_port = htons(8080); // 端口号
listen()
将套接字置为被动监听状态:
int listen(int sockfd, int backlog);
backlog
: 已完成连接队列的最大长度,现代内核会自动调整这个值- 成功返回0,失败返回-1
典型调用:
if (listen(sockfd, SOMAXCONN) == -1) {perror("listen failed");close(sockfd);exit(EXIT_FAILURE);
}
accept()
接受客户端连接,返回新套接字描述符:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr
: 用于存储客户端地址信息(可设为NULL)addrlen
: 输入时为addr缓冲区长度,输出时为实际长度- 成功返回非负文件描述符,失败返回-1
recv()/send()
进行数据收发操作:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
flags
: 通常为0,可选MSG_WAITALL
等标志- 返回值表示实际收发字节数,0表示对端关闭连接,-1表示错误
close()
关闭套接字描述符:
int close(int fd);
对于服务端程序,需要分别关闭监听套接字和连接套接字。
服务端示例:
TCP客户端函数解析
在Linux中,TCP客户端通常使用套接字API实现网络通信。以下是TCP客户端中常用函数的详细解析:
socket()
用于创建一个套接字,返回文件描述符。
int socket(int domain, int type, int protocol);
domain
: 协议族,TCP通常使用AF_INET或AF_INET6type
: 套接字类型,TCP使用SOCK_STREAMprotocol
: 通常为0,系统自动选择- 返回值:成功返回非负描述符,失败返回-1
connect()
用于与服务器建立连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
: socket()返回的描述符addr
: 指向包含服务器地址和端口的结构体addrlen
: 结构体长度- 返回值:成功返回0,失败返回-1
send()
用于向已连接的套接字发送数据。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
: 已连接的套接字描述符buf
: 发送数据缓冲区len
: 发送数据长度flags
: 通常为0- 返回值:成功返回发送的字节数,失败返回-1
recv()
用于从已连接的套接字接收数据。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
: 已连接的套接字描述符buf
: 接收数据缓冲区len
: 缓冲区长度flags
: 通常为0- 返回值:成功返回接收的字节数,0表示连接关闭,-1表示错误
close()
用于关闭套接字并释放资源。
int close(int fd);
fd
: 要关闭的文件描述符- 返回值:成功返回0,失败返回-1
客户端示例:
TCP与UDP的对比
特性 | TCP | UDP |
---|---|---|
连接方式 | 面向连接 | 无连接 |
可靠性 | 可靠传输 | 尽最大努力交付 |
数据顺序 | 保证顺序 | 不保证顺序 |
流量控制 | 滑动窗口机制 | 无 |
头部开销 | 20字节(最小) | 8字节 |
适用场景 | 文件传输、Web浏览 | 视频流、DNS查询 |
TCP粘包问题概述
在Linux网络编程中,TCP粘包是指发送方多次发送的数据被接收方一次性接收的现象,导致数据边界模糊。TCP是字节流协议,本身没有"包"的概念,数据被视为连续的字节流,因此需要应用层自行处理消息边界。
粘包产生原因
发送方和接收方缓冲区机制可能导致粘包:
- 发送方Nagle算法合并小数据包
- 接收方缓冲区数据堆积后一次性读取
- 网络传输延迟导致多个数据包同时到达
解决方案
固定长度法 发送方和接收方约定固定长度的消息,不足部分填充。
特殊字符分隔法 使用特殊字符(如\n
)作为消息边界,适用于文本协议。
长度前缀法 在消息头添加长度字段,接收方先读长度再读内容。最常用的可靠方案。
注意事项
- UDP不存在粘包问题,因其保留消息边界
- 异步/非阻塞IO场景需配合状态机处理半包情况
- 网络字节序转换需使用
htonl/ntohl
等函数保证跨平台兼容性