网络编程 day03

发布于:2025-03-07 ⋅ 阅读:(19) ⋅ 点赞:(0)

9. 三次握手,四次挥手

1. 三次握手

第一次握手只能由客户端发起

过程

  1. 服务器处于监听状态,称为被动打开,服务器状态:listen
  2. 第一次握手由客户端发起,客户端调用connect进行主动打开,客户端向服务器发送SYN数据包,此时客户端进入SYN_SEND状态
  3. 第二次握手由服务器发起。服务器收到客户端的链接请求(SYN)后,向客户端回复ACK数据包,并且发送一个请求连接的数据包SYN,此时服务器变为SYN_RECV状态
  4. 第三次握手由客户端发起。客户端收到服务器的回复(ACK)和请求(SYN)后,向服务器回复ACK数据包。服务器收到该数据包之后,双方进入ESTABLISHED状态

状态

  1. listen:服务器被动打开,进入监听状态
  2. SYN_SEND:客户端发送链接请求之后进入该状态,等待服务器的回复和请求
  3. SYN_RECV:服务器发送回复和请求之后进入该状态,同时等待客户端的请求
  4. ESTABLISHED:服务器和客户端完成三次握手之后进入该状态,代表双方准备好进行通信
    在这里插入图片描述

练习

  1. TCP在建立链接的过程中,会涉及到哪些状态的变换:
     服务器:listen——》SYN_RECV——》ESTABLISHED
     客户端:——》SYN_SEND——》ESTABLISHED
  2. TCP的连接过程:三次挥手
  3. TCP连接的三次挥手发生在哪两个函数之间:connect——accept
  4. 为什么一定是三次挥手,两次挥手为什么不可以?
     主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而导致不必要的错误和资源的浪费。两次握手只能保证单向连接是畅通的。因为TCP是一个双向传输协议,只有经过第三次握手,才能确保双向都可以接收到对方的发送的数据。

两次挥手:客户端向服务器发送请求,一旦出现网络问题,服务器迟迟没有收到数据包,客户端也迟迟没有收到服务器的回复,客户端会废弃此次通信链接,重新发送请求连接的数据包,此时网络突然畅通,客户端发送的数据包一前一后到达服务器,服务器会发送两次同意连接数据包,建立两条通信。服务器会认为两次连接都是有效的,但实际上只有一条连接畅通,如此会造成服务器的资源浪费。这种错误也就是不必要的错误。
三次挥手:在第一条通信被废弃掉之后,客户端收到两个同意连接的数据包之后,第一个通信是废弃的,客户端只会向服务器回复一个数据包,服务器在没有收到第一个通信的回复,所以只会与客户端建立一个通信,双方都废弃了一个通信连接
在这里插入图片描述

四次挥手

服务器和客户端都可以发起挥手

过程

  1. 第一次挥手:由先调用close的一方发起,称这一端主动关闭,主动端向被动端发送分手请求FIN,主动端进入FIN_WAIT-1状态,表示数据发送完毕,主动端即将关闭
  2. 第二次挥手:另一端收到FIN之后,向主动端发送确认数据包ACK。被动端进入CLOSE-WAIT状态。然后发送剩余数据。
  3. 第三次挥手:被动端将剩余数据发送完毕后,向主动端发送FIN数据包,被动端进入LAST-ACK状态
  4. 第四次挥手:主动端收到FIN后,向被动端发送ACK数据包,同时进入TIME-WAIT状态,持续2MSL之后,进入CLOSED状态,彻底关闭
  5. 在被动端收到ACK之后,进入CLOSED状态,彻底关闭

状态转换

在这里插入图片描述

练习

  1. 描述一下四次挥手
  2. 第二次挥手与第三次挥手之间有一段时间间隔是为什么?
     被动端的数据传输可能没有结束
  3. 第四次挥手之后主动断开方会等待一段时间再关闭,为什么要等待?
    数据包的存活时间是1MSL,如果出现问题导致被动端没有收到ACK数据包,那么被动端会重新发送一次FIN,主动端将重新收到FIN,也会重新发送一次ACK,一来一回是两次数据包的通信,共需要2MSL

10. Linux IO 模型

阻塞IO

最常见,效率低,不耗费CPU

  1. 阻塞IO 模式是最普遍使用的IO 模式,大部分程序使用的都是阻塞IO
  2. 系统默认情况下,套接字建立后所处于的模式就是阻塞IO

非阻塞IO

轮询、耗费CPU,可以处理多路IO

  1. 非阻塞IO设置后,在没有完成IO操作之后,回直接返回一个错误
  2. 非阻塞IO设置之后,需要一个循环来不停的进行IO操作,直到完成操作

非阻塞IO的设置

  1. 通过函数参数设置
    recv
recv(fd_accept, buf, sizeof(buf), MSG_DONTWAIT);
  1. 通过修改文件描述符属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

功能:设置文件描述符属性
参数:
int fd目标文件描述符
int cmd设置模式
返回值:成功返回0,失败返回-1,更新errno
补充:

  1. 参数:int cmd
参数 参数功能 第三个参数
F_GETFL 获取文件描述符的状态信息 忽略
F_SETFL 设置文件描述符的状态信息 需要
O_NONBLOCK 非阻塞
O_ASYNC 异步
O_SYNC 同步
  1. 第三个参数:int类型
int flags = fcntl(0, F_GETFL);
flags |= O_NONBLOCK;	// 设置为非阻塞
fcntl(0, F_SETFL, falgs);

IO多路复用

select poll epoll

select

特点
  1. 1个进程最多可以监听1024个文件描述符(默认)
  2. select 每次被唤醒之后,要重新轮询,效率低
  3. select 每次都会清空未发生相应的文件描述符,每次都要经过用户空间复制到内核空间,效率低,开销大
流程
  1. 创建关于文件描述符的表
  2. 清空表——FD_ZERO
  3. 将关心的文件描述符添加进表中——FD_SET
  4. 调用select ,监听表
  5. if判断是哪个或哪些文件描述符响应——FD_ISSET
  6. 做对应的逻辑处理
函数接口
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);		//清除集合中的fd位
void FD_SET(int fd, fd_set *set);		//将fd放入关注列表中
int  FD_ISSET(int fd, fd_set *set);		//判断fd是否在集合中  是返回1   不是返回0
void FD_ZERO(fd_set *set);				//清空关注列表

功能:实现IO多路复用
参数:
int nfds关注的最大文件描述符+1
fd_set *readfds关注读表
fd_set *writefds写表
fd_set *exceptfds异常表
struct timeval *timeout超时设置,一般为NULL
返回值:成功返回文件描述符个数,失败返回-1,超时没有准备好返回0
在这里插入图片描述