自学内容网 自学内容网

十九、Linux网络编程(三)

5.IO多路复用

unix/linux下主要有四种I/O模式:

1.阻塞I/O: 最常用

        大部分程序使用的都是阻塞模式的I/O

        缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式

        读操作:read,recv,recvfrom

        写操作:write,send

        其他操作:accept,connect

读阻塞:

进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发送阻塞

它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读

经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据

如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。

UDP不用等待,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。


写阻塞:

在写操作时发送阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。

这时,写操作不进行任何拷贝工作,将发送阻塞。

一但发送缓冲区内有足够多的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。

UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞,sendto

2.非阻塞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);

3.I/O多路复用:允许同时对多个I/O进行控制

基本常识:linux中每个进程最多可以打开1024个文件,最多有1024个文件描述符,文件描述符的特点:

        1.非负整数

        2.从最小可用的数字来分配

        3.每个进程启动时默认打开012三个文件描述符

        多路复用针对不止套接字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,...);

                        //依次判断已建立连接的客户端是否有数据

                }

        }

}

select()里面的各个文件描述符fd_set集合的参数在select()前后发生了变化

前:表示关心的文件描述符集合

后:有数据的集合(如不是在超时还回情况下)

        kernel对集合进行了改变

信号驱动I/O:一种异步通信模型

SIGIO

6.TCPIP协议原理

三次握手:提供可靠的字节流服务,为了准确无误地将数据送达目的地

四次挥手:断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开

   一定要标注客户端和服务器

   三次握手的连接必须是由客户端发起,四次握手客户端和服务器都可以发起

   SYN,ACK,FIN等标志符号应该写上


原文地址:https://blog.csdn.net/2402_87254779/article/details/143816599

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!