IO多路复用
1.IO模型
在unix/linu下主要有四种I/O模式: 阻塞I/O: 最常用
大部分程序使用的都是阻塞模式的I/O
阻塞I/O
缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式
读操作:read,recv,recvfrom
写操作:write,send
其他操作:accept,connect
以read函数为例
进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数
read将发送阻塞
它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读
经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据 如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。
UDP 不用等待,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在
UDP套接字上执行的写操作永远都不会阻塞。
写阻塞
在写操作时发送阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数 据量的情况下。
这时,写操作不进行任何拷贝工作,将发送阻塞。
一但发送缓冲区内有足够多的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发 送数据缓冲区。
UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况, 在UDP套接字上执行的写操作永远都不会阻塞,sendto。
非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核"当我请求的I/O操作不能够马上完 成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。"
当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停的测试是否一个文件描述符有 数据可读(polling)。
应用程序不停 的polling内核来检查是否I/O操作已经就绪 ,这将是一个极浪费CPU资源的操作。 这种模式使用中不普遍。
1.fcntl()函数
当你一开始建立一个套接字描述符的时候,系统内核 将其设置为阻塞IO模式。可以使用函数fcntl()
设置一个套接字的标志位为O_NOBLOCK来实现非阻塞。
int fcntl(int fd, int cmd, long arg)
int flag; flag = fcntl(sockfd,F_GETFL,0)
flag |= O_NOBLOCK;
fcntl(sockfd,F_SETFL,flag);
2.ioctl()函数
int b_on = 1;
ioctl(sock_fd, FIONBIO,&b_on);
I/O多路复用:允许同时对多个I/O进行控制
基本常识:linux中每个进程最多可以打开1024个文件,最多有1024个文件描述符,文件描述符的特 点:
1.非负整数
2.从最小可用的数字来分配
3.每个进程启动时默认打开0、1、2三个文件描述符
多路复用针对不止套接字fd,也针对普通的文件描述符fd。
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用IO多路复用,其基本思想是: 先构造一张又关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行
IO时函数才返回。 函数返回时告诉进程那个描述符已就绪,可以进行IO操作
步骤:
1.fd_set
void FD_zero(fd_set* fdset) //对集合清零
void FD_set(int fd, fd_set * fdset) //把fd加入集合
void FD_CLR(int fd,fd_set *fdset) //从集合中清除fd void FD_ISSET(int fd,fd_set *fdset) //判断fd是否在fd_set中
2.select()
int select(int maxfdp, //maxfd+1 fd_set *readfds, //读集合
fd_set *writefds, //写集合
fd_set *errorfds, //异常集合
struct timeval *timeout //超时
);
一般:填写集合,写集合填空,异常集合(带位数据),
超时:
struct timeval{
long tv_sec; //秒
long tv_usec; //微妙
};
select退出后:集合表示有数据的集合
if(FD_ISSET(fd,set)) {
//1.如果监听套接字有数据,新的客户端进行连接,则accept
//2.若建立连接的套接字有数据,则去读read
}
int main(void)
{
struct timeval tout;
fd_set rset;
int maxfd = ‐1;
fd = socket(...);
bind(fd,...);
listen(fd,...);
while(1)
{
maxfd = fd;
FD_ZERO(&rset);
FD_SET(&rset); //依次把已经建立好连接fd加入到集合中,记录最大的文件描述符 tout.tv_sec = 5;
tout.tv_user = 0;
select(maxfd+1,&rset,NULL,NULL,&tout); //阻塞
if(FD_ISSET(fd,&rset)) //有1个或者多个文件描述符有数据
{
newfd = accept(fd,...);
//依次判断已建立连接的客户端是否有数据
//xxx
}
}
}
select()里面的各个文件描述符fd_set集合的参数在select()前后发生了变化
前:表示关心的文件描述符集合
后:有数据的集合(如不是在超时还回情况下)
kernel对集合进行了改变 信号驱动I/O:一种异步通信模型
SIGIO
案例:
服务器端:
客户端:
6.TCPIP协议原理
1.三次、四次挥手 一定要标注客户端和服务器
三次握手的连接必须是由客户端发起(四次握手客户端和服务器都可以发起)
SYN,ACK,FIN等标志符号应该写上。
TCP/IP协议中的三次握手和四次挥手是TCP(传输控制协议)建立连接和断开连接的关键过程。下面分别讲解这两个过程:
三次握手(Three-way Handshake)
三次握手是TCP建立连接的过程,它确保了通信双方都能准备好接收数据。具体过程如下:
- 第一次握手 :
- 发起方(通常是客户端) 向接收方(通常是服务器)发送一个SYN(同步序列编号)报文段,其中将SYN标志位设置为1,表示这是一个连接请求,并选择一个初始序列号seq=x。此时,客户端进入SYN_SEND状态,等待服务器的确认。
- SYN报文段:SYN=1,ACK=0,seq=x(x为客户端随机生成的初始序列号)。
- 第二次握手 :
- 接收方(服务器) 收到SYN报文段后,进行确认,设置自己的SYN标志位为1,ACK标志位为1,表示确认收到客户端的SYN报文段,并将确认号ack设置为x+1(即客户端的初始序列号加1)。同时,服务器也选择一个自己的初始序列号seq=y,并将这些信息(SYN+ACK报文段)发送给客户端。此时,服务器进入SYN_RECV状态。
- SYN+ACK报文段:SYN=1,ACK=1,seq=y,ack=x+1。
- 第三次握手 :
- 发起方(客户端) 收到服务器的SYN+ACK报文段后,向服务器发送一个ACK报文段,将ACK标志位设置为1,确认号ack设置为y+1(即服务器的初始序列号加1),表示确认收到服务器的SYN报文段。这个报文段发送完毕后,客户端和服务器都进入ESTABLISHED(已建立连接)状态,此时连接建立成功。
- ACK报文段:ACK=1,seq=x+1,ack=y+1。
四次挥手(Four-way Handshake)
四次挥手是TCP断开连接的过程,它确保了通信双方都能正常地释放资源。具体过程如下:
- 第一次挥手 :
- 主动关闭方(可以是客户端或服务器) 发送一个FIN报文段,将FIN标志位设置为1,表示没有数据要发送了,请求关闭连接。此时,主动关闭方进入FIN_WAIT_1状态。
- FIN报文段:FIN=1,seq=u(u为发送FIN报文段一方的序列号)。
- 第二次挥手 :
- 被动关闭方 收到FIN报文段后,发送一个ACK报文段,将ACK标志位设置为1,确认号ack设置为u+1,表示确认收到主动关闭方的FIN报文段。此时,被动关闭方进入CLOSE_WAIT状态,而主动关闭方则进入FIN_WAIT_2状态。
- ACK报文段:ACK=1,seq=v(v为被动关闭方的序列号),ack=u+1。
- 第三次挥手 :
- 被动关闭方 在数据传送完毕后,也发送一个FIN报文段,请求关闭连接,并设置FIN标志位为1。此时,被动关闭方进入LAST_ACK状态。
- FIN报文段:FIN=1,ACK=1,seq=w(w为被动关闭方发送FIN报文段时的序列号),ack=u+1。
- 第四次挥手 :
- 主动关闭方 收到被动关闭方的FIN报文段后,发送一个ACK报文段,将ACK标志位设置为1,确认号ack设置为w+1,表示确认收到被动关闭方的FIN报文段。此时,主动关闭方进入TIME_WAIT状态,等待一段时间后(通常是2倍的MSL,即最长报文段寿命),确保被动关闭方收到自己的ACK报文段后,最终进入CLOSED状态。而被动关闭方在收到ACK报文段后,也进入CLOSED状态。
- ACK报文段:ACK=1,seq=u+1,ack=w+1。
总结
- 三次握手 确保了通信双方都能准备好接收数据,是建立TCP连接的关键过程。
- 四次挥手 确保了通信双方都能正常地释放资源,是断开TCP连接的过程。
- 这两个过程共同保证了TCP连接的可靠性和资源的有效利用。