【从零开始的嵌入式生活】网络编程5——IO多路复用

【从零开始的嵌入式生活】网络编程5——IO多路复用_第1张图片
今天继续网络编程,基本的TCP和UDP实现方式我们已经可以实现了,接下来就是学习一些更加底层的原理了,预计这部分是需要七天文章对应一星期的写作。这周刚好完结,希望有人愿意跟我一起学习呀。

作者简介:一个学嵌入式的年轻人
✨联系方式:2201891280(QQ)
源码地址:https://gitee.com/xingleigao/study_qianrushi
全文大约阅读时间: 60min


文章目录

  • IO模型及多路复用
    • IO模型
      • 阻塞I/O模式
      • 读阻塞
      • 写阻塞
      • 非阻塞模式I/O(不常用)
    • 多路复用IO
      • 1.fd_set()
      • 2.select
  • 写在最后


IO模型及多路复用

IO模型

在UNIX/Linux下主要有四种I/O模型

  • 阻塞I/O:
    最常用
  • 非阻塞I/O:
    可防止进程阻塞在I/O操作上,需要轮询
  • I/O多路复用
    允许同时对多个I/O进行控制
  • 信号驱动I/O:
    一种异步通信模式

阻塞I/O模式

阻塞I/O模式时最普遍使用的I/O模式,大部分程序使用的都是阻塞模式的I/O
缺省情况下,套接字建立后所处于的模式就是阻塞式I/O模式
之前使用的很多函数在调用的时候会发生阻塞

  • 读操作中 read、recv、recvfrom
  • 写操作中的 write、send、sendto默认不阻塞
  • 其他操作:accept、connect

读阻塞

  • 进程调用read函数从套接字上读取数据,当套接字的接收缓冲区还没有数据可读,就会阻塞。
  • 它会一直阻塞,等待套接字的接收缓冲区有数据可读。
  • 经过一段时间后,缓冲区内接收数据,于是内核便去唤醒该进程,然后访问。
  • 如果进程阻塞过程中,对方发生故障,就会永远阻塞下去。

写阻塞

  • 写操作发生阻塞的情况要比读操作少很多,主要时发生在要写入的缓冲区的大小小于要写入的数据量。
  • 这些,写操作不进行任何拷贝工作,讲发生阻塞。
  • 一旦发送缓冲有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。
  • UDP不等待确认,没有实际的发送缓冲区,所以UDP不存在发送缓冲区满的情况,在UDP套接字上执行写操作永远不会阻塞

非阻塞模式I/O(不常用)

  • 当我们将一个套接字设置为非阻塞模式,我们相当于告诉系统内核:“当请求I/O操作不能完成的时候不要休眠我,而是返回一个错误给我”。
  • 当一个应用使用了非阻塞模式的套接字,它需要使用一个循环来不停测试是否一个文件描述符有数据可读(polling)
  • 应用程序不停的polling内核来检查是否I/O操作已经就绪,这是一个极浪费CPU资源的操作。

非阻塞模式的实现

  1. fcntl()函数
int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockdf, F_SETEL, flag);
  1. ioctl()函数
int b_on = 1;
ioctl(sock_fd, FIONBIO, &b_on);

多路复用IO

基本常识:
Linux中每个进程默认情况下,最多可以打开1024个文件,最多有1024哥文件描述符
文件描述符特点:
1. 非负整数
2. 从最小的数字分配
3. 每个进程启动时默认打开0、1、2三个文件描述符
多路复用针对不止套接字,也针对普通文件描述符。
【从零开始的嵌入式生活】网络编程5——IO多路复用_第2张图片

1.fd_set()

void FD_ZERO(df_set *fdset)			//清空集合
void FD_SET(int fd, fd_set *fdset)	//把fd加入集合
void FD_CLR(int fd, fd_set *fdset)	//从集合中删去fd
int FD_ISSET(int fd, set *fdset)	//判断fd是否在set中

2.select

int select(int nfds, fd_set *readfds, fd_set *writefds,
			fd_set *exceptfds, struct timeval *timeout);

一般:填读集合,写集合填NULL,异常集合(带外数据)一般填NULL

struct timeval{
	long tv_sec;	/*seconds*/
	long tv_usec;	/*microsecods*/
};

时间单位:1秒(s) = 103毫秒(ms) = 106微秒(us)=109纳秒(ns) = 1012皮秒(ps)
注:select推出后,集合表示有数据的集合,内核对fd_set进行了改变。

if(FD_ISSET(fd, &Rset){
		1. 若是监听套接字有数据,则有新客户端链接,则accept()
		2 .若是建立的连接套接字有数据,则读数据
	} 

示例代码:

#include "net.h"


#define QUIT_STR "quit"
int main(void){

        int fd = -1;
        struct sockaddr_in sin;

        /*创建sockt fd*/
        if((fd = socket(AF_INET, SOCK_STREAM, 0) ) < 0){
               perror("socket");
                exit(1);
        }
        /*2.连接服务器*/
        bzero(&sin, sizeof(sin));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(SERV_PORT); //网络字节序的端口号转换
        //sin.sin_addr = inet_addr(SERV_IP_ADDR); //IPV4
        if(inet_pton(AF_INET, SERV_IP_ADDR,(void *)&sin.sin_addr) != 1){
                perror("inet_pton");
                exit(1);
        }
        if(connect(fd, (struct sockaddr *)&sin, sizeof(sin)) <0){
                perror("connect");
                exit(1);
        }

        fd_set rset;
        int maxfd = -1;
        struct timeval tout;
        char buf[BUFSIZ];
        int ret;
        while(1){
                FD_ZERO(&rset);
                FD_SET(0, &rset);
                FD_SET(fd, &rset);
                maxfd = fd;

                tout.tv_sec = 5;
                tout.tv_usec = 0;

                select(maxfd + 1, &rset, NULL, NULL, &tout);
                if(FD_ISSET(0,&rset)){
                        bzero(buf, BUFSIZ);
                        do{
                                ret = read(0, buf, BUFSIZ - 1);
                        }while(ret <0 &&EINTR == errno);
                        if(ret < 0){
                                continue;
                        }
                        if(!ret) break; //服务器关闭
                        if(write(fd, buf, strlen(buf)) < 0){
                                perror("write() to socket");
                                continue;
                        }
                        if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))){
                                printf("Client is exiting!\n");
                                break;
                        }
                }
                if(FD_ISSET(fd, &rset)){//服务器给发送过了数据
                        //的读取套接字数据,处理
                        bzero(buf, BUFSIZ);
                        do{
                                ret = read(fd, buf, BUFSIZ - 1);
                        }while(ret <0 &&EINTR == errno);
                        if(ret < 0){
                                perror("read from socket");
                                continue;
                        }
                        if(!ret) break; //服务器关闭
                        printf("serve send mesg:%s\n",buf);
                        if((strlen(buf) > strlen(SERV_RESP_STR)) && !strncasecmp(buf + strlen(SERV_RESP_STR), QUIT_STR, strlen(QUIT_STR))){
                                printf("Client is exiting!\n");
                                break;
                        }
                }
        }


        /*4.关闭服务器*/
        close(fd);
        return 0;
}

写在最后

放假这两天有些懒了,今天我要更完这部分内容,主要是后面的用的也不多,前面的应用较多大家跟我一起改变世界。啊哈哈哈,求求大家给个三连再走吧


【从零开始的嵌入式生活】网络编程5——IO多路复用_第3张图片

你可能感兴趣的:(嵌入式笔记,生活,unix,服务器)