网络编程 day03
网络编程 day03
- 9. 三次握手,四次挥手
- 1. 三次握手
- 过程
- 状态
- 练习
- 四次挥手
- 过程
- 状态转换
- 练习
- 10. Linux IO 模型
- 阻塞IO
- 非阻塞IO
- 非阻塞IO的设置
- IO多路复用
- select
- 特点
- 流程
- 函数接口
9. 三次握手,四次挥手
1. 三次握手
第一次握手只能由客户端发起
过程
- 服务器处于监听状态,称为被动打开,服务器状态:listen
- 第一次握手由客户端发起,客户端调用
connect
进行主动打开,客户端向服务器发送SYN数据包,此时客户端进入SYN_SEND状态 - 第二次握手由服务器发起。服务器收到客户端的链接请求(SYN)后,向客户端回复ACK数据包,并且发送一个请求连接的数据包SYN,此时服务器变为SYN_RECV状态
- 第三次握手由客户端发起。客户端收到服务器的回复(ACK)和请求(SYN)后,向服务器回复ACK数据包。服务器收到该数据包之后,双方进入ESTABLISHED状态
状态
- listen:服务器被动打开,进入监听状态
- SYN_SEND:客户端发送链接请求之后进入该状态,等待服务器的回复和请求
- SYN_RECV:服务器发送回复和请求之后进入该状态,同时等待客户端的请求
- ESTABLISHED:服务器和客户端完成三次握手之后进入该状态,代表双方准备好进行通信
练习
- TCP在建立链接的过程中,会涉及到哪些状态的变换:
服务器:listen——》SYN_RECV——》ESTABLISHED
客户端:——》SYN_SEND——》ESTABLISHED - TCP的连接过程:三次挥手
- TCP连接的三次挥手发生在哪两个函数之间:connect——accept
- 为什么一定是三次挥手,两次挥手为什么不可以?
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而导致不必要的错误和资源的浪费。两次握手只能保证单向连接是畅通的。因为TCP是一个双向传输协议,只有经过第三次握手,才能确保双向都可以接收到对方的发送的数据。
两次挥手:客户端向服务器发送请求,一旦出现网络问题,服务器迟迟没有收到数据包,客户端也迟迟没有收到服务器的回复,客户端会废弃此次通信链接,重新发送请求连接的数据包,此时网络突然畅通,客户端发送的数据包一前一后到达服务器,服务器会发送两次同意连接数据包,建立两条通信。服务器会认为两次连接都是有效的,但实际上只有一条连接畅通,如此会造成服务器的资源浪费。这种错误也就是不必要的错误。
三次挥手:在第一条通信被废弃掉之后,客户端收到两个同意连接的数据包之后,第一个通信是废弃的,客户端只会向服务器回复一个数据包,服务器在没有收到第一个通信的回复,所以只会与客户端建立一个通信,双方都废弃了一个通信连接
四次挥手
服务器和客户端都可以发起挥手
过程
- 第一次挥手:由先调用close的一方发起,称这一端主动关闭,主动端向被动端发送分手请求FIN,主动端进入FIN_WAIT-1状态,表示数据发送完毕,主动端即将关闭
- 第二次挥手:另一端收到FIN之后,向主动端发送确认数据包ACK。被动端进入CLOSE-WAIT状态。然后发送剩余数据。
- 第三次挥手:被动端将剩余数据发送完毕后,向主动端发送FIN数据包,被动端进入LAST-ACK状态
- 第四次挥手:主动端收到FIN后,向被动端发送ACK数据包,同时进入TIME-WAIT状态,持续2MSL之后,进入CLOSED状态,彻底关闭
- 在被动端收到ACK之后,进入CLOSED状态,彻底关闭
状态转换
练习
- 描述一下四次挥手
- 第二次挥手与第三次挥手之间有一段时间间隔是为什么?
被动端的数据传输可能没有结束 - 第四次挥手之后主动断开方会等待一段时间再关闭,为什么要等待?
数据包的存活时间是1MSL,如果出现问题导致被动端没有收到ACK数据包,那么被动端会重新发送一次FIN,主动端将重新收到FIN,也会重新发送一次ACK,一来一回是两次数据包的通信,共需要2MSL
10. Linux IO 模型
阻塞IO
最常见,效率低,不耗费CPU
- 阻塞IO 模式是最普遍使用的IO 模式,大部分程序使用的都是阻塞IO
- 系统默认情况下,套接字建立后所处于的模式就是阻塞IO
非阻塞IO
轮询、耗费CPU,可以处理多路IO
- 非阻塞IO设置后,在没有完成IO操作之后,回直接返回一个错误
- 非阻塞IO设置之后,需要一个循环来不停的进行IO操作,直到完成操作
非阻塞IO的设置
- 通过函数参数设置
recv
recv(fd_accept, buf, sizeof(buf), MSG_DONTWAIT);
- 通过修改文件描述符属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置文件描述符属性
参数:
int fd
目标文件描述符
int cmd
设置模式
返回值:成功返回0,失败返回-1,更新errno
补充:
- 参数:
int cmd
参数 | 参数功能 | 第三个参数 |
---|---|---|
F_GETFL | 获取文件描述符的状态信息 | 忽略 |
F_SETFL | 设置文件描述符的状态信息 | 需要 |
O_NONBLOCK | 非阻塞 | |
O_ASYNC | 异步 | |
O_SYNC | 同步 |
- 第三个参数:int类型
int flags = fcntl(0, F_GETFL);
flags |= O_NONBLOCK; // 设置为非阻塞
fcntl(0, F_SETFL, falgs);
IO多路复用
select poll epoll
select
特点
- 1个进程最多可以监听1024个文件描述符(默认)
- select 每次被唤醒之后,要重新轮询,效率低
- select 每次都会清空未发生相应的文件描述符,每次都要经过用户空间复制到内核空间,效率低,开销大
流程
- 创建关于文件描述符的表
- 清空表——FD_ZERO
- 将关心的文件描述符添加进表中——FD_SET
- 调用select ,监听表
- if判断是哪个或哪些文件描述符响应——FD_ISSET
- 做对应的逻辑处理
函数接口
#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