今天继续网络编程,基本的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);
int b_on = 1;
ioctl(sock_fd, FIONBIO, &b_on);
2、ioctl()函数
多路复用IO
基本常识:
Linux中每个进程默认情况下,最多可以打开1024个文件,最多有1024哥文件描述符
文件描述符特点:
1. 非负整数
2. 从最小的数字分配
3. 每个进程启动时默认打开0、1、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; }
写在最后
放假这两天有些懒了,今天我要更完这部分内容,主要是后面的用的也不多,前面的应用较多大家跟我一起改变世界。啊哈哈哈,求求大家给个三连再走吧